You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by dk...@apache.org on 2019/04/02 17:46:11 UTC

[tinkerpop] 01/01: Implemented EdgeLabelVerificationStrategy

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

dkuppitz pushed a commit to branch TINKERPOP-2191
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 21bfb55e12ffd86dad9de39bbaf155ae6bac39fc
Author: Daniel Kuppitz <da...@hotmail.com>
AuthorDate: Thu Mar 14 15:05:09 2019 -0700

    Implemented EdgeLabelVerificationStrategy
---
 .../tinkerpop/gremlin/jsr223/CoreImports.java      |   2 +
 .../EdgeLabelVerificationStrategy.java             | 130 ++++++++++++++
 .../structure/io/graphson/GraphSONModule.java      |   5 +
 .../gremlin/structure/io/gryo/GryoVersion.java     |   7 +-
 .../EdgeLabelVerificationStrategyTest.java         | 188 +++++++++++++++++++++
 5 files changed, 330 insertions(+), 2 deletions(-)

diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
index 576d0de..e298951 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java
@@ -86,6 +86,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.Orde
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathProcessorStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.CountStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ComputerVerificationStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.EdgeLabelVerificationStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.LambdaRestrictionStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.StandardVerificationStrategy;
@@ -241,6 +242,7 @@ public final class CoreImports {
         CLASS_IMPORTS.add(LambdaRestrictionStrategy.class);
         CLASS_IMPORTS.add(ReadOnlyStrategy.class);
         CLASS_IMPORTS.add(StandardVerificationStrategy.class);
+        CLASS_IMPORTS.add(EdgeLabelVerificationStrategy.class);
         // graph traversal
         CLASS_IMPORTS.add(AnonymousTraversalSource.class);
         CLASS_IMPORTS.add(__.class);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/EdgeLabelVerificationStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/EdgeLabelVerificationStrategy.java
new file mode 100644
index 0000000..1b8876a
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/EdgeLabelVerificationStrategy.java
@@ -0,0 +1,130 @@
+/*
+ * 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.process.traversal.strategy.verification;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.commons.configuration.MapConfiguration;
+import org.apache.tinkerpop.gremlin.process.traversal.Step;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * {@code EdgeLabelVerificationStrategy} does not allow edge traversal steps to have no label specified.
+ * Providing one or more labels is considered to be a best practice, however, TinkerPop will not force the specification
+ * of edge labels; instead, providers or users will have to enable this strategy explicitly.
+ * <p/>
+ *
+ * @author Daniel Kujppitz (http://gremlin.guru)
+ * @example <pre>
+ * __.outE()           // throws an IllegalStateException
+ * __.out()            // throws an IllegalStateException
+ * __.bothE()          // throws an IllegalStateException
+ * __.to(OUT)          // throws an IllegalStateException
+ * __.toE(IN)          // throws an IllegalStateException
+ * </pre>
+ */
+public final class EdgeLabelVerificationStrategy
+        extends AbstractTraversalStrategy<TraversalStrategy.VerificationStrategy>
+        implements TraversalStrategy.VerificationStrategy {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(EdgeLabelVerificationStrategy.class);
+
+    private static final String THROW_EXCEPTION = "throwException";
+    private static final String LOG_WARNING = "logWarning";
+
+    private final boolean throwException;
+    private final boolean logWarning;
+
+    private EdgeLabelVerificationStrategy(final boolean throwException, final boolean logWarning) {
+        this.throwException = throwException;
+        this.logWarning = logWarning;
+    }
+
+    @Override
+    public void apply(final Traversal.Admin<?, ?> traversal) {
+        for (final Step<?, ?> step : traversal.getSteps()) {
+            if (step instanceof VertexStep && ((VertexStep) step).getEdgeLabels().length == 0) {
+                final String msg = String.format(
+                        "The provided traversal contains a vertex step without any specified edge label: %s", step);
+                if (logWarning) {
+                    LOGGER.warn(msg);
+                }
+                if (throwException) {
+                    throw new VerificationException(msg, traversal);
+                }
+            }
+        }
+    }
+
+    public static EdgeLabelVerificationStrategy create(final Configuration configuration) {
+        return new EdgeLabelVerificationStrategy(
+                configuration.getBoolean(THROW_EXCEPTION, false),
+                configuration.getBoolean(LOG_WARNING, false));
+    }
+
+    @Override
+    public Configuration getConfiguration() {
+        final Map<String, Object> m = new HashMap<>(2);
+        m.put(THROW_EXCEPTION, this.throwException);
+        m.put(LOG_WARNING, this.logWarning);
+        return new MapConfiguration(m);
+    }
+
+    public static EdgeLabelVerificationStrategy.Builder build() {
+        return new EdgeLabelVerificationStrategy.Builder();
+    }
+
+    public final static class Builder {
+
+        private boolean throwException;
+        private boolean logWarning;
+
+        private Builder() {
+        }
+
+        public EdgeLabelVerificationStrategy.Builder throwException() {
+            return this.throwException(true);
+        }
+
+        public EdgeLabelVerificationStrategy.Builder throwException(final boolean throwException) {
+            this.throwException = throwException;
+            return this;
+        }
+
+        public EdgeLabelVerificationStrategy.Builder logWarning() {
+            return this.logWarning(true);
+        }
+
+        public EdgeLabelVerificationStrategy.Builder logWarning(final boolean logWarning) {
+            this.logWarning = logWarning;
+            return this;
+        }
+
+        public EdgeLabelVerificationStrategy create() {
+            return new EdgeLabelVerificationStrategy(this.throwException, this.logWarning);
+        }
+    }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
index 1078a6b..3bbea66 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java
@@ -54,6 +54,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.Path
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.CountStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ComputerVerificationStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.EdgeLabelVerificationStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.LambdaRestrictionStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.StandardVerificationStrategy;
@@ -183,6 +184,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             ReadOnlyStrategy.class,
                             StandardVerificationStrategy.class,
                             EarlyLimitStrategy.class,
+                            EdgeLabelVerificationStrategy.class,
                             //
                             GraphFilterStrategy.class,
                             VertexProgramStrategy.class
@@ -300,6 +302,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                     ReadOnlyStrategy.class,
                     StandardVerificationStrategy.class,
                     EarlyLimitStrategy.class,
+                    EdgeLabelVerificationStrategy.class,
                     //
                     GraphFilterStrategy.class,
                     VertexProgramStrategy.class
@@ -400,6 +403,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                             ReadOnlyStrategy.class,
                             StandardVerificationStrategy.class,
                             EarlyLimitStrategy.class,
+                            EdgeLabelVerificationStrategy.class,
                             //
                             GraphFilterStrategy.class,
                             VertexProgramStrategy.class
@@ -509,6 +513,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule {
                     ReadOnlyStrategy.class,
                     StandardVerificationStrategy.class,
                     EarlyLimitStrategy.class,
+                    EdgeLabelVerificationStrategy.class,
                     //
                     GraphFilterStrategy.class,
                     VertexProgramStrategy.class
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
index 7c1dd5a..974542d 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java
@@ -63,6 +63,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.Path
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathRetractionStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.CountStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy;
+import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.EdgeLabelVerificationStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.LambdaRestrictionStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ReadOnlyStrategy;
 import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_LP_O_P_S_SE_SL_Traverser;
@@ -336,9 +337,10 @@ public enum GryoVersion {
             add(GryoTypeReg.of(GraphFilterStrategy.class, 157));
             add(GryoTypeReg.of(LambdaRestrictionStrategy.class, 158));
             add(GryoTypeReg.of(ReadOnlyStrategy.class, 159));
-            add(GryoTypeReg.of(EarlyLimitStrategy.class, 188));   // ***LAST ID***
+            add(GryoTypeReg.of(EarlyLimitStrategy.class, 188));
             add(GryoTypeReg.of(MatchStep.CountMatchAlgorithm.class, 160));
             add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 164));
+            add(GryoTypeReg.of(EdgeLabelVerificationStrategy.class, 189));   // ***LAST ID***
 
             add(GryoTypeReg.of(TraverserSet.class, 58));
             add(GryoTypeReg.of(Tree.class, 61));
@@ -555,9 +557,10 @@ public enum GryoVersion {
             add(GryoTypeReg.of(GraphFilterStrategy.class, 157));
             add(GryoTypeReg.of(LambdaRestrictionStrategy.class, 158));
             add(GryoTypeReg.of(ReadOnlyStrategy.class, 159));
-            add(GryoTypeReg.of(EarlyLimitStrategy.class, 188));   // ***LAST ID***
+            add(GryoTypeReg.of(EarlyLimitStrategy.class, 188));
             add(GryoTypeReg.of(MatchStep.CountMatchAlgorithm.class, 160));
             add(GryoTypeReg.of(MatchStep.GreedyMatchAlgorithm.class, 167));
+            add(GryoTypeReg.of(EdgeLabelVerificationStrategy.class, 189));   // ***LAST ID***
             // skip 171, 172 to sync with tp33
             add(GryoTypeReg.of(IndexedTraverserSet.VertexIndexedTraverserSet.class, 173));
         }};
diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/EdgeLabelVerificationStrategyTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/EdgeLabelVerificationStrategyTest.java
new file mode 100644
index 0000000..72a8841
--- /dev/null
+++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/EdgeLabelVerificationStrategyTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.process.traversal.strategy.verification;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.apache.tinkerpop.gremlin.process.traversal.util.DefaultTraversalStrategies;
+import org.apache.tinkerpop.gremlin.structure.Direction;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Daniel Kuppitz (http://gremlin.guru)
+ */
+@RunWith(Parameterized.class)
+public class EdgeLabelVerificationStrategyTest {
+
+    private final static Predicate<String> MSG_PREDICATE = Pattern.compile(
+            "^The provided traversal contains a vertex step without any specified edge label: VertexStep.*")
+            .asPredicate();
+
+    private TestLogAppender logAppender;
+    private Level previousLogLevel;
+
+    @Before
+    public void setupForEachTest() {
+        final org.apache.log4j.Logger strategyLogger = org.apache.log4j.Logger.getLogger(EdgeLabelVerificationStrategy.class);
+        previousLogLevel = strategyLogger.getLevel();
+        strategyLogger.setLevel(Level.WARN);
+        Logger.getRootLogger().addAppender(logAppender = new TestLogAppender());
+    }
+
+    @After
+    public void teardownForEachTest() {
+        final org.apache.log4j.Logger strategyLogger = org.apache.log4j.Logger.getLogger(EdgeLabelVerificationStrategy.class);
+        strategyLogger.setLevel(previousLogLevel);
+        Logger.getRootLogger().removeAppender(logAppender);
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object[]> data() {
+        return Arrays.asList(new Object[][]{
+                {"__.inE()", __.inE(), false},
+                {"__.outE()", __.outE(), false},
+                {"__.bothE()", __.bothE(), false},
+                {"__.to(OUT)", __.to(Direction.OUT), false},
+                {"__.toE(IN)", __.toE(Direction.IN), false},
+                {"__.inE('knows')", __.inE("knows"), true},
+                {"__.outE('knows')", __.outE("knows"), true},
+                {"__.bothE('created','knows')", __.bothE("created", "knows"), true},
+                {"__.to(OUT,'created','knows')", __.to(Direction.OUT, "created", "knows"), true},
+                {"__.toE(IN,'knows')", __.toE(Direction.IN, "knows"), true}
+        });
+    }
+
+    @Parameterized.Parameter(value = 0)
+    public String name;
+
+    @Parameterized.Parameter(value = 1)
+    public Traversal traversal;
+
+    @Parameterized.Parameter(value = 2)
+    public boolean allow;
+
+    @Test
+    public void shouldIgnore() {
+        final TraversalStrategies strategies = new DefaultTraversalStrategies();
+        strategies.addStrategies(EdgeLabelVerificationStrategy.build().create());
+        final Traversal traversal = this.traversal.asAdmin().clone();
+        traversal.asAdmin().setStrategies(strategies);
+        traversal.asAdmin().applyStrategies();
+        assertTrue(logAppender.isEmpty());
+    }
+
+    @Test
+    public void shouldOnlyThrow() {
+        final TraversalStrategies strategies = new DefaultTraversalStrategies();
+        strategies.addStrategies(EdgeLabelVerificationStrategy.build().throwException().create());
+        final Traversal traversal = this.traversal.asAdmin().clone();
+        traversal.asAdmin().setStrategies(strategies);
+        if (allow) {
+            traversal.asAdmin().applyStrategies();
+        } else {
+            try {
+                traversal.asAdmin().applyStrategies();
+                fail("The strategy should not allow vertex steps with unspecified edge labels: " + this.traversal);
+            } catch (VerificationException ise) {
+                assertTrue(MSG_PREDICATE.test(ise.getMessage()));
+            }
+        }
+        assertTrue(logAppender.isEmpty());
+    }
+
+    @Test
+    public void shouldOnlyLog() {
+        final TraversalStrategies strategies = new DefaultTraversalStrategies();
+        strategies.addStrategies(EdgeLabelVerificationStrategy.build().logWarning().create());
+        final Traversal traversal = this.traversal.asAdmin().clone();
+        traversal.asAdmin().setStrategies(strategies);
+        traversal.asAdmin().applyStrategies();
+        if (!allow) {
+            assertTrue(String.format("Expected log entry not found in %s", logAppender.messages),
+                    logAppender.messages().anyMatch(MSG_PREDICATE));
+        }
+    }
+
+    @Test
+    public void shouldThrowAndLog() {
+        final TraversalStrategies strategies = new DefaultTraversalStrategies();
+        strategies.addStrategies(EdgeLabelVerificationStrategy.build().throwException().logWarning().create());
+        final Traversal traversal = this.traversal.asAdmin().clone();
+        traversal.asAdmin().setStrategies(strategies);
+        if (allow) {
+            traversal.asAdmin().applyStrategies();
+            assertTrue(logAppender.isEmpty());
+        } else {
+            try {
+                traversal.asAdmin().applyStrategies();
+                fail("The strategy should not allow vertex steps with unspecified edge labels: " + this.traversal);
+            } catch (VerificationException ise) {
+                assertTrue(MSG_PREDICATE.test(ise.getMessage()));
+            }
+            assertTrue(String.format("Expected log entry not found in %s", logAppender.messages),
+                    logAppender.messages().anyMatch(MSG_PREDICATE));
+        }
+    }
+
+    class TestLogAppender extends AppenderSkeleton {
+
+        private List<String> messages = new ArrayList<>();
+
+        boolean isEmpty() {
+            return messages.isEmpty();
+        }
+
+        Stream<String> messages() {
+            return messages.stream();
+        }
+
+        @Override
+        protected void append(org.apache.log4j.spi.LoggingEvent loggingEvent) {
+            messages.add(loggingEvent.getMessage().toString());
+        }
+
+        @Override
+        public void close() {
+
+        }
+
+        @Override
+        public boolean requiresLayout() {
+            return false;
+        }
+    }
+}
\ No newline at end of file