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 2023/01/04 20:39:42 UTC

[tinkerpop] 02/03: Merge branch '3.5-dev' into 3.6-dev

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 2e57d05d603f5e08a055bc82a45cc26e7b983f74
Merge: 69fe9694be 0adaa5ff81
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Wed Jan 4 15:39:13 2023 -0500

    Merge branch '3.5-dev' into 3.6-dev

 CHANGELOG.asciidoc                                 |   1 +
 .../gremlin/jsr223/GremlinScriptChecker.java       | 226 ++++++++++++---------
 .../gremlin/jsr223/GremlinScriptCheckerTest.java   | 113 ++++++++++-
 3 files changed, 242 insertions(+), 98 deletions(-)

diff --cc gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinScriptChecker.java
index 4952fb3bd6,0000000000..3cea1db7a6
mode 100644,000000..100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinScriptChecker.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinScriptChecker.java
@@@ -1,203 -1,0 +1,239 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + * http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.tinkerpop.gremlin.jsr223;
 +
 +import java.util.Arrays;
- import java.util.List;
++import java.util.HashSet;
 +import java.util.Optional;
++import java.util.Set;
 +import java.util.regex.Matcher;
 +import java.util.regex.Pattern;
 +
 +/**
-  * Processes Gremlin strings using regex so as to try to detect certain properties from the script without actual
++ * Processes Gremlin strings using regex to try to detect certain properties from the script without actual
 + * having to execute a {@code eval()} on it.
 + */
 +public class GremlinScriptChecker {
 +
-     public static final Result EMPTY_RESULT = new Result(0);
-     private static final List<String> tokens = Arrays.asList("evaluationTimeout", "scriptEvaluationTimeout",
-                                                              "ARGS_EVAL_TIMEOUT", "ARGS_SCRIPT_EVAL_TIMEOUT");
++    /**
++     * An empty result whose properties return as empty.
++     */
++    public static final Result EMPTY_RESULT = new Result(null, null);
 +
++    /**
++     * At least one of these tokens should be present somewhere in the Gremlin string for {@link #parse(String)} to
++     * take any action at all.
++     */
++    private static final Set<String> tokens = new HashSet<>(Arrays.asList("evaluationTimeout", "scriptEvaluationTimeout",
++                                                                          "ARGS_EVAL_TIMEOUT", "ARGS_SCRIPT_EVAL_TIMEOUT",
++                                                                          "requestId", "REQUEST_ID"));
 +    /**
 +     * Matches single line comments, multi-line comments and space characters.
 +     * <pre>
++     * From https://regex101.com/
++     *
 +     * 	OR: match either of the followings
 +     * Sequence: match all of the followings in order
 +     * / /
 +     * Repeat
 +     * AnyCharacterExcept\n
 +     * zero or more times
 +     * EndOfLine
 +     * Sequence: match all of the followings in order
 +     * /
 +     * *
 +     * Repeat
 +     * CapturingGroup
 +     * GroupNumber:1
 +     * OR: match either of the followings
 +     * AnyCharacterExcept\n
 +     * AnyCharIn[ CarriageReturn NewLine]
 +     * zero or more times (ungreedy)
 +     * *
 +     * /
 +     * WhiteSpaceCharacter
 +     * </pre>
 +     */
 +    private static final Pattern patternClean = Pattern.compile("//.*$|/\\*(.|[\\r\\n])*?\\*/|\\s", Pattern.MULTILINE);
 +
 +    /**
 +     * Regex fragment for the timeout tokens to look for. There are basically four:
 +     * <ul>
 +     *     <li>{@code evaluationTimeout} which is a string value and thus single or double quoted</li>
 +     *     <li>{@code scriptEvaluationTimeout} which is a string value and thus single or double quoted</li>
 +     *     <li>{@code ARGS_EVAL_TIMEOUT} which is a enum type of value which can be referenced with or without a {@code Tokens} qualifier</li>
 +     *     <li>{@code ARGS_SCRIPT_EVAL_TIMEOUT} which is a enum type of value which can be referenced with or without a {@code Tokens} qualifier</li>
 +     * </ul>
-      * <pre>
-      * 	OR: match either of the followings
-      * Sequence: match all of the followings in order
-      * AnyCharIn[ " ']
-      * e v a l u a t i o n T i m e o u t
-      * AnyCharIn[ " ']
-      * Sequence: match all of the followings in order
-      * AnyCharIn[ " ']
-      * s c r i p t E v a l u a t i o n T i m e o u t
-      * AnyCharIn[ " ']
-      * Sequence: match all of the followings in order
-      * Repeat
-      * CapturingGroup
-      * (NonCapturingGroup)
-      * Sequence: match all of the followings in order
-      * T o k e n s
-      * .
-      * optional
-      * A R G S _ E V A L _ T I M E O U T
-      * Sequence: match all of the followings in order
-      * Repeat
-      * CapturingGroup
-      * (NonCapturingGroup)
-      * Sequence: match all of the followings in order
-      * T o k e n s
-      * .
-      * optional
-      * A R G S _ S C R I P T _ E V A L _ T I M E O U T
-      * </pre>
++     * See {@link #patternWithOptions} for explain as this regex is embedded in there.
 +     */
 +    private static final String timeoutTokens = "[\"']evaluationTimeout[\"']|[\"']scriptEvaluationTimeout[\"']|(?:Tokens\\.)?ARGS_EVAL_TIMEOUT|(?:Tokens\\.)?ARGS_SCRIPT_EVAL_TIMEOUT";
 +
++    /**
++     * Regex fragment for the timeout tokens to look for. There are basically four:
++     * <ul>
++     *     <li>{@code requestId} which is a string value and thus single or double quoted</li>
++     *     <li>{@code REQUEST_ID} which is a enum type of value which can be referenced with or without a {@code Tokens} qualifier</li>
++     * </ul>
++     * See {@link #patternWithOptions} for a full explain as this regex is embedded in there.
++     */
++    private static final String requestIdTokens = "[\"']requestId[\"']|(?:Tokens\\.)?REQUEST_ID";
++
 +    /**
 +     * Matches {@code .with({timeout-token},{timeout})} with a matching group on the {@code timeout}.
 +     *
 +     * <pre>
-      * Sequence: match all of the followings in order
-      * .
-      * w i t h
-      * (
-      * CapturingGroup
-      * (NonCapturingGroup)
-      * OR: match either of the followings
-      * Sequence: match all of the followings in order
-      * AnyCharIn[ " ']
-      * e v a l u a t i o n T i m e o u t
-      * AnyCharIn[ " ']
-      * Sequence: match all of the followings in order
-      * AnyCharIn[ " ']
-      * s c r i p t E v a l u a t i o n T i m e o u t
-      * AnyCharIn[ " ']
-      * Sequence: match all of the followings in order
-      * Repeat
-      * CapturingGroup
-      * (NonCapturingGroup)
-      * Sequence: match all of the followings in order
-      * T o k e n s
-      * .
-      * optional
-      * A R G S _ E V A L _ T I M E O U T
-      * Sequence: match all of the followings in order
-      * Repeat
-      * CapturingGroup
-      * (NonCapturingGroup)
-      * Sequence: match all of the followings in order
-      * T o k e n s
-      * .
-      * optional
-      * A R G S _ S C R I P T _ E V A L _ T I M E O U T
-      * ,
-      * CapturingGroup
-      * GroupNumber:1
-      * Repeat
-      * Digit
-      * zero or more times
-      * Repeat
-      * CapturingGroup
-      * GroupNumber:2
-      * OR: match either of the followings
-      * Sequence: match all of the followings in order
-      * Repeat
-      * :
-      * optional
-      * L
-      * l
-      * optional
-      * )
++     * From https://regex101.com/
++     *
++     * \.with\((?:(?:["']evaluationTimeout["']|["']scriptEvaluationTimeout["']|(?:Tokens\.)?ARGS_EVAL_TIMEOUT|(?:Tokens\.)?ARGS_SCRIPT_EVAL_TIMEOUT),(?<to>\d*)(:?L|l)?)|(?:(?:["']requestId["']|(?:Tokens\.)?REQUEST_ID),["'](?<rid>.*?))["']\)
++     *
++     * gm
++     * 1st Alternative \.with\((?:(?:["']evaluationTimeout["']|["']scriptEvaluationTimeout["']|(?:Tokens\.)?ARGS_EVAL_TIMEOUT|(?:Tokens\.)?ARGS_SCRIPT_EVAL_TIMEOUT),(?<to>\d*)(:?L|l)?)
++     * \. matches the character . with index 4610 (2E16 or 568) literally (case sensitive)
++     * with matches the characters with literally (case sensitive)
++     * \( matches the character ( with index 4010 (2816 or 508) literally (case sensitive)
++     * Non-capturing group (?:(?:["']evaluationTimeout["']|["']scriptEvaluationTimeout["']|(?:Tokens\.)?ARGS_EVAL_TIMEOUT|(?:Tokens\.)?ARGS_SCRIPT_EVAL_TIMEOUT),(?<to>\d*)(:?L|l)?)
++     * Non-capturing group (?:["']evaluationTimeout["']|["']scriptEvaluationTimeout["']|(?:Tokens\.)?ARGS_EVAL_TIMEOUT|(?:Tokens\.)?ARGS_SCRIPT_EVAL_TIMEOUT)
++     * 1st Alternative ["']evaluationTimeout["']
++     * Match a single character present in the list below ["']
++     * "' matches a single character in the list "' (case sensitive)
++     * evaluationTimeout matches the characters evaluationTimeout literally (case sensitive)
++     * Match a single character present in the list below ["']
++     * "' matches a single character in the list "' (case sensitive)
++     * 2nd Alternative ["']scriptEvaluationTimeout["']
++     * Match a single character present in the list below ["']
++     * "' matches a single character in the list "' (case sensitive)
++     * scriptEvaluationTimeout matches the characters scriptEvaluationTimeout literally (case sensitive)
++     * Match a single character present in the list below ["']
++     * "' matches a single character in the list "' (case sensitive)
++     * 3rd Alternative (?:Tokens\.)?ARGS_EVAL_TIMEOUT
++     * Non-capturing group (?:Tokens\.)?
++     * ? matches the previous token between zero and one times, as many times as possible, giving back as needed (greedy)
++     * Tokens matches the characters Tokens literally (case sensitive)
++     * \. matches the character . with index 4610 (2E16 or 568) literally (case sensitive)
++     * ARGS_EVAL_TIMEOUT matches the characters ARGS_EVAL_TIMEOUT literally (case sensitive)
++     * 4th Alternative (?:Tokens\.)?ARGS_SCRIPT_EVAL_TIMEOUT
++     * Non-capturing group (?:Tokens\.)?
++     * ? matches the previous token between zero and one times, as many times as possible, giving back as needed (greedy)
++     * Tokens matches the characters Tokens literally (case sensitive)
++     * \. matches the character . with index 4610 (2E16 or 568) literally (case sensitive)
++     * ARGS_SCRIPT_EVAL_TIMEOUT matches the characters ARGS_SCRIPT_EVAL_TIMEOUT literally (case sensitive)
++     * , matches the character , with index 4410 (2C16 or 548) literally (case sensitive)
++     * Named Capture Group to (?<to>\d*)
++     * \d matches a digit (equivalent to [0-9])
++     * * matches the previous token between zero and unlimited times, as many times as possible, giving back as needed (greedy)
++     * 2nd Capturing Group (:?L|l)?
++     * ? matches the previous token between zero and one times, as many times as possible, giving back as needed (greedy)
++     * 1st Alternative :?L
++     * : matches the character : with index 5810 (3A16 or 728) literally (case sensitive)
++     * ? matches the previous token between zero and one times, as many times as possible, giving back as needed (greedy)
++     * L matches the character L with index 7610 (4C16 or 1148) literally (case sensitive)
++     * 2nd Alternative l
++     * l matches the character l with index 10810 (6C16 or 1548) literally (case sensitive)
++     * 2nd Alternative (?:(?:["']requestId["']|(?:Tokens\.)?REQUEST_ID),["'](?<rid>.*?))["']\)
++     * Non-capturing group (?:(?:["']requestId["']|(?:Tokens\.)?REQUEST_ID),["'](?<rid>.*?))
++     * Non-capturing group (?:["']requestId["']|(?:Tokens\.)?REQUEST_ID)
++     * 1st Alternative ["']requestId["']
++     * 2nd Alternative (?:Tokens\.)?REQUEST_ID
++     * , matches the character , with index 4410 (2C16 or 548) literally (case sensitive)
++     * Match a single character present in the list below ["']
++     * "' matches a single character in the list "' (case sensitive)
++     * Named Capture Group rid (?<rid>.*?)
++     * . matches any character (except for line terminators)
++     * *? matches the previous token between zero and unlimited times, as few times as possible, expanding as needed (lazy)
++     * Match a single character present in the list below ["']
++     * "' matches a single character in the list "' (case sensitive)
++     * \) matches the character ) with index 4110 (2916 or 518) literally (case sensitive)
 +     * </pre>
 +     */
-     private static final Pattern patternTimeout = Pattern.compile("\\.with\\((?:" + timeoutTokens + "),(\\d*)(:?L|l)?\\)");
++    private static final Pattern patternWithOptions =
++            Pattern.compile("\\.with\\(((?:" + timeoutTokens + "),(?<to>\\d*)(:?L|l)?)|((?:" + requestIdTokens + "),[\"'](?<rid>.*?))[\"']\\)");
 +
 +    /**
 +     * Parses a Gremlin script and extracts a {@code Result} containing properties that are relevant to the checker.
 +     */
 +    public static Result parse(final String gremlin) {
 +        if (gremlin.isEmpty()) return EMPTY_RESULT;
 +
 +        // do a cheap check for tokens we care about - no need to parse unless one of these tokens is present in
 +        // the string.
 +        if (tokens.stream().noneMatch(gremlin::contains)) return EMPTY_RESULT;
 +
 +        // kill out comments/whitespace. for whitespace, ignoring the need to keep string literals together as that
 +        // isn't currently a requirement
 +        final String cleanGremlin = patternClean.matcher(gremlin).replaceAll("");
 +
-         final Matcher m = patternTimeout.matcher(cleanGremlin);
++        final Matcher m = patternWithOptions.matcher(cleanGremlin);
 +        if (!m.find()) return EMPTY_RESULT;
 +
-         long l = Long.parseLong(m.group(1));
-         while (m.find()) {
-             l += Long.parseLong(m.group(1));
-         }
++        // arguments given to Result class as null mean they weren't assigned (or the parser didn't find them somehow - eek!)
++        Long timeout = null;
++        String requestId = null;
++        do {
++            // timeout is added up across all scripts
++            final String to = m.group("to");
++            if (to != null) {
++                if (null == timeout) timeout = 0L;
++                timeout += Long.parseLong(to);
++            }
++
++            // request id just uses the last one found
++            final String rid = m.group("rid");
++            if (rid != null) requestId = rid;
++        } while (m.find());
 +
-         return new Result(l);
++        return new Result(timeout, requestId);
 +    }
 +
++    /**
++     * A result returned from a {@link #parse(String)} of a Gremlin string.
++     */
 +    public static class Result {
-         private final long timeout;
++        private final Long timeout;
++        private final String requestId;
 +
-         private Result(final long timeout) {
++        private Result(final Long timeout, final String requestId) {
 +            this.timeout = timeout;
++            this.requestId = requestId;
 +        }
 +
 +        /**
 +         * Gets the value of the timeouts that were set using the {@code with()} source step. If there are multiple
 +         * commands using this step, the timeouts are summed together.
 +         */
 +        public final Optional<Long> getTimeout() {
-             return timeout == 0 ? Optional.empty() : Optional.of(timeout);
++            return null == timeout ? Optional.empty() : Optional.of(timeout);
++        }
++
++        /**
++         * Gets the value of the request identifier supplied using the {@code with()} source step. If there are
++         * multiple commands using this step, the last usage should represent the id returned here.
++         */
++        public Optional<String> getRequestId() {
++            return null == requestId ? Optional.empty() : Optional.of(requestId);
++        }
++
++        @Override
++        public String toString() {
++            return "GremlinScriptChecker.Result{" +
++                    "timeout=" + timeout +
++                    ", requestId='" + requestId + '\'' +
++                    '}';
 +        }
 +    }
 +}
diff --cc gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinScriptCheckerTest.java
index 35662ccf13,0000000000..620ea0e73a
mode 100644,000000..100644
--- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinScriptCheckerTest.java
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinScriptCheckerTest.java
@@@ -1,300 -1,0 +1,407 @@@
 +/*
 + * Licensed to the Apache Software Foundation (ASF) under one
 + * or more contributor license agreements.  See the NOTICE file
 + * distributed with this work for additional information
 + * regarding copyright ownership.  The ASF licenses this file
 + * to you under the Apache License, Version 2.0 (the
 + * "License"); you may not use this file except in compliance
 + * with the License.  You may obtain a copy of the License at
 + *
 + * http://www.apache.org/licenses/LICENSE-2.0
 + *
 + * Unless required by applicable law or agreed to in writing,
 + * software distributed under the License is distributed on an
 + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 + * KIND, either express or implied.  See the License for the
 + * specific language governing permissions and limitations
 + * under the License.
 + */
 +package org.apache.tinkerpop.gremlin.jsr223;
 +
 +import org.junit.Test;
 +
 +import java.util.Optional;
 +
 +import static org.apache.tinkerpop.gremlin.jsr223.GremlinScriptChecker.EMPTY_RESULT;
++import static org.hamcrest.MatcherAssert.assertThat;
++import static org.hamcrest.core.Is.is;
 +import static org.junit.Assert.assertEquals;
 +import static org.junit.Assert.assertSame;
 +
 +public class GremlinScriptCheckerTest {
 +
 +    @Test
-     public void shouldNotFindTimeout() {
-         assertEquals(Optional.empty(), GremlinScriptChecker.parse("g.with(true).V().out('knows')").getTimeout());
++    public void shouldNotFindAResult() {
++        final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("g.with(true).V().out('knows')");
++        assertEquals(Optional.empty(), r.getTimeout());
++        assertEquals(Optional.empty(), r.getRequestId());
 +    }
 +
 +    @Test
 +    public void shouldReturnEmpty() {
-         assertSame(EMPTY_RESULT, GremlinScriptChecker.parse(""));
++        final GremlinScriptChecker.Result r = GremlinScriptChecker.parse("");
++        assertSame(EMPTY_RESULT, r);
++        assertEquals(Optional.empty(), r.getTimeout());
++        assertEquals(Optional.empty(), r.getRequestId());
 +    }
 +
 +    @Test
 +    public void shouldNotFindTimeoutCozWeCommentedItOut() {
 +        assertEquals(Optional.empty(), GremlinScriptChecker.parse("g.\n" +
 +                "                                                  // with('evaluationTimeout', 1000L).\n" +
 +                "                                                  with(true).V().out('knows')").getTimeout());
 +    }
 +
++    @Test
++    public void shouldIdentifyTimeoutWithOddSpacing() {
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('evaluationTimeout' , 1000L).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('scriptEvaluationTimeout'   ,  1000L).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('evaluationTimeout',1000L).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++    }
++
++    @Test
++    public void shouldIdentifyRequestIdWithOddSpacing() {
++        assertEquals("4F53FB59-CFC9-4984-B477-452073A352FD", GremlinScriptChecker.parse("g.with('requestId' , '4F53FB59-CFC9-4984-B477-452073A352FD').with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("4F53FB59-CFC9-4984-B477-452073A352FD", GremlinScriptChecker.parse("g.with('requestId'   ,  '4F53FB59-CFC9-4984-B477-452073A352FD').with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("4F53FB59-CFC9-4984-B477-452073A352FD", GremlinScriptChecker.parse("g.with('requestId','4F53FB59-CFC9-4984-B477-452073A352FD').with(true).V().out('knows')").
++                getRequestId().get());
++    }
++
++    @Test
++    public void shouldIdentifyRequestIdWithEmbeddedQuote() {
++        assertEquals("te\"st", GremlinScriptChecker.parse("g.with('requestId','te\"st').with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("te\\\"st", GremlinScriptChecker.parse("g.with('requestId', \"te\\\"st\").with(true).V().out('knows')").
++                getRequestId().get());
++    }
++
++    @Test
++    public void shouldIdentifyTimeoutWithLowerL() {
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('evaluationTimeout', 1000l).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('scriptEvaluationTimeout', 1000L).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++    }
++
++    @Test
++    public void shouldIdentifyTimeoutWithNoL() {
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('evaluationTimeout', 1000).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++        assertEquals(1000, GremlinScriptChecker.parse("g.with('scriptEvaluationTimeout', 1000).with(true).V().out('knows')").
++                getTimeout().get().longValue());
++    }
++
 +    @Test
 +    public void shouldIdentifyTimeoutAsStringKeySingleQuoted() {
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with('evaluationTimeout', 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with('scriptEvaluationTimeout', 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +    }
 +
++    @Test
++    public void shouldIdentifyRequestIdAsStringKeySingleQuoted() {
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with('requestId', 'db024fca-ed15-4375-95de-4c6106aef895').with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(\"requestId\", 'db024fca-ed15-4375-95de-4c6106aef895').with(true).V().out('knows')").
++                getRequestId().get());
++    }
++
 +    @Test
 +    public void shouldIdentifyTimeoutAsStringKeyDoubleQuoted() {
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with(\"evaluationTimeout\", 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with(\"scriptEvaluationTimeout\", 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +    }
 +
++    @Test
++    public void shouldIdentifyRequestIdAsStringKeyDoubleQuoted() {
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(\"requestId\", \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(\"requestId\", \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
++                getRequestId().get());
++    }
++
 +    @Test
 +    public void shouldIdentifyTimeoutAsTokenKey() {
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with(Tokens.ARGS_EVAL_TIMEOUT, 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with(Tokens.ARGS_SCRIPT_EVAL_TIMEOUT, 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +    }
 +
++    @Test
++    public void shouldIdentifyRequestIdAsTokenKey() {
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(Tokens.REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(Tokens.REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
++                getRequestId().get());
++    }
++
 +    @Test
 +    public void shouldIdentifyTimeoutAsTokenKeyWithoutClassName() {
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with(ARGS_EVAL_TIMEOUT, 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with(ARGS_SCRIPT_EVAL_TIMEOUT, 1000L).with(true).V().out('knows')").
 +                getTimeout().get().longValue());
 +    }
 +
++    @Test
++    public void shouldIdentifyRequestIdAsTokenKeyWithoutClassName() {
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
++                getRequestId().get());
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", GremlinScriptChecker.parse("g.with(REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").with(true).V().out('knows')").
++                getRequestId().get());
++    }
++
 +    @Test
 +    public void shouldIdentifyMultipleTimeouts() {
 +        assertEquals(6000, GremlinScriptChecker.parse("g.with('evaluationTimeout', 1000L).with(true).V().out('knows');" +
 +                "g.with('evaluationTimeout', 1000L).with(true).V().out('knows');\n" +
 +                "                                                   //g.with('evaluationTimeout', 1000L).with(true).V().out('knows');\n" +
 +                "                                                   /* g.with('evaluationTimeout', 1000L).with(true).V().out('knows');*/\n" +
 +                "                                                   /* \n" +
 +                "g.with('evaluationTimeout', 1000L).with(true).V().out('knows'); \n" +
 +                "*/ \n" +
 +                "                                                   g.with('evaluationTimeout', 1000L).with(true).V().out('knows');\n" +
 +                "                                                   g.with(Tokens.ARGS_SCRIPT_EVAL_TIMEOUT, 1000L).with(true).V().out('knows');\n" +
 +                "                                                   g.with(ARGS_EVAL_TIMEOUT, 1000L).with(true).V().out('knows');\n" +
 +                "                                                   g.with('scriptEvaluationTimeout', 1000L).with(true).V().out('knows');").
 +                getTimeout().get().longValue());
 +    }
 +
++    @Test
++    public void shouldIdentifyMultipleRequestIds() {
++        assertEquals("test9", GremlinScriptChecker.parse("g.with('requestId', 'test1').with(true).V().out('knows');" +
++                        "g.with('requestId', 'test2').with(true).V().out('knows');\n" +
++                        "                                                   //g.with('requestId', 'test3').with(true).V().out('knows');\n" +
++                        "                                                   /* g.with('requestId', 'test4').with(true).V().out('knows');*/\n" +
++                        "                                                   /* \n" +
++                        "g.with('requestId', 'test5').with(true).V().out('knows'); \n" +
++                        "*/ \n" +
++                        "                                                   g.with('requestId', 'test6').with(true).V().out('knows');\n" +
++                        "                                                   g.with(Tokens.REQUEST_ID, 'test7').with(true).V().out('knows');\n" +
++                        "                                                   g.with(REQUEST_ID, 'test8').with(true).V().out('knows');\n" +
++                        "                                                   g.with('requestId', 'test9').with(true).V().out('knows');").
++                getRequestId().get());
++    }
++
++    @Test
++    public void shouldFindAllResults() {
++        final GremlinScriptChecker.Result r = GremlinScriptChecker.parse(
++                "g.with('evaluationTimeout', 1000).with(true).with(REQUEST_ID, \"db024fca-ed15-4375-95de-4c6106aef895\").V().out('knows')");
++        assertEquals(1000, r.getTimeout().get().longValue());
++        assertEquals("db024fca-ed15-4375-95de-4c6106aef895", r.getRequestId().get());
++    }
++
 +    @Test
 +    public void shouldParseLong() {
 +        assertEquals(1000, GremlinScriptChecker.parse("g.with('evaluationTimeout', 1000L).addV().property(id, 'blue').as('b').\n" +
 +                "  addV().property(id, 'orange').as('o').\n" +
 +                "  addV().property(id, 'red').as('r').\n" +
 +                "  addV().property(id, 'green').as('g').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('b').\n" +
 +                "  addE('bridge').from('g').to('o').\n" +
 +                "  addE('bridge').from('g').to('r').\n" +
 +                "  addE('bridge').from('g').to('r').\n" +
 +                "  addE('bridge').from('o').to('b').\n" +
 +                "  addE('bridge').from('o').to('b').\n" +
 +                "  addE('bridge').from('o').to('r').\n" +
 +                "  addE('bridge').from('o').to('r').iterate()").
 +                getTimeout().get().longValue());
 +    }
 +}