////////////////////////////////////////////////////////////////////////////////////////// // // Implementation of the TinkerPop OLTP Provider API for ArangoDB // // Copyright triAGENS GmbH Cologne and The University of York // ////////////////////////////////////////////////////////////////////////////////////////// package com.arangodb.tinkerpop.gremlin.structure; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.stream.Collectors; import org.apache.commons.lang.ArrayUtils; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality; import org.apache.tinkerpop.gremlin.structure.util.ElementHelper; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.arangodb.ArangoCursor; import com.arangodb.tinkerpop.gremlin.client.ArangoDBBaseDocument; import com.arangodb.tinkerpop.gremlin.client.ArangoDBGraphException; import com.arangodb.tinkerpop.gremlin.client.ArangoDBIterator; import com.arangodb.tinkerpop.gremlin.client.ArangoDBPropertyFilter; import com.arangodb.tinkerpop.gremlin.client.ArangoDBPropertyIterator; import com.arangodb.tinkerpop.gremlin.client.ArangoDBQueryBuilder; import com.arangodb.tinkerpop.gremlin.utils.ArangoDBUtil; /** * The ArangoDB vertex class. * * @author Achim Brandt (http://www.triagens.de) * @author Johannes Gocke (http://www.triagens.de) * @author Guido Schwab (http://www.triagens.de) * @author Horacio Hoyos Rodriguez (https://www.york.ac.uk) */ public class ArangoDBVertex extends ArangoDBBaseDocument implements Vertex { private static final Logger logger = LoggerFactory.getLogger(ArangoDBVertex.class); /** * Constructor used for ArabgoDB JavaBeans serialisation. */ public ArangoDBVertex() { super(); } /** * Instantiates a new ArangoDB vertex with he given key. * * @param key the key to assign to the vertex * @param label the label of the vertex * @param graph the graph that owns the vertex */ public ArangoDBVertex(String key, String label, ArangoDBGraph graph) { super(key, label, graph); } /** * Instantiates a new ArangoDB vertex. * * @param graph the graph * @param collection the collection */ public ArangoDBVertex(ArangoDBGraph graph, String collection) { this(null, collection, graph); } @Override public Object id() { return _id(); } @Override public String label() { return label; } @Override public void remove() { logger.info("remove {}", this._id()); if (paired) { Map<String, Object> bindVars = new HashMap<>(); // Delete properties properties().forEachRemaining(VertexProperty::remove); //Remove vertex try { graph.getClient().deleteDocument(this); // Need to delete incoming edges too } catch (ArangoDBGraphException ex) { // Pass Removing a property that does not exists should not throw an exception. } } } @Override public Edge addEdge(String label, Vertex inVertex, Object... keyValues) { logger.info("addEdge in collection {} to vertex {}", label, inVertex == null ? "?" :inVertex.id()); ElementHelper.legalPropertyKeyValueArray(keyValues); ElementHelper.validateLabel(label); if (!graph.edgeCollections().contains(label)) { throw new IllegalArgumentException(String.format("Edge label (%s)not in graph (%s) edge collections.", label, graph.name())); } if (inVertex == null) { Graph.Exceptions.argumentCanNotBeNull("vertex"); } Object id; ArangoDBEdge edge = null; if (ElementHelper.getIdValue(keyValues).isPresent()) { id = ElementHelper.getIdValue(keyValues).get(); if (graph.features().edge().willAllowId(id)) { if (id.toString().contains("/")) { String fullId = id.toString(); String[] parts = fullId.split("/"); // The collection name is the last part of the full name String[] collectionParts = parts[0].split("_"); String collectionName = collectionParts[collectionParts.length-1]; if (collectionName.contains(label)) { id = parts[1]; } } Matcher m = ArangoDBUtil.DOCUMENT_KEY.matcher((String)id); if (m.matches()) { edge = new ArangoDBEdge(id.toString(), label, this, ((ArangoDBVertex) inVertex), graph); } else { throw new ArangoDBGraphException(String.format("Given id (%s) has unsupported characters.", id)); } } else { throw Vertex.Exceptions.userSuppliedIdsOfThisTypeNotSupported(); } } else { edge = new ArangoDBEdge(graph, label, this, ((ArangoDBVertex) inVertex)); } // The vertex needs to exist before we can attach properties graph.getClient().insertEdge(edge); ElementHelper.attachProperties(edge, keyValues); return edge; } @Override public <V> VertexProperty<V> property( Cardinality cardinality, String key, V value, Object... keyValues) { logger.debug("setting vertex property {} = {} ({})", key, value, keyValues); ElementHelper.validateProperty(key, value); ElementHelper.legalPropertyKeyValueArray(keyValues); Optional<Object> idValue = ElementHelper.getIdValue(keyValues); String id = null; if (idValue.isPresent()) { if (graph.features().vertex().willAllowId(idValue.get())) { id = idValue.get().toString(); if (id.toString().contains("/")) { String fullId = id.toString(); String[] parts = fullId.split("/"); // The collection name is the last part of the full name String[] collectionParts = parts[0].split("_"); String collectionName = collectionParts[collectionParts.length-1]; if (collectionName.contains(ArangoDBGraph.ELEMENT_PROPERTIES_COLLECTION)) { id = parts[1]; } } Matcher m = ArangoDBUtil.DOCUMENT_KEY.matcher((String)id); if (!m.matches()) { throw new ArangoDBGraphException(String.format("Given id (%s) has unsupported characters.", id)); } } else { throw VertexProperty.Exceptions.userSuppliedIdsOfThisTypeNotSupported(); } int idIndex = 0; for (int i = 0; i < keyValues.length; i+=2) { if (keyValues[i] == T.id) { idIndex = i; break; } } keyValues = ArrayUtils.remove(keyValues, idIndex); keyValues = ArrayUtils.remove(keyValues, idIndex); } final Optional<VertexProperty<V>> optionalVertexProperty = ElementHelper.stageVertexProperty(this, cardinality, key, value, keyValues); if (optionalVertexProperty.isPresent()) return optionalVertexProperty.get(); ArangoDBVertexProperty<V> p = ArangoDBUtil.createArangoDBVertexProperty(id, key, value, this); ElementHelper.attachProperties(p, keyValues); return p; } @Override public Iterator<Edge> edges(Direction direction, String... edgeLabels) { List<String> edgeCollections = getQueryEdgeCollections(edgeLabels); // If edgeLabels was not empty but all were discarded, this means that we should // return an empty iterator, i.e. no edges for that edgeLabels exist. if (edgeCollections.isEmpty()) { return Collections.emptyIterator(); } return new ArangoDBIterator<>(graph, graph.getClient().getVertexEdges(this, edgeCollections, direction)); } @Override public Iterator<Vertex> vertices(Direction direction, String... edgeLabels) { List<String> edgeCollections = getQueryEdgeCollections(edgeLabels); // If edgeLabels was not empty but all were discarded, this means that we should // return an empty iterator, i.e. no edges for that edgeLabels exist. if (edgeCollections.isEmpty()) { return Collections.emptyIterator(); } ArangoCursor<ArangoDBVertex> documentNeighbors = graph.getClient().getDocumentNeighbors(this, edgeCollections, direction, ArangoDBPropertyFilter.empty(), ArangoDBVertex.class); return new ArangoDBIterator<>(graph, documentNeighbors); } @SuppressWarnings("unchecked") @Override public <V> Iterator<VertexProperty<V>> properties(String... propertyKeys) { logger.debug("Get properties {}", (Object[])propertyKeys); List<String> labels = new ArrayList<>(); labels.add(graph.getPrefixedCollectioName(ArangoDBGraph.ELEMENT_PROPERTIES_EDGE_COLLECTION)); ArangoDBPropertyFilter filter = new ArangoDBPropertyFilter(); for (String pk : propertyKeys) { filter.has("name", pk, ArangoDBPropertyFilter.Compare.EQUAL); } ArangoCursor<?> query = graph.getClient().getElementProperties(this, labels, filter, ArangoDBVertexProperty.class); return new ArangoDBPropertyIterator<>(graph, (ArangoCursor<ArangoDBVertexProperty<V>>) query); } @Override public String toString() { return StringFactory.vertexString(this); } /** * Save. */ public void save() { if (paired) { graph.getClient().updateDocument(this); } } /** * Query will raise an exception if the edge_collection name is not in the graph, so we need to filter out * edgeLabels not in the graph. * * @param edgeLabels * @return */ private List<String> getQueryEdgeCollections(String... edgeLabels) { List<String> vertexCollections; if (edgeLabels.length == 0) { vertexCollections = graph.edgeCollections().stream().map(graph::getPrefixedCollectioName).collect(Collectors.toList()); } else { vertexCollections = Arrays.stream(edgeLabels) .filter(el -> graph.edgeCollections().contains(el)) .map(graph::getPrefixedCollectioName) .collect(Collectors.toList()); } return vertexCollections; } @Override public boolean equals(final Object object) { return ElementHelper.areEqual(this, object); } @Override public int hashCode() { return ElementHelper.hashCode(this); } }