//////////////////////////////////////////////////////////////////////////////////////////
//
// Implementation of the Blueprints Interface for ArangoDB by triAGENS GmbH Cologne.
//
// Copyright triAGENS GmbH Cologne.
//
//////////////////////////////////////////////////////////////////////////////////////////
package com.arangodb.blueprints;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import com.arangodb.ArangoException;
import com.arangodb.blueprints.client.ArangoDBConfiguration;
import com.arangodb.blueprints.client.ArangoDBException;
import com.arangodb.blueprints.client.ArangoDBIndex;
import com.arangodb.blueprints.client.ArangoDBSimpleGraph;
import com.arangodb.blueprints.client.ArangoDBSimpleGraphClient;
import com.arangodb.blueprints.utils.ArangoDBUtil;
import com.arangodb.entity.EdgeDefinitionEntity;
import com.arangodb.entity.GraphEntity;
import com.arangodb.entity.IndexType;
import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Element;
import com.tinkerpop.blueprints.Features;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.GraphQuery;
import com.tinkerpop.blueprints.KeyIndexableGraph;
import com.tinkerpop.blueprints.MetaGraph;
import com.tinkerpop.blueprints.Parameter;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.ExceptionFactory;
import com.tinkerpop.blueprints.util.StringFactory;
/**
* The ArangoDB graph class
*
* @author Achim Brandt (http://www.triagens.de)
* @author Johannes Gocke (http://www.triagens.de)
* @author Guido Schwab (http://www.triagens.de)
*/
public class ArangoDBGraph implements Graph, MetaGraph<ArangoDBSimpleGraph>, KeyIndexableGraph {
/**
* the logger
*/
private static final Logger logger = Logger.getLogger(ArangoDBElement.class);
private static final Features FEATURES = new Features();
static {
FEATURES.supportsDuplicateEdges = true;
FEATURES.supportsSelfLoops = true;
FEATURES.isPersistent = true;
FEATURES.supportsVertexIteration = true;
FEATURES.supportsEdgeIteration = true;
FEATURES.supportsVertexIndex = false;
FEATURES.supportsEdgeIndex = false;
FEATURES.ignoresSuppliedIds = false;
FEATURES.supportsTransactions = false;
FEATURES.supportsEdgeKeyIndex = true;
FEATURES.supportsVertexKeyIndex = true;
FEATURES.supportsKeyIndices = true;
FEATURES.isWrapper = true;
FEATURES.supportsIndices = false;
FEATURES.supportsEdgeRetrieval = true;
FEATURES.supportsVertexProperties = true;
FEATURES.supportsEdgeProperties = true;
// For more information on supported types, please see:
// http://code.google.com/p/orient/wiki/Types
FEATURES.supportsSerializableObjectProperty = true;
FEATURES.supportsBooleanProperty = true;
FEATURES.supportsDoubleProperty = true;
FEATURES.supportsFloatProperty = true;
FEATURES.supportsIntegerProperty = true;
FEATURES.supportsPrimitiveArrayProperty = true;
FEATURES.supportsUniformListProperty = true;
FEATURES.supportsMixedListProperty = true;
FEATURES.supportsLongProperty = true;
FEATURES.supportsMapProperty = true;
FEATURES.supportsStringProperty = true;
FEATURES.supportsThreadedTransactions = false;
FEATURES.supportsThreadIsolatedTransactions = false;
}
/**
* ArangoDBSimpleGraph
*/
private ArangoDBSimpleGraph simpleGraph = null;
/**
* A ArangoDBSimpleGraphClient to handle the connection to the Database
*/
private ArangoDBSimpleGraphClient client = null;
/**
* Creates a Graph (simple configuration)
*
* @param host
* the ArangoDB host name
* @param port
* the ArangoDB port
* @param name
* the name of the graph
* @param verticesCollectionName
* the name of the vertices collection
* @param edgesCollectionName
* the name of the edges collection
*
* @throws ArangoDBGraphException
* if the graph could not be created
*/
public ArangoDBGraph(String host, int port, String name, String verticesCollectionName, String edgesCollectionName)
throws ArangoDBGraphException {
this(new ArangoDBConfiguration(host, port), name, verticesCollectionName, edgesCollectionName);
}
/**
* Creates a Graph
*
* @param configuration
* an ArangoDB configuration object
* @param name
* the name of the graph
* @param verticesCollectionName
* the name of the vertices collection
* @param edgesCollectionName
* the name of the edges collection
*
* @throws ArangoDBGraphException
* if the graph could not be created
*/
public ArangoDBGraph(ArangoDBConfiguration configuration, String name, String verticesCollectionName,
String edgesCollectionName) throws ArangoDBGraphException {
checkNames(name, verticesCollectionName, edgesCollectionName);
client = new ArangoDBSimpleGraphClient(configuration);
try {
GraphEntity graph = client.getGraph(name);
if (graph != null) {
boolean error = graphHasError(verticesCollectionName, edgesCollectionName, graph);
if (error) {
throw new ArangoDBGraphException("Graph with that name already exists but with other settings");
}
simpleGraph = new ArangoDBSimpleGraph(graph, verticesCollectionName, edgesCollectionName);
}
} catch (ArangoException e1) {
logger.debug("could not get graph", e1);
}
if (simpleGraph == null) {
try {
simpleGraph = this.client.createGraph(name, verticesCollectionName, edgesCollectionName);
} catch (ArangoException e2) {
throw new ArangoDBGraphException(e2);
}
}
}
private void checkNames(String name, String verticesCollectionName, String edgesCollectionName)
throws ArangoDBGraphException {
if (StringUtils.isBlank(name)) {
throw new ArangoDBGraphException("graph name must not be null.");
}
if (StringUtils.isBlank(verticesCollectionName)) {
throw new ArangoDBGraphException("vertex collection name must not be null.");
}
if (StringUtils.isBlank(edgesCollectionName)) {
throw new ArangoDBGraphException("edge collection name must not be null.");
}
}
private boolean graphHasError(String verticesCollectionName, String edgesCollectionName, GraphEntity graph) {
boolean error = false;
List<EdgeDefinitionEntity> edgeDefinitions = graph.getEdgeDefinitions();
if (edgeDefinitions.size() != 1 || CollectionUtils.isNotEmpty(graph.getOrphanCollections())) {
error = true;
} else {
EdgeDefinitionEntity edgeDefinitionEntity = edgeDefinitions.get(0);
if (!edgesCollectionName.equals(edgeDefinitionEntity.getCollection())
|| hasOneFromAndTo(edgeDefinitionEntity)
|| !verticesCollectionName.equals(edgeDefinitionEntity.getFrom().get(0))
|| !verticesCollectionName.equals(edgeDefinitionEntity.getTo().get(0))) {
error = true;
}
}
return error;
}
private boolean hasOneFromAndTo(EdgeDefinitionEntity edgeDefinitionEntity) {
return edgeDefinitionEntity.getFrom().size() != 1 || edgeDefinitionEntity.getTo().size() != 1;
}
@Override
public Features getFeatures() {
return FEATURES;
}
@Override
public void shutdown() {
client.shutdown();
}
@Override
public Vertex addVertex(Object id) {
return ArangoDBVertex.create(this, id);
}
@Override
public Vertex getVertex(Object id) {
return ArangoDBVertex.load(this, id);
}
@Override
public void removeVertex(Vertex vertex) {
if (vertex.getClass().equals(ArangoDBVertex.class)) {
ArangoDBVertex v = (ArangoDBVertex) vertex;
v.remove();
}
}
@Override
public Iterable<Vertex> getVertices() {
ArangoDBGraphQuery q = new ArangoDBGraphQuery(this);
return q.vertices();
}
@Override
public Iterable<Vertex> getVertices(String key, Object value) {
ArangoDBGraphQuery q = new ArangoDBGraphQuery(this);
q.has(key, value);
return q.vertices();
}
@Override
public Edge addEdge(Object id, Vertex outVertex, Vertex inVertex, String label) {
if (label == null) {
throw ExceptionFactory.edgeLabelCanNotBeNull();
}
return ArangoDBEdge.create(this, id, outVertex, inVertex, label);
}
@Override
public Edge getEdge(Object id) {
return ArangoDBEdge.load(this, id);
}
@Override
public void removeEdge(Edge edge) {
if (edge.getClass().equals(ArangoDBEdge.class)) {
ArangoDBEdge e = (ArangoDBEdge) edge;
e.remove();
}
}
@Override
public Iterable<Edge> getEdges() {
ArangoDBGraphQuery q = new ArangoDBGraphQuery(this);
return q.edges();
}
@Override
public Iterable<Edge> getEdges(String key, Object value) {
ArangoDBGraphQuery q = new ArangoDBGraphQuery(this);
q.has(key, value);
return q.edges();
}
@Override
public ArangoDBSimpleGraph getRawGraph() {
return simpleGraph;
}
@Override
public String toString() {
return StringFactory.graphString(this, this.simpleGraph.toString());
}
@Override
public <T extends Element> void dropKeyIndex(String key, Class<T> elementClass) {
List<ArangoDBIndex> indices = null;
try {
if (elementClass.isAssignableFrom(Vertex.class)) {
indices = client.getVertexIndices(simpleGraph);
} else if (elementClass.isAssignableFrom(Edge.class)) {
indices = client.getEdgeIndices(simpleGraph);
}
} catch (ArangoDBException e) {
logger.warn("error while reading an index", e);
}
String normalizedKey = ArangoDBUtil.normalizeKey(key);
if (indices != null) {
for (ArangoDBIndex index : indices) {
if (index.getFields().size() == 1) {
deleteIndexByKey(normalizedKey, index);
}
}
}
}
private void deleteIndexByKey(String normalizedKey, ArangoDBIndex index) {
String field = index.getFields().get(0);
if (field.equals(normalizedKey)) {
try {
client.deleteIndex(index.getId());
} catch (ArangoDBException e) {
logger.warn("error while deleting an index", e);
}
}
}
@SuppressWarnings("rawtypes")
@Override
public <T extends Element> void createKeyIndex(String key, Class<T> elementClass, Parameter... indexParameters) {
IndexType type = IndexType.SKIPLIST;
boolean unique = false;
List<String> fields = new ArrayList<String>();
String n = ArangoDBUtil.normalizeKey(key);
fields.add(n);
for (Parameter p : indexParameters) {
if ("type".equals(p.getKey())) {
type = object2IndexType(p.getValue());
}
if ("unique".equals(p.getKey())) {
unique = (Boolean) p.getValue();
}
}
try {
if (elementClass.isAssignableFrom(Vertex.class)) {
getClient().createVertexIndex(simpleGraph, type, unique, fields);
} else if (elementClass.isAssignableFrom(Edge.class)) {
getClient().createEdgeIndex(simpleGraph, type, unique, fields);
}
} catch (ArangoDBException e) {
logger.warn("error while creating a vertex index", e);
}
}
private IndexType object2IndexType(Object obj) {
if (obj instanceof IndexType) {
return (IndexType) obj;
}
if (obj != null) {
String str = obj.toString();
for (IndexType indexType : IndexType.values()) {
if (indexType.toString().equalsIgnoreCase(str)) {
return indexType;
}
}
}
return IndexType.SKIPLIST;
}
@Override
public <T extends Element> Set<String> getIndexedKeys(Class<T> elementClass) {
HashSet<String> result = new HashSet<String>();
List<ArangoDBIndex> indices = null;
try {
if (elementClass.isAssignableFrom(Vertex.class)) {
indices = client.getVertexIndices(simpleGraph);
} else if (elementClass.isAssignableFrom(Edge.class)) {
indices = client.getEdgeIndices(simpleGraph);
}
for (ArangoDBIndex i : indices) {
if (i.getFields().size() == 1) {
addNotSystemKey(result, i);
}
}
} catch (ArangoDBException e) {
logger.warn("error while reading index keys", e);
}
return result;
}
private void addNotSystemKey(HashSet<String> result, ArangoDBIndex i) {
String key = i.getFields().get(0);
// ignore system index
if (key.charAt(0) != '_') {
result.add(ArangoDBUtil.denormalizeKey(key));
}
}
@Override
public GraphQuery query() {
return new ArangoDBGraphQuery(this);
}
/**
* Returns the ArangoDBSimpleGraphClient object
*
* @return the ArangoDBSimpleGraphClient object
*/
public ArangoDBSimpleGraphClient getClient() {
return client;
}
/**
* Returns the identifier of the graph
*
* @return the identifier of the graph
*/
public String getId() {
return simpleGraph.getGraphEntity().getDocumentKey();
}
}