/* * 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.decoration; import org.apache.commons.configuration.Configuration; import org.apache.commons.configuration.MapConfiguration; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Parameterizing; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.IdStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.PropertiesStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.PropertyType; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.function.Supplier; /** * Provides a degree of control over element identifier assignment as some graphs don't provide that feature. This * strategy provides for identifier assignment by enabling users to utilize vertex and edge indices under the hood, * thus simulating that capability. * <p/> * By default, when an identifier is not supplied by the user, newly generated identifiers are {@link UUID} objects. * This behavior can be overridden by setting the {@link Builder#idMaker(Supplier)}. * <p/> * Unless otherwise specified the identifier is stored in the {@code __id} property. This can be changed by setting * the {@link Builder#idPropertyKey(String)} * * @author Marko A. Rodriguez (http://markorodriguez.com) * @author Stephen Mallette (http://stephen.genoprime.com) */ public final class ElementIdStrategy extends AbstractTraversalStrategy<TraversalStrategy.DecorationStrategy> implements TraversalStrategy.DecorationStrategy { private final String idPropertyKey; private final Supplier<Object> idMaker; private ElementIdStrategy(final String idPropertyKey, final Supplier<Object> idMaker) { this.idPropertyKey = idPropertyKey; this.idMaker = idMaker; } public String getIdPropertyKey() { return idPropertyKey; } public Supplier<Object> getIdMaker() { return idMaker; } @Override public void apply(final Traversal.Admin<?, ?> traversal) { TraversalHelper.getStepsOfAssignableClass(HasStep.class, traversal).stream() .filter(hasStep -> ((HasStep<?>) hasStep).getHasContainers().get(0).getKey().equals(T.id.getAccessor())) .forEach(hasStep -> ((HasStep<?>) hasStep).getHasContainers().get(0).setKey(this.idPropertyKey)); if (traversal.getStartStep() instanceof GraphStep) { final GraphStep graphStep = (GraphStep) traversal.getStartStep(); // only need to apply the custom id if ids were assigned - otherwise we want the full iterator. // note that it is then only necessary to replace the step if the id is a non-element. other tests // in the suite validate that items in getIds() is uniform so it is ok to just test the first item // in the list. if (graphStep.getIds().length > 0 && !(graphStep.getIds()[0] instanceof Element)) { if (graphStep instanceof HasContainerHolder) ((HasContainerHolder) graphStep).addHasContainer(new HasContainer(this.idPropertyKey, P.within(Arrays.asList(graphStep.getIds())))); else TraversalHelper.insertAfterStep(new HasStep(traversal, new HasContainer(this.idPropertyKey, P.within(Arrays.asList(graphStep.getIds())))), graphStep, traversal); graphStep.clearIds(); } } TraversalHelper.getStepsOfAssignableClass(IdStep.class, traversal).stream().forEach(step -> { TraversalHelper.replaceStep(step, new PropertiesStep(traversal, PropertyType.VALUE, idPropertyKey), traversal); }); // in each case below, determine if the T.id is present and if so, replace T.id with the idPropertyKey or if // it is not present then shove it in there and generate an id traversal.getSteps().forEach(step -> { if (step instanceof AddVertexStep || step instanceof AddVertexStartStep || step instanceof AddEdgeStep) { final Parameterizing parameterizing = (Parameterizing) step; if (parameterizing.getParameters().contains(T.id)) parameterizing.getParameters().rename(T.id, this.idPropertyKey); else if (!parameterizing.getParameters().contains(this.idPropertyKey)) parameterizing.getParameters().set(null, this.idPropertyKey, idMaker.get()); } }); } public static Builder build() { return new Builder(); } @Override public String toString() { return StringFactory.traversalStrategyString(this); } public final static class Builder { private String idPropertyKey = "__id"; private Supplier<Object> idMaker = () -> UUID.randomUUID().toString(); private Builder() { } /** * Creates a new unique identifier for the next created {@link Element}. */ public Builder idMaker(final Supplier<Object> idMaker) { this.idMaker = idMaker; return this; } /** * This key within which to hold the user-specified identifier. This field should be indexed by the * underlying graph. */ public Builder idPropertyKey(final String idPropertyKey) { this.idPropertyKey = idPropertyKey; return this; } public ElementIdStrategy create() { return new ElementIdStrategy(idPropertyKey, idMaker); } } public static final String ID_PROPERTY_KEY = "idPropertyKey"; public static final String ID_MAKER = "idMaker"; public static ElementIdStrategy create(final Configuration configuration) { final ElementIdStrategy.Builder builder = ElementIdStrategy.build(); if (configuration.containsKey(ID_MAKER)) builder.idMaker((Supplier) configuration.getProperty(ID_MAKER)); if (configuration.containsKey(ID_PROPERTY_KEY)) builder.idPropertyKey(configuration.getString(ID_PROPERTY_KEY)); return builder.create(); } @Override public Configuration getConfiguration() { final Map<String, Object> map = new HashMap<>(); map.put(STRATEGY, ElementIdStrategy.class.getCanonicalName()); map.put(ID_PROPERTY_KEY, this.idPropertyKey); map.put(ID_MAKER, this.idMaker); return new MapConfiguration(map); } }