Update Javascript GLV Address feedback and provide maven integration: - Reorganize gremlin-javascript into node.js project - Simplify javascript code generators - Generate package.json to match project version - Introduce Promise factory for third-party promise library integration (ie: bluebird) - Use toList() and next() traversal methods - Include Remote connection implementation - Fix enum casing - Use Maven nodejs plugin - .gitignore and .npmignore at gremlin-javascript level - Run integration tests using a gremlin-server instance - Add gremlin-javascript doc in gremlin-variants.asciidoc
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/baeabd7c Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/baeabd7c Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/baeabd7c Branch: refs/heads/TINKERPOP-1489 Commit: baeabd7c81f823b3586d0c6b4649e6cfa2c3204d Parents: 2bedccc Author: Jorge Bay Gondra <[email protected]> Authored: Tue Jun 6 15:05:14 2017 +0200 Committer: Stephen Mallette <[email protected]> Committed: Wed Jun 28 15:14:02 2017 -0400 ---------------------------------------------------------------------- docs/src/reference/gremlin-variants.asciidoc | 71 + gremlin-javascript/pom.xml | 171 +- .../javascript/GenerateGremlinJavascript.groovy | 36 + .../GraphTraversalSourceGenerator.groovy | 226 +- .../javascript/PackageJsonGenerator.groovy | 72 + .../gremlin/javascript/SymbolHelper.groovy | 50 + .../javascript/TraversalSourceGenerator.groovy | 437 ++-- .../javascript/GenerateGremlinJavascript.java | 32 - .../gremlin/javascript/jsr223/SymbolHelper.java | 59 - .../javascript/gremlin-javascript/.gitignore | 5 + .../javascript/gremlin-javascript/.npmignore | 26 + .../javascript/gremlin-javascript/README.md | 39 + .../driver/remote-connection.js | 107 - .../main/javascript/gremlin-javascript/index.js | 113 +- .../lib/driver/driver-remote-connection.js | 200 ++ .../lib/driver/remote-connection.js | 84 + .../gremlin-javascript/lib/process/bytecode.js | 99 + .../lib/process/graph-traversal.js | 2095 ++++++++++++++++++ .../lib/process/traversal-strategy.js | 88 + .../gremlin-javascript/lib/process/traversal.js | 236 ++ .../gremlin-javascript/lib/structure/graph.js | 138 ++ .../lib/structure/io/graph-serializer.js | 398 ++++ .../javascript/gremlin-javascript/lib/utils.js | 62 + .../javascript/gremlin-javascript/package.json | 36 + .../process/graph-traversal.js | 2016 ----------------- .../gremlin-javascript/process/traversal.js | 392 ---- .../gremlin-javascript/structure/graph.js | 157 -- .../structure/io/graph-serializer.js | 415 ---- .../test/integration/remote-connection-tests.js | 64 + .../test/integration/traversal-test.js | 71 + .../test/unit/exports-test.js | 73 + .../test/unit/graphson-test.js | 112 + .../test/unit/traversal-test.js | 119 + .../javascript/gremlin-javascript/helper.js | 84 - .../gremlin-javascript/test-exports.js | 87 - .../gremlin-javascript/test-graphson.js | 108 - .../gremlin-javascript/test-traversal.js | 69 - pom.xml | 4 + 38 files changed, 4623 insertions(+), 4028 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/docs/src/reference/gremlin-variants.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 39b5558..7eaa706 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -354,4 +354,75 @@ graphson_writer = GraphSONWriter({MyType: MyType}) connection = DriverRemoteConnection('ws://localhost:8182/gremlin', 'g', graphson_reader=graphson_reader, graphson_writer=graphson_writer) +---- + +[[gremlin-javascript]] +Gremlin-JavaScript +------------------ + +Apache TinkerPop's Gremlin-JavaScript implements Gremlin within the JavaScript language. It targets Node.js runtime +and can be used on different operating systems on any Node.js 4 or above. Since the JavaScript naming conventions are +very similar to that of Java, it should be very easy to switch between Gremlin-Java and Gremlin-JavaScript. + +[source,bash] +npm install gremlin-javascript + +In Gremlin-JavaScript there exists `GraphTraversalSource`, `GraphTraversal`, and `__` which mirror the respective classes +in Gremlin-Java. The `GraphTraversalSource` requires a RemoteConnection implementation in order to communicate with +<<gremlin-server,GremlinServer>>. + +A traversal source can be spawned with `RemoteStrategy` from an empty `Graph`. + +[source,javascript] +---- +const graph = new Graph(); +const g = graph.traversal().withRemote(new DriverRemoteConnection('ws://localhost:8182/gremlin')); +---- + +When a traversal from the `GraphTraversalSource` is iterated, the traversalâs `Bytecode` is sent over the wire via +the registered `RemoteConnection`. The bytecode is used to construct the equivalent traversal at the remote +traversal source. + +Since Gremlin-JavaScript currently doesn't support lambda expressions, all traversals can be translated to +Gremlin-Java on the remote location (e.g. Gremlin Server). + +IMPORTANT: Gremlin-JavaScriptâs `Traversal` base class supports the standard Gremlin methods such as `next()` and +`toList()` Such "terminal" methods trigger the evaluation of the traversal. + +RemoteConnection Submission +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Very similar to Gremlin-Python and Gremlin-Java, there are various ways to submit a traversal to a +`RemoteConnection` using terminal/action methods off of `Traversal`. + +* `Traversal.next()` +* `Traversal.toList()` + +Static Enums and Methods +~~~~~~~~~~~~~~~~~~~~~~~~ + +Gremlin has various tokens (e.g. `t`, `P`, `order`, `direction`, etc.) that are represented in Gremlin-JavaScript as +objects. + +These can be used analogously to how they are used in Gremlin-Java. + +[source,javascript] +g.V().hasLabel("person").has("age",P.gt(30)).Order().By("age", order.decr).toList() + +These objects must be required manually from the `process` namespace: + +[source,javascript] +---- +const gremlin = require('gremlin-javascript'); +const P = gremlin.process.P; +---- + +Finally, using static `__` anonymous traversals like `__.out()` can be expressed as below: + +[source,javascript] +---- +const gremlin = require('gremlin-javascript'); +const __ = gremlin.process.statics; + +g.V().repeat(__.out()).times(2).values("name").fold().toList(); ---- \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/pom.xml ---------------------------------------------------------------------- diff --git a/gremlin-javascript/pom.xml b/gremlin-javascript/pom.xml index b9b78cc..1c9ffcc 100644 --- a/gremlin-javascript/pom.xml +++ b/gremlin-javascript/pom.xml @@ -37,6 +37,7 @@ limitations under the License. <version>${groovy.version}</version> <classifier>indy</classifier> </dependency> + <!-- TESTING --> <dependency> <groupId>org.apache.tinkerpop</groupId> <artifactId>tinkergraph-gremlin</artifactId> @@ -58,12 +59,10 @@ limitations under the License. <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> - <version>${slf4j.version}</version> <scope>test</scope> </dependency> </dependencies> <properties> - <!-- provides a way to convert maven.test.skip value to skipTests for use in skipping python tests --> <maven.test.skip>false</maven.test.skip> <skipTests>${maven.test.skip}</skipTests> <gremlin.server.dir>${project.parent.basedir}/gremlin-server</gremlin.server.dir> @@ -86,33 +85,92 @@ limitations under the License. <configuration> <mainClass>org.apache.tinkerpop.gremlin.javascript.GenerateGremlinJavascript</mainClass> <arguments> - <argument>${basedir}/src/main/javascript/gremlin-javascript/process/traversal.js</argument> - <argument>${basedir}/src/main/javascript/gremlin-javascript/process/graph-traversal.js</argument> + <argument>${project.version}</argument> + <argument>${basedir}</argument> </arguments> </configuration> </execution> + </executions> + </plugin> + <plugin> + <groupId>org.codehaus.gmavenplus</groupId> + <artifactId>gmavenplus-plugin</artifactId> + <dependencies> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <version>1.2.17</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.apache.tinkerpop</groupId> + <artifactId>gremlin-server</artifactId> + <version>${project.version}</version> + <scope>runtime</scope> + </dependency> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-ant</artifactId> + <version>${groovy.version}</version> + </dependency> + </dependencies> + <executions> <execution> - <id>js-tests</id> - <phase>test</phase> + <id>gremlin-server-start</id> + <phase>pre-integration-test</phase> <goals> - <goal>exec</goal> + <goal>execute</goal> </goals> <configuration> - <executable>jjs</executable> - <arguments> - <argument>${basedir}/src/test/javascript/gremlin-javascript/test-exports.js</argument> - <argument>${basedir}/src/test/javascript/gremlin-javascript/test-graphson.js</argument> - <argument>${basedir}/src/test/javascript/gremlin-javascript/test-traversal.js</argument> - </arguments> + <scripts> + <script> + <![CDATA[ +import org.apache.tinkerpop.gremlin.server.GremlinServer +import org.apache.tinkerpop.gremlin.server.Settings +import org.apache.tinkerpop.gremlin.server.Settings.ScriptEngineSettings +import org.apache.tinkerpop.gremlin.server.op.session.Session + +if (${skipIntegrationTests}) return +log.info("Starting Gremlin Server instances for native testing of gremlin-javascript") +def settings = Settings.read("${gremlin.server.dir}/conf/gremlin-server-modern.yaml") +settings.graphs.graph = "${gremlin.server.dir}/conf/tinkergraph-empty.properties" +settings.scriptEngines["gremlin-groovy"].scripts = ["${gremlin.server.dir}/scripts/generate-modern.groovy"] +settings.port = 45950 +def server = new GremlinServer(settings) +server.start().join() +project.setContextValue("gremlin.javascript.server", server) +log.info("Gremlin Server started on port 45950") +]]> + </script> + </scripts> + </configuration> + </execution> + <execution> + <id>gremlin-server-stop</id> + <phase>post-integration-test</phase> + <goals> + <goal>execute</goal> + </goals> + <configuration> + <scripts> + <script> + <![CDATA[ +import org.apache.tinkerpop.gremlin.server.GremlinServer +import org.apache.tinkerpop.gremlin.server.op.session.Session + +if (${skipIntegrationTests}) return +log.info("Tests for native gremlin-javascript complete") +def server = project.getContextValue("gremlin.javascript.server") +log.info("Shutting down $server") +server.stop().join() +]]> + </script> + </scripts> </configuration> </execution> </executions> </plugin> <plugin> - <groupId>org.codehaus.gmavenplus</groupId> - <artifactId>gmavenplus-plugin</artifactId> - </plugin> - <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> <version>3.0.0</version> @@ -124,6 +182,85 @@ limitations under the License. </execution> </executions> </plugin> + <plugin> + <groupId>com.github.eirslett</groupId> + <artifactId>frontend-maven-plugin</artifactId> + <version>1.4</version> + <executions> + <execution> + <id>install node and npm</id> + <goals> + <goal>install-node-and-npm</goal> + </goals> + <phase>generate-test-resources</phase> + </execution> + <execution> + <id>npm install</id> + <goals> + <goal>npm</goal> + </goals> + <phase>generate-test-resources</phase> + <configuration> + <arguments>install</arguments> + </configuration> + </execution> + <execution> + <id>npm test</id> + <goals> + <goal>npm</goal> + </goals> + <phase>integration-test</phase> + <configuration> + <arguments>test</arguments> + </configuration> + </execution> + </executions> + <configuration> + <skip>${skipIntegrationTests}</skip> + <workingDirectory>src/main/javascript/gremlin-javascript</workingDirectory> + <nodeVersion>v4.8.3</nodeVersion> + </configuration> + </plugin> </plugins> </build> + <profiles> + <!-- + Provides a way to deploy the gremlin-javascript GLV to npm. This cannot be part of the standard maven execution + because npm does not have a staging environment like sonatype for releases. As soon as the release is + published it is public. In our release workflow, deploy occurs prior to vote on the release and we can't + make this stuff public until the vote is over. + --> + <profile> + <id>glv-javascript-deploy</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <plugins> + <plugin> + <groupId>com.github.eirslett</groupId> + <artifactId>frontend-maven-plugin</artifactId> + <version>1.4</version> + <executions> + <execution> + <id>npm publish</id> + <phase>deploy</phase> + <goals> + <goal>npm</goal> + </goals> + <configuration> + <arguments>publish</arguments> + </configuration> + </execution> + </executions> + <configuration> + <skip>${skipIntegrationTests}</skip> + <workingDirectory>src/main/javascript/gremlin-javascript</workingDirectory> + <nodeVersion>v4.8.3</nodeVersion> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy new file mode 100644 index 0000000..ab9eaf1 --- /dev/null +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.groovy @@ -0,0 +1,36 @@ +/* + * 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.javascript + +public class GenerateGremlinJavascript { + public static void main(String[] args) { + String projectVersion = args[0]; + final String baseDir = args[1]; + final String jsModuleDir = "${baseDir}/src/main/javascript/gremlin-javascript"; + Integer versionSnapshotIndex = projectVersion.indexOf("-SNAPSHOT"); + if (versionSnapshotIndex > 0) { + // Use a alpha version x.y.z-alpha1 + projectVersion = projectVersion.substring(0, versionSnapshotIndex) + "-alpha1"; + } + TraversalSourceGenerator.create("$jsModuleDir/lib/process/traversal.js"); + GraphTraversalSourceGenerator.create("$jsModuleDir/lib/process/graph-traversal.js"); + PackageJsonGenerator.create("$jsModuleDir/package.json", projectVersion); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy index 0ae6079..3227f2c 100644 --- a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/GraphTraversalSourceGenerator.groovy @@ -23,7 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__ -import org.apache.tinkerpop.gremlin.javascript.jsr223.SymbolHelper +import org.apache.tinkerpop.gremlin.javascript.SymbolHelper import java.lang.reflect.Modifier @@ -63,45 +63,46 @@ class GraphTraversalSourceGenerator { /** * @author Jorge Bay Gondra */ -(function defineGraphTraversalModule() { - "use strict"; - - var t = loadModule.call(this, './traversal.js'); - var remote = loadModule.call(this, '../driver/remote-connection.js'); - var Bytecode = t.Bytecode; - var inherits = t.inherits; - var parseArgs = t.parseArgs; - - /** - * - * @param {Graph} graph - * @param {TraversalStrategies} traversalStrategies - * @param {Bytecode} [bytecode] - * @constructor - */ - function GraphTraversalSource(graph, traversalStrategies, bytecode) { - this.graph = graph; - this.traversalStrategies = traversalStrategies; - this.bytecode = bytecode || new Bytecode(); - } - - /** - * @param remoteConnection - * @returns {GraphTraversalSource} - */ - GraphTraversalSource.prototype.withRemote = function (remoteConnection) { - var traversalStrategy = new t.TraversalStrategies(this.traversalStrategies); - traversalStrategy.addStrategy(new remote.RemoteStrategy(remoteConnection)); - return new GraphTraversalSource(this.graph, traversalStrategy, new Bytecode(this.bytecode)); - }; - - /** - * Returns the string representation of the GraphTraversalSource. - * @returns {string} - */ - GraphTraversalSource.prototype.toString = function () { - return 'graphtraversalsource[' + this.graph.toString() + ']'; - }; +'use strict'; + +var t = require('./traversal.js'); +var remote = require('../driver/remote-connection'); +var utils = require('../utils'); +var Bytecode = require('./bytecode'); +var TraversalStrategies = require('./traversal-strategy').TraversalStrategies; +var inherits = utils.inherits; +var parseArgs = utils.parseArgs; + +/** + * + * @param {Graph} graph + * @param {TraversalStrategies} traversalStrategies + * @param {Bytecode} [bytecode] + * @constructor + */ +function GraphTraversalSource(graph, traversalStrategies, bytecode) { + this.graph = graph; + this.traversalStrategies = traversalStrategies; + this.bytecode = bytecode || new Bytecode(); +} + +/** + * @param remoteConnection + * @returns {GraphTraversalSource} + */ +GraphTraversalSource.prototype.withRemote = function (remoteConnection) { + var traversalStrategy = new TraversalStrategies(this.traversalStrategies); + traversalStrategy.addStrategy(new remote.RemoteStrategy(remoteConnection)); + return new GraphTraversalSource(this.graph, traversalStrategy, new Bytecode(this.bytecode)); +}; + +/** + * Returns the string representation of the GraphTraversalSource. + * @returns {string} + */ +GraphTraversalSource.prototype.toString = function () { + return 'graphtraversalsource[' + this.graph.toString() + ']'; +}; """) GraphTraversalSource.getMethods(). // SOURCE STEPS findAll { GraphTraversalSource.class.equals(it.returnType) }. @@ -110,40 +111,40 @@ class GraphTraversalSourceGenerator { !it.name.equals(TraversalSource.Symbols.withBindings) && !it.name.equals(TraversalSource.Symbols.withRemote) }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - forEach { method -> + forEach { methodName -> moduleOutput.append( """ - /** - * ${method} GraphTraversalSource method. - * @param {...Object} args - * @returns {GraphTraversalSource} - */ - GraphTraversalSource.prototype.${method} = function (args) { - var b = new Bytecode(this.bytecode).addSource('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - return new GraphTraversalSource(this.graph, new t.TraversalStrategies(this.traversalStrategies), b); - }; +/** + * Graph Traversal Source ${methodName} method. + * @param {...Object} args + * @returns {GraphTraversalSource} + */ +GraphTraversalSource.prototype.${SymbolHelper.toJs(methodName)} = function (args) { + var b = new Bytecode(this.bytecode).addSource('$methodName', parseArgs.apply(null, arguments)); + return new GraphTraversalSource(this.graph, new TraversalStrategies(this.traversalStrategies), b); +}; """) } - GraphTraversalSource.getMethods(). // SPAWN STEPS + GraphTraversalSource.getMethods(). findAll { GraphTraversal.class.equals(it.returnType) }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - forEach { method -> + forEach { methodName -> moduleOutput.append( """ - /** - * ${method} GraphTraversalSource step method. - * @param {...Object} args - * @returns {GraphTraversal} - */ - GraphTraversalSource.prototype.${method} = function (args) { - var b = new Bytecode(this.bytecode).addStep('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - return new GraphTraversal(this.graph, new t.TraversalStrategies(this.traversalStrategies), b); - }; +/** + * $methodName GraphTraversalSource step method. + * @param {...Object} args + * @returns {GraphTraversal} + */ +GraphTraversalSource.prototype.${SymbolHelper.toJs(methodName)} = function (args) { + var b = new Bytecode(this.bytecode).addStep('$methodName', parseArgs.apply(null, arguments)); + return new GraphTraversal(this.graph, new TraversalStrategies(this.traversalStrategies), b); +}; """) } //////////////////// @@ -151,34 +152,35 @@ class GraphTraversalSourceGenerator { //////////////////// moduleOutput.append( """ - /** - * Represents a graph traversal. - * @extends Traversal - * @constructor - */ - function GraphTraversal(graph, traversalStrategies, bytecode) { - t.Traversal.call(this, graph, traversalStrategies, bytecode); - } - - inherits(GraphTraversal, t.Traversal); +/** + * Represents a graph traversal. + * @extends Traversal + * @constructor + */ +function GraphTraversal(graph, traversalStrategies, bytecode) { + t.Traversal.call(this, graph, traversalStrategies, bytecode); +} + +inherits(GraphTraversal, t.Traversal); """) GraphTraversal.getMethods(). findAll { GraphTraversal.class.equals(it.returnType) }. findAll { !it.name.equals("clone") && !it.name.equals("iterate") }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - forEach { method -> + forEach { methodName -> moduleOutput.append( """ - /** - * @param {...Object} args - * @returns {GraphTraversal} - */ - GraphTraversal.prototype.${method} = function (args) { - this.bytecode.addStep('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - return this; - }; +/** + * Graph traversal $methodName method. + * @param {...Object} args + * @returns {GraphTraversal} + */ +GraphTraversal.prototype.${SymbolHelper.toJs(methodName)} = function (args) { + this.bytecode.addStep('$methodName', parseArgs.apply(null, arguments)); + return this; +}; """) }; @@ -186,11 +188,11 @@ class GraphTraversalSourceGenerator { // AnonymousTraversal // //////////////////////// moduleOutput.append(""" - /** - * Contains the static method definitions - * @type {Object} - */ - var statics = {}; +/** + * Contains the static method definitions + * @type {Object} + */ +var statics = {}; """); __.class.getMethods(). findAll { GraphTraversal.class.equals(it.returnType) }. @@ -202,44 +204,24 @@ class GraphTraversalSourceGenerator { forEach { method -> moduleOutput.append( """ - /** - * ${method}() static method - * @param {...Object} args - * @returns {GraphTraversal} - */ - statics.${method} = function (args) { - var g = new GraphTraversal(null, null, new Bytecode()); - return g.${method}.apply(g, arguments); - }; +/** + * ${method}() static method + * @param {...Object} args + * @returns {GraphTraversal} + */ +statics.${method} = function (args) { + var g = new GraphTraversal(null, null, new Bytecode()); + return g.${method}.apply(g, arguments); +}; """) }; moduleOutput.append(""" - function loadModule(moduleName) { - if (typeof require !== 'undefined') { - return require(moduleName); - } - if (typeof load !== 'undefined') { - var path = new java.io.File(__DIR__ + moduleName).getCanonicalPath(); - this.__dependencies = this.__dependencies || {}; - return this.__dependencies[path] = (this.__dependencies[path] || load(path)); - } - throw new Error('No module loader was found'); - } - - var toExport = { - GraphTraversal: GraphTraversal, - GraphTraversalSource: GraphTraversalSource, - statics: statics - }; - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; - } - // Nashorn and rest - return toExport; -}).call(this);""") +module.exports = { + GraphTraversal: GraphTraversal, + GraphTraversalSource: GraphTraversalSource, + statics: statics +};"""); // save to file final File file = new File(graphTraversalSourceFile); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy new file mode 100644 index 0000000..4b4d012 --- /dev/null +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/PackageJsonGenerator.groovy @@ -0,0 +1,72 @@ +/* + * 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.javascript + +/** + * @author Jorge Bay Gondra + */ +class PackageJsonGenerator { + + public static void create(final String traversalSourceFile, final String version) { + + final StringBuilder moduleOutput = new StringBuilder(); + moduleOutput.append("""{ + "name": "gremlin-javascript", + "version": "${version}", + "description": "JavaScript Gremlin Language Variant", + "author": "Apache TinkerPop team", + "keywords": [ + "graph", + "gremlin", + "tinkerpop", + "connection", + "glv", + "driver", + "graphdb" + ], + "license": "Apache-2.0", + "dependencies": { + "ws": "^3.0.0" + }, + "devDependencies": { + "mocha": ">= 1.14.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/apache/tinkerpop.git" + }, + "bugs": { + "url": "https://issues.apache.org/jira/browse/TINKERPOP" + }, + "scripts": { + "test": "./node_modules/.bin/mocha test --recursive -t 5000", + "unit-test": "./node_modules/.bin/mocha test/unit" + }, + "engines": { + "node": ">=4" + } +}""" ); + + // save to a file + final File file = new File(traversalSourceFile); + file.delete() + moduleOutput.eachLine { file.append(it + "\n") } + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy new file mode 100644 index 0000000..ed45cdb --- /dev/null +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/SymbolHelper.groovy @@ -0,0 +1,50 @@ +/* + * 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.javascript + +/** + * @author Jorge Bay Gondra + */ +public final class SymbolHelper { + + private final static Map<String, String> TO_JS_MAP = new HashMap<>(); + + static { + TO_JS_MAP.put("in", "in_"); + TO_JS_MAP.put("from", "from_"); + } + + private SymbolHelper() { + // static methods only, do not instantiate + } + + public static String toJs(final String symbol) { + return TO_JS_MAP.getOrDefault(symbol, symbol); + } + + public static String decapitalize(String string) { + if (string == null || string.length() == 0) { + return string; + } + def c = string.toCharArray(); + c[0] = Character.toLowerCase(c[0]); + return new String(c); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy index d5899f0..f3405c3 100644 --- a/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy +++ b/gremlin-javascript/src/main/groovy/org/apache/tinkerpop/gremlin/javascript/TraversalSourceGenerator.groovy @@ -21,7 +21,6 @@ package org.apache.tinkerpop.gremlin.javascript import org.apache.tinkerpop.gremlin.process.traversal.P import org.apache.tinkerpop.gremlin.util.CoreImports -import org.apache.tinkerpop.gremlin.javascript.jsr223.SymbolHelper import java.lang.reflect.Modifier @@ -57,334 +56,178 @@ class TraversalSourceGenerator { /** * @author Jorge Bay Gondra */ -(function defineTraversalModule() { - "use strict"; - - function Traversal(graph, traversalStrategies, bytecode) { - this.graph = graph; - this.traversalStrategies = traversalStrategies; - this.bytecode = bytecode; - this.traversers = null; - this.sideEffects = null; - } +'use strict'; + +var utils = require('../utils'); +var parseArgs = utils.parseArgs; +var itemDone = Object.freeze({ value: null, done: true }); +var emptyArray = Object.freeze([]); + +function Traversal(graph, traversalStrategies, bytecode) { + this.graph = graph; + this.traversalStrategies = traversalStrategies; + this.bytecode = bytecode; + this.traversers = null; + this.sideEffects = null; + this._traversalStrategiesPromise = null; + this._traversersIteratorIndex = 0; +} - /** @returns {Bytecode} */ - Traversal.prototype.getBytecode = function () { - return this.bytecode; - }; +/** @returns {Bytecode} */ +Traversal.prototype.getBytecode = function () { + return this.bytecode; +}; - /** @param {Function} callback */ - Traversal.prototype.list = function (callback) { - var self = this; - this.traversalStrategies.applyStrategies(this, function (err) { - if (err) { - return callback(err); - } - callback(err, self.traversers); - }); - }; +/** + * Returns an Array containing the traverser objects. + * @returns {Promise.<Array>} + */ +Traversal.prototype.toList = function () { + var self = this; + return this._applyStrategies().then(function () { + if (!self.traversers || self._traversersIteratorIndex === self.traversers.length) { + return emptyArray; + } + var arr = new Array(self.traversers.length - self._traversersIteratorIndex); + for (var i = self._traversersIteratorIndex; i < self.traversers.length; i++) { + arr[i] = self.traversers[i].object; + } + self._traversersIteratorIndex = self.traversers.length; + return arr; + }); +}; - /** @param {Function} callback */ - Traversal.prototype.one = function (callback) { - this.list(function (err, result) { - callback(err, result && result.length > 0 ? result[0] : null); - }); - }; +/** + * Async iterator method implementation. + * Returns a promise containing an iterator item. + * @returns {Promise.<{value, done}>} + */ +Traversal.prototype.next = function () { + var self = this; + return this._applyStrategies().then(function () { + if (!self.traversers || self._traversersIteratorIndex === self.traversers.length) { + return itemDone; + } + return { value: self.traversers[self._traversersIteratorIndex++].object, done: false }; + }); +}; + +Traversal.prototype._applyStrategies = function () { + if (this._traversalStrategiesPromise) { + // Apply strategies only once + return this._traversalStrategiesPromise; + } + return this._traversalStrategiesPromise = this.traversalStrategies.applyStrategies(this); +}; - /** - * Returns the Bytecode JSON representation of the traversal - * @returns {String} - */ - Traversal.prototype.toString = function () { - return this.bytecode.toString(); - }; +/** + * Returns the Bytecode JSON representation of the traversal + * @returns {String} + */ +Traversal.prototype.toString = function () { + return this.bytecode.toString(); +}; """); moduleOutput.append(""" - /** - * Represents an operation. - * @constructor - */ - function P(operator, value, other) { - this.operator = operator; - this.value = value; - this.other = other; - } - - /** - * Returns the string representation of the instance. - * @returns {string} - */ - P.prototype.toString = function () { - if (this.other === undefined) { - return this.operator + '(' + this.value + ')'; - } - return this.operator + '(' + this.value + ', ' + this.other + ')'; - }; +/** + * Represents an operation. + * @constructor + */ +function P(operator, value, other) { + this.operator = operator; + this.value = value; + this.other = other; +} - function createP(operator, args) { - args.unshift(null, operator); - return new (Function.prototype.bind.apply(P, args)); +/** + * Returns the string representation of the instance. + * @returns {string} + */ +P.prototype.toString = function () { + if (this.other === undefined) { + return this.operator + '(' + this.value + ')'; } + return this.operator + '(' + this.value + ', ' + this.other + ')'; +}; + +function createP(operator, args) { + args.unshift(null, operator); + return new (Function.prototype.bind.apply(P, args)); +} """) P.class.getMethods(). findAll { Modifier.isStatic(it.getModifiers()) }. findAll { P.class.isAssignableFrom(it.returnType) }. - collect { SymbolHelper.toJs(it.name) }. + collect { it.name }. unique(). sort { a, b -> a <=> b }. - each { method -> + each { methodName -> moduleOutput.append( """ - /** @param {...Object} args */ - P.${method} = function (args) { - return createP('${SymbolHelper.toJava(method)}', parseArgs.apply(null, arguments)); - }; +/** @param {...Object} args */ +P.${SymbolHelper.toJs(methodName)} = function (args) { + return createP('$methodName', parseArgs.apply(null, arguments)); +}; """) }; moduleOutput.append(""" - P.prototype.and = function (arg) { - return new P('and', this, arg); - }; +P.prototype.and = function (arg) { + return new P('and', this, arg); +}; - P.prototype.or = function (arg) { - return new P('or', this, arg); - }; +P.prototype.or = function (arg) { + return new P('or', this, arg); +}; """) moduleOutput.append(""" - function Traverser(object, bulk) { - this.object = object; - this.bulk = bulk == undefined ? 1 : bulk; - } - - function TraversalSideEffects() { - - } - - /** - * Creates a new instance of TraversalStrategies. - * @param {TraversalStrategies} [traversalStrategies] - * @constructor - */ - function TraversalStrategies(traversalStrategies) { - /** @type {Array<TraversalStrategy>} */ - this.strategies = traversalStrategies ? traversalStrategies.strategies : []; - } - - /** @param {TraversalStrategy} strategy */ - TraversalStrategies.prototype.addStrategy = function (strategy) { - this.strategies.push(strategy); - }; - - /** - * @param {Traversal} traversal - * @param {Function} callback - */ - TraversalStrategies.prototype.applyStrategies = function (traversal, callback) { - eachSeries(this.strategies, function eachStrategy(s, next) { - s.apply(traversal, next); - }, callback); - }; - - /** - * @abstract - * @constructor - */ - function TraversalStrategy() { - - } - - /** - * @abstract - * @param {Traversal} traversal - * @param {Function} callback - */ - TraversalStrategy.prototype.apply = function (traversal, callback) { - - }; - - /** - * Creates a new instance of Bytecode - * @param {Bytecode} [toClone] - * @constructor - */ - function Bytecode(toClone) { - this._bindings = {}; - if (!toClone) { - this.sourceInstructions = []; - this.stepInstructions = []; - } - else { - this.sourceInstructions = toClone.sourceInstructions.slice(0); - this.stepInstructions = toClone.sourceInstructions.slice(0); - } - } - - /** - * Adds a new source instructions - * @param {String} name - * @param {Array} values - * @returns {Bytecode} - */ - Bytecode.prototype.addSource = function (name, values) { - if (name === undefined) { - throw new Error('Name is not defined'); - } - var instruction = new Array(values.length + 1); - instruction[0] = name; - for (var i = 0; i < values.length; ++i) { - instruction[i + 1] = this._convertToArgument(values[i]); - } - this.sourceInstructions.push(this._generateInstruction(name, values)); - return this; - }; - - /** - * Adds a new step instructions - * @param {String} name - * @param {Array} values - * @returns {Bytecode} - */ - Bytecode.prototype.addStep = function (name, values) { - if (name === undefined) { - throw new Error('Name is not defined'); - } - this.stepInstructions.push(this._generateInstruction(name, values)); - return this; - }; - - Bytecode.prototype._generateInstruction = function (name, values) { - var instruction = new Array(values.length + 1); - instruction[0] = name; - for (var i = 0; i < values.length; ++i) { - instruction[i + 1] = this._convertToArgument(values[i]); - } - return instruction; - }; - - /** - * Returns the JSON representation of the source and step instructions - * @returns {String} - */ - Bytecode.prototype.toString = function () { - return ( - (this.sourceInstructions.length > 0 ? JSON.stringify(this.sourceInstructions) : '') + - (this.stepInstructions.length > 0 ? JSON.stringify(this.stepInstructions) : '') - ); - }; - - Bytecode.prototype._convertToArgument = function (value) { - return value; - }; - - function toEnum(typeName, keys) { - var result = {}; - keys.split(' ').forEach(function (k) { - if (k === k.toUpperCase()) { - k = k.toLowerCase(); - } - result[k] = new EnumValue(typeName, k); - }); - return result; - } - - function EnumValue(typeName, elementName) { - this.typeName = typeName; - this.elementName = elementName; - } +function Traverser(object, bulk) { + this.object = object; + this.bulk = bulk == undefined ? 1 : bulk; +} - // Utility functions - /** @returns {Array} */ - function parseArgs() { - return (arguments.length === 1 ? [ arguments[0] ] : Array.apply(null, arguments)); - } +function TraversalSideEffects() { - /** - * @param {Array} arr - * @param {Function} fn - * @param {Function} [callback] - */ - function eachSeries(arr, fn, callback) { - if (!Array.isArray(arr)) { - throw new TypeError('First parameter is not an Array'); - } - callback = callback || noop; - var length = arr.length; - if (length === 0) { - return callback(); - } - var sync; - var index = 1; - fn(arr[0], next); - if (sync === undefined) { - sync = false; - } +} - function next(err) { - if (err) { - return callback(err); - } - if (index >= length) { - return callback(); - } - if (sync === undefined) { - sync = true; - } - if (sync) { - return process.nextTick(function () { - fn(arr[index++], next); - }); - } - fn(arr[index++], next); +function toEnum(typeName, keys) { + var result = {}; + keys.split(' ').forEach(function (k) { + var jsKey = k; + if (jsKey === jsKey.toUpperCase()) { + jsKey = jsKey.toLowerCase(); } - } + result[jsKey] = new EnumValue(typeName, k); + }); + return result; +} - function inherits(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - } +function EnumValue(typeName, elementName) { + this.typeName = typeName; + this.elementName = elementName; +} - var toExport = { - Bytecode: Bytecode, - EnumValue: EnumValue, - inherits: inherits, - P: P, - parseArgs: parseArgs, - Traversal: Traversal, - TraversalSideEffects: TraversalSideEffects, - TraversalStrategies: TraversalStrategies, - TraversalStrategy: TraversalStrategy, - Traverser: Traverser""") - for (final Class<? extends Enum> enumClass : CoreImports.getClassImports() - .findAll { Enum.class.isAssignableFrom(it) } - .sort { a, b -> a.getSimpleName() <=> b.getSimpleName() } - .collect()) { - moduleOutput.append(",\n ${SymbolHelper.decapitalize(enumClass.getSimpleName())}: " + - "toEnum('${SymbolHelper.toJs(enumClass.getSimpleName())}', '"); - enumClass.getEnumConstants() - .sort { a, b -> a.name() <=> b.name() } - .each { value -> moduleOutput.append("${SymbolHelper.toJs(value.name())} "); } - moduleOutput.deleteCharAt(moduleOutput.length() - 1).append("')") +module.exports = { + EnumValue: EnumValue, + P: P, + Traversal: Traversal, + TraversalSideEffects: TraversalSideEffects, + Traverser: Traverser""") + for (final Class<? extends Enum> enumClass : CoreImports.getClassImports(). + findAll { Enum.class.isAssignableFrom(it) }. + sort { a, b -> a.simpleName <=> b.simpleName }) { + moduleOutput.append(",\n ${SymbolHelper.decapitalize(enumClass.simpleName)}: " + + "toEnum('${SymbolHelper.toJs(enumClass.simpleName)}', '"); + moduleOutput.append( + enumClass.getEnumConstants(). + sort { a, b -> a.name() <=> b.name() }. + collect { SymbolHelper.toJs(it.name()) }. + join(' ')); + moduleOutput.append("\')"); } - moduleOutput.append(""" - }; - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; - } - // Nashorn and rest - return toExport; -}).call(this);""") + moduleOutput.append("""\n};"""); // save to a file final File file = new File(traversalSourceFile); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java b/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java deleted file mode 100644 index 1656db4..0000000 --- a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/GenerateGremlinJavascript.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.javascript; - -public class GenerateGremlinJavascript { - - private GenerateGremlinJavascript() { - // just need the main method - } - - public static void main(String[] args) { - TraversalSourceGenerator.create(args[0]); - GraphTraversalSourceGenerator.create(args[1]); - } -} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java b/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java deleted file mode 100644 index 535de44..0000000 --- a/gremlin-javascript/src/main/java/org/apache/tinkerpop/gremlin/javascript/jsr223/SymbolHelper.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.javascript.jsr223; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Jorge Bay Gondra - */ -public final class SymbolHelper { - - private final static Map<String, String> TO_JS_MAP = new HashMap<>(); - private final static Map<String, String> FROM_JS_MAP = new HashMap<>(); - - static { - TO_JS_MAP.put("in", "in_"); - TO_JS_MAP.put("from", "from_"); - TO_JS_MAP.forEach((k, v) -> FROM_JS_MAP.put(v, k)); - } - - private SymbolHelper() { - // static methods only, do not instantiate - } - - public static String toJs(final String symbol) { - return TO_JS_MAP.getOrDefault(symbol, symbol); - } - - public static String toJava(final String symbol) { - return FROM_JS_MAP.getOrDefault(symbol, symbol); - } - - public static String decapitalize(String string) { - if (string == null || string.length() == 0) { - return string; - } - char c[] = string.toCharArray(); - c[0] = Character.toLowerCase(c[0]); - return new String(c); - } -} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore b/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore new file mode 100644 index 0000000..4c7120c --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +.idea/ +npm-debug.log +# maven plugin com.github.eirslett frontend-maven-plugin installs node.js runtime in "node" folder +node/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore b/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore new file mode 100644 index 0000000..82943d0 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/.npmignore @@ -0,0 +1,26 @@ +# 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. + +*.gz +*.tgz +test/ +.DS_Store +.idea +node_modules/ +npm-debug.log +# maven plugin com.github.eirslett frontend-maven-plugin installs node.js runtime in "node" folder +node/ \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md b/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md new file mode 100644 index 0000000..388e175 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/README.md @@ -0,0 +1,39 @@ +<!-- + + 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. + +--> + +# JavaScript Gremlin Language Variant + +[Apache TinkerPopâ¢][tk] is a graph computing framework for both graph databases (OLTP) and graph analytic systems +(OLAP). [Gremlin][gremlin] is the graph traversal language of TinkerPop. It can be described as a functional, +data-flow language that enables users to succinctly express complex traversals on (or queries of) their application's +property graph. + +Gremlin-Javascript implements Gremlin within the JavaScript language and can be used on Node.js. + +```bash +npm install gremlin-javascript +``` + +Please see the [reference documentation][docs] at Apache TinkerPop for more information. + +[tk]: http://tinkerpop.apache.org +[gremlin]: http://tinkerpop.apache.org/gremlin.html +[docs]: http://tinkerpop.apache.org/docs/current/reference/#gremlin-javascript \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js deleted file mode 100644 index e19b537..0000000 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/driver/remote-connection.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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. - */ - -/** - * @author Jorge Bay Gondra - */ -(function defineRemoteConnectionModule() { - "use strict"; - - var t = loadModule.call(this, '../process/traversal.js'); - var inherits = t.inherits; - - function RemoteConnection(url, traversalSource) { - this.url = url; - this.traversalSource = traversalSource; - } - - /** - * @abstract - * @param {Bytecode} bytecode - * @param {Function} callback - */ - RemoteConnection.prototype.submit = function (bytecode, callback) { - throw new Error('submit() needs to be implemented'); - }; - - /** - * @extends {Traversal} - * @constructor - */ - function RemoteTraversal(traversers, sideEffects) { - t.Traversal.call(this, null, null, null); - this.traversers = traversers; - this.sideEffects = sideEffects; - } - - inherits(RemoteTraversal, t.Traversal); - - /** - * - * @param {RemoteConnection} connection - * @extends {TraversalStrategy} - * @constructor - */ - function RemoteStrategy(connection) { - t.TraversalStrategy.call(this); - this.connection = connection; - } - - inherits(RemoteStrategy, t.TraversalStrategy); - - /** @override */ - RemoteStrategy.prototype.apply = function (traversal, callback) { - if (traversal.traversers) { - return callback(); - } - this.connection.submit(traversal.getBytecode(), function (err, remoteTraversal) { - if (err) { - return callback(err); - } - traversal.sideEffects = remoteTraversal.sideEffects; - traversal.traversers = remoteTraversal.traversers; - callback(); - }); - }; - - function loadModule(moduleName) { - if (typeof require !== 'undefined') { - return require(moduleName); - } - if (typeof load !== 'undefined') { - var path = new java.io.File(__DIR__ + moduleName).getCanonicalPath(); - this.__dependencies = this.__dependencies || {}; - return this.__dependencies[path] = (this.__dependencies[path] || load(path)); - } - throw new Error('No module loader was found'); - } - - var toExport = { - RemoteConnection: RemoteConnection, - RemoteStrategy: RemoteStrategy, - RemoteTraversal: RemoteTraversal - }; - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; - } - // Nashorn and rest - return toExport; -}).call(this); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js index ddcdac2..600ac53 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/index.js @@ -20,73 +20,54 @@ /** * @author Jorge Bay Gondra */ -(function exportModule() { - "use strict"; +'use strict'; - function loadModule(moduleName) { - if (typeof require !== 'undefined') { - return require(moduleName); - } - if (typeof load !== 'undefined') { - var path = new java.io.File(__DIR__ + moduleName).getCanonicalPath(); - this.__dependencies = this.__dependencies || {}; - return this.__dependencies[path] = (this.__dependencies[path] || load(path)); - } - throw new Error('No module loader was found'); - } +var t = require('./lib/process/traversal'); +var gt = require('./lib/process/graph-traversal'); +var strategiesModule = require('./lib/process/traversal-strategy'); +var graph = require('./lib/structure/graph'); +var gs = require('./lib/structure/io/graph-serializer'); +var rc = require('./lib/driver/remote-connection'); +var Bytecode = require('./lib/process/bytecode'); - var t = loadModule.call(this, './process/traversal.js'); - var gt = loadModule.call(this, './process/graph-traversal.js'); - var graph = loadModule.call(this, './structure/graph.js'); - var gs = loadModule.call(this, './structure/io/graph-serializer.js'); - var rc = loadModule.call(this, './driver/remote-connection.js'); - var toExport = { - driver: { - RemoteConnection: rc.RemoteConnection, - RemoteStrategy: rc.RemoteStrategy, - RemoteTraversal: rc.RemoteTraversal - }, - process: { - Bytecode: t.Bytecode, - EnumValue: t.EnumValue, - P: t.P, - Traversal: t.Traversal, - TraversalSideEffects: t.TraversalSideEffects, - TraversalStrategies: t.TraversalStrategies, - TraversalStrategy: t.TraversalStrategy, - Traverser: t.Traverser, - barrier: t.barrier, - cardinality: t.cardinality, - column: t.column, - direction: t.direction, - operator: t.operator, - order: t.order, - pop: t.pop, - scope: t.scope, - t: t.t, - GraphTraversal: gt.GraphTraversal, - GraphTraversalSource: gt.GraphTraversalSource, - statics: gt.statics +module.exports = { + driver: { + RemoteConnection: rc.RemoteConnection, + RemoteStrategy: rc.RemoteStrategy, + RemoteTraversal: rc.RemoteTraversal + }, + process: { + Bytecode: Bytecode, + EnumValue: t.EnumValue, + P: t.P, + Traversal: t.Traversal, + TraversalSideEffects: t.TraversalSideEffects, + TraversalStrategies: strategiesModule.TraversalStrategies, + TraversalStrategy: strategiesModule.TraversalStrategy, + Traverser: t.Traverser, + barrier: t.barrier, + cardinality: t.cardinality, + column: t.column, + direction: t.direction, + operator: t.operator, + order: t.order, + pop: t.pop, + scope: t.scope, + t: t.t, + GraphTraversal: gt.GraphTraversal, + GraphTraversalSource: gt.GraphTraversalSource, + statics: gt.statics + }, + structure: { + io: { + GraphSONReader: gs.GraphSONReader, + GraphSONWriter: gs.GraphSONWriter }, - structure: { - io: { - GraphSONReader: gs.GraphSONReader, - GraphSONWriter: gs.GraphSONWriter - }, - Edge: graph.Edge, - Graph: graph.Graph, - Path: graph.Path, - Property: graph.Property, - Vertex: graph.Vertex, - VertexProperty: graph.VertexProperty - } - }; - - - if (typeof module !== 'undefined') { - // CommonJS - module.exports = toExport; - return; + Edge: graph.Edge, + Graph: graph.Graph, + Path: graph.Path, + Property: graph.Property, + Vertex: graph.Vertex, + VertexProperty: graph.VertexProperty } - return toExport; -}).call(this); \ No newline at end of file +}; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js new file mode 100644 index 0000000..0d6d507 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/driver-remote-connection.js @@ -0,0 +1,200 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +var crypto = require('crypto'); +var WebSocket = require('ws'); +var util = require('util'); +var RemoteConnection = require('./remote-connection').RemoteConnection; +var utils = require('../utils'); +var serializer = require('../structure/io/graph-serializer'); +var inherits = utils.inherits; +var mimeType = 'application/vnd.gremlin-v2.0+json'; +var header = String.fromCharCode(mimeType.length) + mimeType; +var responseStatusCode = { + success: 200, + noContent: 204, + partialContent: 206 +}; + +/** + * Creates a new instance of DriverRemoteConnection. + * @param {String} url The resource uri. + * @param {Object} [options] The connection options. + * @param {Array} [options.ca] Trusted certificates. + * @param {String|Array|Buffer} [options.cert] The certificate key. + * @param {String|Buffer} [options.pfx] The private key, certificate, and CA certs. + * @param {GraphSONReader} [options.reader] The GraphSON2 reader to use. + * @param {Boolean} [options.rejectUnauthorized] Determines whether to verify or not the server certificate. + * @param {GraphSONWriter} [options.writer] The GraphSON2 writer to use. + * @constructor + */ +function DriverRemoteConnection(url, options) { + options = options || {}; + this._ws = new WebSocket(url, { + ca: options.ca, + cert: options.cert, + pfx: options.pfx, + rejectUnauthorized: options.rejectUnauthorized + }); + var self = this; + this._ws.on('open', function opened () { + self.isOpen = true; + if (self._openCallback) { + self._openCallback(); + } + }); + this._ws.on('message', function incoming (data) { + self._handleMessage(data); + }); + // A map containing the request id and the handler + this._responseHandlers = {}; + this._reader = options.reader || new serializer.GraphSONReader(); + this._writer = options.writer || new serializer.GraphSONWriter(); + this._openPromise = null; + this._openCallback = null; + this._closePromise = null; + this.isOpen = false; +} + +inherits(DriverRemoteConnection, RemoteConnection); + +/** + * Opens the connection, if its not already opened. + * @returns {Promise} + */ +DriverRemoteConnection.prototype.open = function (promiseFactory) { + if (this._closePromise) { + return this._openPromise = utils.toPromise(promiseFactory, function promiseHandler(callback) { + callback(new Error('Connection has been closed')); + }); + } + if (this._openPromise) { + return this._openPromise; + } + var self = this; + return this._openPromise = utils.toPromise(promiseFactory, function promiseHandler(callback) { + if (self.isOpen) { + return callback(); + } + // It will be invoked when opened + self._openCallback = callback; + }); +}; + +/** @override */ +DriverRemoteConnection.prototype.submit = function (bytecode, promiseFactory) { + var self = this; + return this.open().then(function () { + return utils.toPromise(promiseFactory, function promiseHandler(callback) { + var requestId = getUuid(); + self._responseHandlers[requestId] = { + callback: callback, + result: null + }; + var message = bufferFromString(header + JSON.stringify(self._getRequest(requestId, bytecode))); + self._ws.send(message); + }); + }); +}; + +DriverRemoteConnection.prototype._getRequest = function (id, bytecode) { + return ({ + 'requestId': { '@type': 'g:UUID', '@value': id }, + 'op': 'bytecode', + 'processor': 'traversal', + 'args': { + 'gremlin': this._writer.adaptObject(bytecode), + 'aliases': { 'g': 'g'} + } + }); +}; + +DriverRemoteConnection.prototype._handleMessage = function (data) { + var response = this._reader.read(JSON.parse(data.toString())); + var handler = this._responseHandlers[response.requestId]; + if (response.status.code >= 400) { + // callback in error + return handler.callback( + new Error(util.format('Server error: %s (%d)', response.status.message, response.status.code))); + } + switch (response.status.code) { + case responseStatusCode.noContent: + return handler.callback(null, { traversers: []}); + case responseStatusCode.partialContent: + handler.result = handler.result || []; + handler.result.push.apply(handler.result, response.result.data); + break; + default: + if (handler.result) { + handler.result.push.apply(handler.result, response.result.data); + } + else { + handler.result = response.result.data; + } + return handler.callback(null, { traversers: handler.result }); + } +}; + +/** + * Closes the Connection. + * @return {Promise} + */ +DriverRemoteConnection.prototype.close = function (promiseFactory) { + if (this._closePromise) { + return this._closePromise; + } + var self = this; + return this._closePromise = utils.toPromise(promiseFactory, function promiseHandler(callback) { + self._ws.on('close', function () { + self.isOpen = false; + callback(); + }); + self._ws.close(); + }); +}; + +function getUuid() { + var buffer = crypto.randomBytes(16); + //clear the version + buffer[6] &= 0x0f; + //set the version 4 + buffer[6] |= 0x40; + //clear the variant + buffer[8] &= 0x3f; + //set the IETF variant + buffer[8] |= 0x80; + var hex = buffer.toString('hex'); + return ( + hex.substr(0, 8) + '-' + + hex.substr(8, 4) + '-' + + hex.substr(12, 4) + '-' + + hex.substr(16, 4) + '-' + + hex.substr(20, 12)); +} + +var bufferFromString = Buffer.from || function newBuffer(text) { + return new Buffer(text, 'utf8'); +}; + +module.exports = DriverRemoteConnection; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js new file mode 100644 index 0000000..8176c37 --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/driver/remote-connection.js @@ -0,0 +1,84 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +var t = require('../process/traversal'); +var TraversalStrategy = require('../process/traversal-strategy').TraversalStrategy; +var utils = require('../utils'); +var inherits = utils.inherits; + +function RemoteConnection(url, traversalSource) { + this.url = url; +} + +/** + * @abstract + * @param {Bytecode} bytecode + * @param {Function|undefined} promiseFactory + * @returns {Promise} + */ +RemoteConnection.prototype.submit = function (bytecode, promiseFactory) { + throw new Error('submit() was not implemented'); +}; + +/** + * @extends {Traversal} + * @constructor + */ +function RemoteTraversal(traversers, sideEffects) { + t.Traversal.call(this, null, null, null); + this.traversers = traversers; + this.sideEffects = sideEffects; +} + +inherits(RemoteTraversal, t.Traversal); + +/** + * + * @param {RemoteConnection} connection + * @extends {TraversalStrategy} + * @constructor + */ +function RemoteStrategy(connection) { + TraversalStrategy.call(this); + this.connection = connection; +} + +inherits(RemoteStrategy, TraversalStrategy); + +/** @override */ +RemoteStrategy.prototype.apply = function (traversal, promiseFactory) { + if (traversal.traversers) { + return utils.resolvedPromise(promiseFactory); + } + return this.connection.submit(traversal.getBytecode(), promiseFactory).then(function (remoteTraversal) { + traversal.sideEffects = remoteTraversal.sideEffects; + traversal.traversers = remoteTraversal.traversers; + }); +}; + +module.exports = { + RemoteConnection: RemoteConnection, + RemoteStrategy: RemoteStrategy, + RemoteTraversal: RemoteTraversal +}; http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/baeabd7c/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js ---------------------------------------------------------------------- diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js new file mode 100644 index 0000000..9256e6a --- /dev/null +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/bytecode.js @@ -0,0 +1,99 @@ +/* + * 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. + */ + +/** + * @author Jorge Bay Gondra + */ +'use strict'; + +/** + * Creates a new instance of Bytecode + * @param {Bytecode} [toClone] + * @constructor + */ +function Bytecode(toClone) { + if (!toClone) { + this.sourceInstructions = []; + this.stepInstructions = []; + } + else { + this.sourceInstructions = toClone.sourceInstructions.slice(0); + this.stepInstructions = toClone.sourceInstructions.slice(0); + } +} + +/** + * Adds a new source instructions + * @param {String} name + * @param {Array} values + * @returns {Bytecode} + */ +Bytecode.prototype.addSource = function (name, values) { + if (name === undefined) { + throw new Error('Name is not defined'); + } + var instruction = new Array(values.length + 1); + instruction[0] = name; + for (var i = 0; i < values.length; ++i) { + instruction[i + 1] = this._convertToArgument(values[i]); + } + this.sourceInstructions.push(this._generateInstruction(name, values)); + return this; +}; + +/** + * Adds a new step instructions + * @param {String} name + * @param {Array} values + * @returns {Bytecode} + */ +Bytecode.prototype.addStep = function (name, values) { + if (name === undefined) { + throw new Error('Name is not defined'); + } + this.stepInstructions.push(this._generateInstruction(name, values)); + return this; +}; + +Bytecode.prototype._generateInstruction = function (name, values) { + var length = (values ? values.length : 0) + 1; + var instruction = new Array(length); + instruction[0] = name; + for (var i = 1; i < length; i++) { + instruction[i] = this._convertToArgument(values[i - 1]); + } + return instruction; +}; + +/** + * Returns the JSON representation of the source and step instructions + * @returns {String} + */ +Bytecode.prototype.toString = function () { + return ( + (this.sourceInstructions.length > 0 ? JSON.stringify(this.sourceInstructions) : '') + + (this.stepInstructions.length > 0 ? JSON.stringify(this.stepInstructions) : '') + ); +}; + +Bytecode.prototype._convertToArgument = function (value) { + return value; +}; + +module.exports = Bytecode; \ No newline at end of file
