wip
Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/939674a5 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/939674a5 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/939674a5 Branch: refs/heads/shortest-path-wip Commit: 939674a5da218566628e54b63a30b98883367036 Parents: 6a40535 Author: Stephen Mallette <[email protected]> Authored: Fri May 25 16:02:59 2018 -0400 Committer: Daniel Kuppitz <[email protected]> Committed: Thu May 31 12:46:10 2018 -0700 ---------------------------------------------------------------------- .../step/util/DefaultStepConfiguration.java | 158 ++++++++++++++++++ .../step/util/StepConfigurationProxy.java | 50 ++++++ .../step/util/DefaultStepConfigurationTest.java | 167 +++++++++++++++++++ .../Process/Traversal/IStepConfiguration.cs | 32 ++++ 4 files changed, 407 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/939674a5/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfiguration.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfiguration.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfiguration.java new file mode 100644 index 0000000..b75358f --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfiguration.java @@ -0,0 +1,158 @@ +/* + * 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.step.util; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.lang.reflect.MethodUtils; +import org.apache.tinkerpop.gremlin.process.remote.traversal.strategy.decoration.RemoteStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.step.StepConfiguration; +import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * A basic {@link StepConfiguration} implementation that uses reflection to set methods on the step to which the + * configuration will be applied. While use of reflection isn't quite as nice as direct application of configuration + * options to a step, this implementation is serialization ready and thus requires no additional work from the + * developer to get a step option ready for usage. If using this implementation, it is of extreme importance that + * the developer implement solid test coverage to ensure that reflection calls will work at runtime as compilation + * errors will not be raised under this approach. + * + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public class DefaultStepConfiguration implements StepConfiguration<Step> { + + private final Map<String, List<Object>> conf; + private final Class<? extends Step> expects; + + /** + * Creates a new {@code DefaultStepConfiguration}. + * + * @param method to call on the step + * @param args the arguments to pass to the method + */ + public DefaultStepConfiguration(final String method, final Object... args) { + this(null, method, args); + } + + /** + * Creates a new {@code DefaultStepConfiguration}. + * + * @param methods a map of methods to call when configuring a step where the keys are the method names and the + * values are the list of arguments to apply to that method + */ + public DefaultStepConfiguration(final LinkedHashMap<String, List<Object>> methods) { + this(null, methods); + } + + /** + * Creates a new {@code DefaultStepConfiguration} with a validation option to ensure that the configuration is + * applied to the right type of step. + * + * @param expects the step type that this configuration should be applied to + * @param method to call on the step + * @param args the arguments to pass to the method + */ + public DefaultStepConfiguration(final Class<? extends Step> expects, final String method, final Object... args) { + if (null == method || method.isEmpty()) throw new IllegalArgumentException("method may not be null or empty"); + conf = new LinkedHashMap<>(); + conf.put(method, Arrays.asList(args)); + this.expects = expects; + } + + /** + * Creates a new {@code DefaultStepConfiguration} with a validation option to ensure that the configuration is + * applied to the right type of step. + * + * @param expects the step type that this configuration should be applied to + * @param methods a map of methods to call when configuring a step where the keys are the method names and the + * values are the list of arguments to apply to that method + */ + public DefaultStepConfiguration(final Class<? extends Step> expects, final LinkedHashMap<String, List<Object>> methods) { + if (null == methods || methods.isEmpty()) throw new IllegalArgumentException("methods may not be null or empty"); + if (IteratorUtils.anyMatch(methods.keySet().iterator(), k -> null == k || k.isEmpty())) throw new IllegalArgumentException("no key of methods map may be null or empty"); + conf = methods; + this.expects = expects; + } + + private DefaultStepConfiguration() { + // for gyro's sake......... + conf = Collections.emptyMap(); + expects = null; + } + + @Override + public void accept(final Step step) { + final Optional<Class<? extends Step>> opt = Optional.ofNullable(expects); + if (opt.isPresent() && !opt.get().isAssignableFrom(step.getClass())) { + throw new IllegalStateException(String.format("Could not apply step configuration of %s to %s", conf, step.getClass().getName())); + } + + for (Map.Entry<String, List<Object>> kv : conf.entrySet()) { + try { + MethodUtils.invokeMethod(step, kv.getKey(), kv.getValue().toArray()); + } catch (NoSuchMethodException nsme) { + if (!step.getTraversal().asAdmin().getStrategies().getStrategy(RemoteStrategy.class).isPresent()) + throw new IllegalStateException(String.format("Step configuration of %s with args of %s cannot be applied to %s", + kv.getKey(), kv.getValue(), step.getClass().getName()), nsme); + } catch (Exception ex) { + throw new IllegalStateException(String.format("Step configuration of %s with args of %s cannot be applied to %s", + kv.getKey(), kv.getValue(), step.getClass().getName()), ex); + } + } + } + + public static StepConfiguration create(final Configuration conf) { + final LinkedHashMap<String,List<Object>> m = new LinkedHashMap<>(); + final Iterator<String> keys = conf.getKeys(); + while (keys.hasNext()) { + final String key = keys.next(); + m.put(key, conf.getList(key)); + } + return new DefaultStepConfiguration(m); + } + + @Override + public Configuration getConfiguration() { + return new MapConfiguration(conf); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final DefaultStepConfiguration that = (DefaultStepConfiguration) o; + + return conf.equals(that.conf); + } + + @Override + public int hashCode() { + return conf.hashCode(); + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/939674a5/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StepConfigurationProxy.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StepConfigurationProxy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StepConfigurationProxy.java new file mode 100644 index 0000000..6226900 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/StepConfigurationProxy.java @@ -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.process.traversal.step.util; + +import org.apache.commons.configuration.Configuration; +import org.apache.tinkerpop.gremlin.process.traversal.step.StepConfiguration; + +import java.io.Serializable; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public class StepConfigurationProxy<T extends StepConfiguration> implements Serializable { + + private final Configuration configuration; + private final Class<T> stepConfigurationClass; + + public StepConfigurationProxy(final T stepConfiguration) { + this((Class<T>) stepConfiguration.getClass(), stepConfiguration.getConfiguration()); + } + + public StepConfigurationProxy(final Class<T> stepConfigurationClass, final Configuration configuration) { + this.configuration = configuration; + this.stepConfigurationClass = stepConfigurationClass; + } + + public Configuration getConfiguration() { + return this.configuration; + } + + public Class<T> getStepConfigurationClass() { + return this.stepConfigurationClass; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/939674a5/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfigurationTest.java ---------------------------------------------------------------------- diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfigurationTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfigurationTest.java new file mode 100644 index 0000000..6b3fc1c --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/DefaultStepConfigurationTest.java @@ -0,0 +1,167 @@ +/* + * 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.step.util; + +import org.apache.commons.configuration.Configuration; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PageRankVertexProgramStep; +import org.apache.tinkerpop.gremlin.process.remote.RemoteConnection; +import org.apache.tinkerpop.gremlin.process.remote.RemoteConnectionException; +import org.apache.tinkerpop.gremlin.process.remote.traversal.RemoteTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.step.StepConfiguration; +import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.NoSuchElementException; + +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public class DefaultStepConfigurationTest { + + @Test + public void shouldApplyDefaultConfiguration() throws Exception { + final Traversal t = __.V().pageRank().with(new DefaultStepConfiguration("modulateBy", "xxx")); + final Step s = t.asAdmin().getEndStep(); + assertEquals("xxx", FieldUtils.readField(s, "pageRankProperty", true)); + } + + @Test + public void shouldApplyDefaultConfigurationWithClassValidation() throws Exception { + final Traversal t = __.V().pageRank().with(new DefaultStepConfiguration(PageRankVertexProgramStep.class, "modulateBy", "xxx")); + final Step s = t.asAdmin().getEndStep(); + assertEquals("xxx", FieldUtils.readField(s, "pageRankProperty", true)); + } + + @Test + public void shouldApplyDefaultConfigurationInOrder() throws Exception { + final LinkedHashMap<String, List<Object>> methods = new LinkedHashMap<>(); + methods.put("setY", Collections.singletonList(100L)); + methods.put("setX", Collections.singletonList("xxx")); + methods.put("setZ", Collections.singletonList("zzz" )); + final StepConfiguration<Step> conf = new DefaultStepConfiguration(methods); + final MockStep step = new MockStep(__.__().asAdmin()); + + conf.accept(step); + + assertThat(step.list, contains(100L, "Xxxx", "Zzzz")); + } + + @Test + public void shouldGenerateConfiguration() throws Exception { + final LinkedHashMap<String, List<Object>> methods = new LinkedHashMap<>(); + methods.put("setY", Collections.singletonList(100L)); + methods.put("setX", Collections.singletonList("xxx")); + methods.put("setZ", Collections.singletonList("zzz" )); + final StepConfiguration<Step> conf = new DefaultStepConfiguration(methods); + final MapConfiguration c = (MapConfiguration) conf.getConfiguration(); + c.setDelimiterParsingDisabled(false); + + assertEquals(100L, c.getList("setY").get(0)); + assertEquals("xxx", c.getList("setX").get(0)); + assertEquals("zzz", c.getList("setZ").get(0)); + } + + @Test(expected = IllegalStateException.class) + public void shouldValidateClass() { + __.V().pageRank().with(new DefaultStepConfiguration(MockStep.class, "modulateBy", "xxx")); + } + + @Test + public void shouldAllowNoSuchMethodIfUsingRemote() { + // create a fake remote + final GraphTraversalSource g = EmptyGraph.instance().traversal().withRemote(new RemoteConnection() { + @Override + public <E> Iterator<Traverser.Admin<E>> submit(final Traversal<?, E> traversal) throws RemoteConnectionException { + return null; + } + + @Override + public <E> RemoteTraversal<?, E> submit(final Bytecode bytecode) throws RemoteConnectionException { + return null; + } + + @Override + public void close() throws Exception { + + } + }); + + // try to set a fake configuration option - lack of exception is good. not really sure how else to directly + // assert this + final LinkedHashMap<String, List<Object>> methods = new LinkedHashMap<>(); + methods.put("setFakeyFakerton", Collections.singletonList(100L)); + final StepConfiguration<Step> conf = new DefaultStepConfiguration(methods); + g.V().with(conf); + } + + @Test(expected = IllegalStateException.class) + public void shouldNotAllowNoSuchMethodUnlessUsingRemote() { + final GraphTraversalSource g = EmptyGraph.instance().traversal(); + + // try to set a fake configuration option + final LinkedHashMap<String, List<Object>> methods = new LinkedHashMap<>(); + methods.put("setFakeyFakerton", Collections.singletonList(100L)); + final StepConfiguration<Step> conf = new DefaultStepConfiguration(methods); + g.V().with(conf); + } + + static class MockStep extends AbstractStep { + + List<Object> list = new ArrayList<>(); + + MockStep(final Traversal.Admin t) { + super(t); + } + + public void setX(final String s) { + list.add("X" + s); + } + + public void setY(final Long s) { + list.add(s); + } + + public void setZ(final String s) { + list.add("Z" + s); + } + + + @Override + protected Traverser.Admin processNextStart() throws NoSuchElementException { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/939674a5/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/IStepConfiguration.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/IStepConfiguration.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/IStepConfiguration.cs new file mode 100644 index 0000000..a01ac1c --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/IStepConfiguration.cs @@ -0,0 +1,32 @@ +#region License + +/* + * 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. + */ + +#endregion + +namespace Gremlin.Net.Process.Traversal +{ + /// <summary> + /// A configuration for a step supplied to the with() modulator of a traversal. + /// </summary> + public interface IStepConfiguration + { + } +} \ No newline at end of file
