Skip to content

Commit ca14407

Browse files
committed
jnosql-arangodb: implement GraphDatabaseManager
1 parent 1edef80 commit ca14407

File tree

13 files changed

+427
-104
lines changed

13 files changed

+427
-104
lines changed

jnosql-arangodb/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@
4141
<groupId>org.eclipse.jnosql.mapping</groupId>
4242
<artifactId>jnosql-mapping-key-value</artifactId>
4343
</dependency>
44+
<dependency>
45+
<groupId>org.eclipse.jnosql.mapping</groupId>
46+
<artifactId>jnosql-mapping-graph</artifactId>
47+
<version>${project.version}</version>
48+
</dependency>
4449
<dependency>
4550
<groupId>${project.groupId}</groupId>
4651
<artifactId>jnosql-database-commons</artifactId>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
*
3+
* Copyright (c) 2025 Contributors to the Eclipse Foundation
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License v1.0
6+
* and Apache License v2.0 which accompanies this distribution.
7+
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
8+
* and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
9+
*
10+
* You may elect to redistribute this code under either of these licenses.
11+
*
12+
* Contributors:
13+
*
14+
* Otavio Santana
15+
*
16+
*/
17+
package org.eclipse.jnosql.databases.arangodb.communication;
18+
19+
import org.eclipse.jnosql.communication.graph.CommunicationEdge;
20+
import org.eclipse.jnosql.communication.semistructured.CommunicationEntity;
21+
22+
import java.util.Map;
23+
24+
record ArangoDBCommunicationEdge(String id, CommunicationEntity source, CommunicationEntity target, String label, Map<String, Object> properties) implements CommunicationEdge {
25+
}

jnosql-arangodb/src/main/java/org/eclipse/jnosql/databases/arangodb/communication/ArangoDBDocumentManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
package org.eclipse.jnosql.databases.arangodb.communication;
1616

17+
import org.eclipse.jnosql.communication.graph.GraphDatabaseManager;
1718
import org.eclipse.jnosql.communication.semistructured.CommunicationEntity;
1819
import org.eclipse.jnosql.communication.semistructured.DatabaseManager;
1920

@@ -23,7 +24,7 @@
2324
* The ArangoDB implementation of {@link DatabaseManager}. This implementation does not support TTL methods in the context of
2425
* {@link DatabaseManager#insert(org.eclipse.jnosql.communication.semistructured.CommunicationEntity)}.
2526
*/
26-
public interface ArangoDBDocumentManager extends DatabaseManager, ArangoDBAccessor {
27+
public interface ArangoDBDocumentManager extends DatabaseManager, GraphDatabaseManager, ArangoDBAccessor {
2728

2829
/**
2930
* Executes an ArangoDB query using the ArangoDB Query Language (AQL).

jnosql-arangodb/src/main/java/org/eclipse/jnosql/databases/arangodb/communication/ArangoDBUtil.java

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
package org.eclipse.jnosql.databases.arangodb.communication;
1717

1818

19+
import com.arangodb.ArangoCollection;
1920
import com.arangodb.ArangoDB;
20-
import com.arangodb.entity.CollectionEntity;
21+
import com.arangodb.entity.CollectionType;
22+
import com.arangodb.model.CollectionCreateOptions;
2123
import jakarta.json.Json;
2224
import jakarta.json.JsonArray;
2325
import jakarta.json.JsonArrayBuilder;
@@ -67,14 +69,21 @@ static void checkDatabase(String database, ArangoDB arangoDB) {
6769
}
6870
}
6971

70-
public static void checkCollection(String bucketName, ArangoDB arangoDB, String namespace) {
71-
checkDatabase(bucketName, arangoDB);
72-
List<String> collections = arangoDB.db(bucketName)
73-
.getCollections().stream()
74-
.map(CollectionEntity::getName)
75-
.toList();
76-
if (!collections.contains(namespace)) {
77-
arangoDB.db(bucketName).createCollection(namespace);
72+
public static void checkCollection(String dbName, ArangoDB arangoDB, String collectionName) {
73+
checkDatabase(dbName, arangoDB);
74+
ArangoCollection collection = arangoDB.db(dbName).collection(collectionName);
75+
if (!collection.exists()) {
76+
collection.create();
77+
}
78+
}
79+
80+
public static void checkEdgeCollection(String dbName, ArangoDB arangoDB, String collectionName) {
81+
checkDatabase(dbName, arangoDB);
82+
ArangoCollection collection = arangoDB.db(dbName).collection(collectionName);
83+
if (!collection.exists()) {
84+
collection.create(new CollectionCreateOptions().type(CollectionType.EDGES));
85+
} else if (collection.getInfo().getType() != CollectionType.EDGES) {
86+
throw new IllegalStateException(String.format("The collection %s is not an edge collection", collectionName));
7887
}
7988
}
8089

jnosql-arangodb/src/main/java/org/eclipse/jnosql/databases/arangodb/communication/DefaultArangoDBDocumentManager.java

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,25 @@
1111
* Contributors:
1212
*
1313
* Otavio Santana
14+
* Michele Rastelli
1415
*/
1516
package org.eclipse.jnosql.databases.arangodb.communication;
1617

1718
import com.arangodb.ArangoCursor;
1819
import com.arangodb.ArangoDB;
19-
import com.arangodb.entity.BaseDocument;
20+
import com.arangodb.ArangoDatabase;
2021
import com.arangodb.entity.DocumentCreateEntity;
2122
import com.arangodb.entity.DocumentUpdateEntity;
2223
import jakarta.json.JsonObject;
24+
import org.eclipse.jnosql.communication.graph.CommunicationEdge;
2325
import org.eclipse.jnosql.communication.semistructured.CommunicationEntity;
2426
import org.eclipse.jnosql.communication.semistructured.DeleteQuery;
2527
import org.eclipse.jnosql.communication.semistructured.Element;
2628
import org.eclipse.jnosql.communication.semistructured.SelectQuery;
2729

2830
import java.time.Duration;
2931
import java.util.Map;
32+
import java.util.Objects;
3033
import java.util.Optional;
3134
import java.util.logging.Level;
3235
import java.util.logging.Logger;
@@ -45,28 +48,27 @@ class DefaultArangoDBDocumentManager implements ArangoDBDocumentManager {
4548
public static final String KEY = "_key";
4649
public static final String ID = "_id";
4750
public static final String REV = "_rev";
51+
public static final String FROM = "_from";
52+
public static final String TO = "_to";
4853

49-
private final String database;
50-
51-
private final ArangoDB arangoDB;
54+
private final ArangoDatabase db;
5255

5356
DefaultArangoDBDocumentManager(String database, ArangoDB arangoDB) {
54-
this.database = database;
55-
this.arangoDB = arangoDB;
57+
db = arangoDB.db(database);
5658
}
5759

5860
@Override
5961
public String name() {
60-
return database;
62+
return db.name();
6163
}
6264

6365
@Override
64-
public CommunicationEntity insert(CommunicationEntity entity) {
66+
public CommunicationEntity insert(CommunicationEntity entity) {
6567
requireNonNull(entity, "entity is required");
6668
String collectionName = entity.name();
6769
checkCollection(collectionName);
6870
JsonObject jsonObject = ArangoDBUtil.toJsonObject(entity);
69-
DocumentCreateEntity<Void> arangoDocument = arangoDB.db(database)
71+
DocumentCreateEntity<Void> arangoDocument = db
7072
.collection(collectionName).insertDocument(jsonObject);
7173
updateEntity(entity, arangoDocument.getKey(), arangoDocument.getId(), arangoDocument.getRev());
7274
return entity;
@@ -77,22 +79,10 @@ public CommunicationEntity update(CommunicationEntity entity) {
7779
requireNonNull(entity, "entity is required");
7880
String collectionName = entity.name();
7981
checkCollection(collectionName);
80-
Optional<String> keyElement = entity.find(KEY, String.class);
81-
Optional<String> idElement = entity.find(ID, String.class);
82-
if (keyElement.isEmpty() && idElement.isEmpty()) {
83-
throw new IllegalArgumentException("To update an entity is necessary to have either " + KEY + " or " + ID);
84-
}
85-
var key = keyElement.orElseGet(() -> {
86-
String id = idElement.orElseThrow();
87-
var elements = id.split("/");
88-
if (elements.length == 2) {
89-
return elements[1];
90-
} else {
91-
return elements[0];
92-
}
93-
});
9482
JsonObject jsonObject = ArangoDBUtil.toJsonObject(entity);
95-
DocumentUpdateEntity<Void> arangoDocument = arangoDB.db(database)
83+
String key = extractKey(entity).orElseThrow(() ->
84+
new IllegalArgumentException("To update an entity is necessary to have either " + KEY + " or " + ID));
85+
DocumentUpdateEntity<Void> arangoDocument = db
9686
.collection(collectionName).updateDocument(key, jsonObject);
9787
updateEntity(entity, arangoDocument.getKey(), arangoDocument.getId(), arangoDocument.getRev());
9888
return entity;
@@ -114,13 +104,12 @@ public void delete(DeleteQuery query) {
114104
checkCollection(query.name());
115105
if (query.condition().isEmpty()) {
116106
AQLQueryResult delete = QueryAQLConverter.delete(query);
117-
arangoDB.db(database).query(delete.query(), BaseDocument.class);
107+
db.query(delete.query(), Void.class);
118108
return;
119109
}
120110

121111
AQLQueryResult delete = QueryAQLConverter.delete(query);
122-
arangoDB.db(database).query(delete.query(), BaseDocument.class, delete.values(),
123-
null);
112+
db.query(delete.query(), Void.class, delete.values(), null);
124113
} catch (com.arangodb.ArangoDBException exception) {
125114
if (ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.equals(exception.getErrorNum())) {
126115
LOGGER.log(Level.FINEST, exception, () -> "An error to run query, that is related to delete " +
@@ -137,7 +126,7 @@ public Stream<CommunicationEntity> select(SelectQuery query) throws NullPointerE
137126
checkCollection(query.name());
138127
AQLQueryResult result = QueryAQLConverter.select(query);
139128
LOGGER.finest("Executing AQL: " + result.query());
140-
ArangoCursor<JsonObject> documents = arangoDB.db(database).query(result.query(),
129+
ArangoCursor<JsonObject> documents = db.query(result.query(),
141130
JsonObject.class,
142131
result.values(), null);
143132

@@ -149,7 +138,7 @@ public Stream<CommunicationEntity> select(SelectQuery query) throws NullPointerE
149138
public long count(String documentCollection) {
150139
requireNonNull(documentCollection, "document collection is required");
151140
String aql = "RETURN LENGTH(" + documentCollection + ")";
152-
ArangoCursor<Object> query = arangoDB.db(database).query(aql, Object.class, emptyMap(), null);
141+
ArangoCursor<Object> query = db.query(aql, Object.class, emptyMap(), null);
153142
return StreamSupport.stream(query.spliterator(), false).findFirst().map(Number.class::cast)
154143
.map(Number::longValue).orElse(0L);
155144
}
@@ -159,7 +148,7 @@ public long count(String documentCollection) {
159148
public Stream<CommunicationEntity> aql(String query, Map<String, Object> params) throws NullPointerException {
160149
requireNonNull(query, "query is required");
161150
requireNonNull(params, "values is required");
162-
ArangoCursor<JsonObject> result = arangoDB.db(database).query(query, JsonObject.class, params, null);
151+
ArangoCursor<JsonObject> result = db.query(query, JsonObject.class, params, null);
163152
return StreamSupport.stream(result.spliterator(), false)
164153
.map(ArangoDBUtil::toEntity);
165154
}
@@ -169,27 +158,30 @@ public <T> Stream<T> aql(String query, Map<String, Object> params, Class<T> type
169158
requireNonNull(query, "query is required");
170159
requireNonNull(params, "values is required");
171160
requireNonNull(type, "typeClass is required");
172-
ArangoCursor<T> result = arangoDB.db(database).query(query, type, params, null);
161+
ArangoCursor<T> result = db.query(query, type, params, null);
173162
return StreamSupport.stream(result.spliterator(), false);
174163
}
175164

176165
@Override
177166
public <T> Stream<T> aql(String query, Class<T> type) {
178167
requireNonNull(query, "query is required");
179168
requireNonNull(type, "typeClass is required");
180-
ArangoCursor<T> result = arangoDB.db(database).query(query, type, emptyMap(), null);
181-
return StreamSupport.stream(result.spliterator(), false);
169+
return db.query(query, type, emptyMap(), null).stream();
182170
}
183171

184172

185173
@Override
186174
public void close() {
187-
arangoDB.shutdown();
175+
db.arango().shutdown();
188176
}
189177

190178

191179
private void checkCollection(String collectionName) {
192-
ArangoDBUtil.checkCollection(database, arangoDB, collectionName);
180+
ArangoDBUtil.checkCollection(db.name(), db.arango(), collectionName);
181+
}
182+
183+
private void checkEdgeCollection(String collectionName) {
184+
ArangoDBUtil.checkEdgeCollection(db.name(), db.arango(), collectionName);
193185
}
194186

195187
@Override
@@ -216,7 +208,7 @@ public Iterable<CommunicationEntity> insert(Iterable<CommunicationEntity> entiti
216208

217209
@Override
218210
public ArangoDB getArangoDB() {
219-
return arangoDB;
211+
return db.arango();
220212
}
221213

222214
private void updateEntity(CommunicationEntity entity, String key, String id, String rev) {
@@ -225,4 +217,72 @@ private void updateEntity(CommunicationEntity entity, String key, String id, Str
225217
entity.add(Element.of(REV, rev));
226218
}
227219

220+
@Override
221+
public CommunicationEdge edge(CommunicationEntity source, String label, CommunicationEntity target, Map<String, Object> properties) {
222+
requireNonNull(source, "Source entity is required");
223+
requireNonNull(target, "Target entity is required");
224+
requireNonNull(label, "Relationship type is required");
225+
requireNonNull(properties, "Properties map is required");
226+
227+
checkCollection(source.name());
228+
checkCollection(target.name());
229+
checkEdgeCollection(label);
230+
231+
source = ensureEntityExists(source);
232+
target = ensureEntityExists(target);
233+
234+
CommunicationEntity entity = CommunicationEntity.of(label);
235+
entity.add(FROM, extractId(source));
236+
entity.add(TO, extractId(target));
237+
properties.forEach(entity::add);
238+
239+
JsonObject jsonObject = ArangoDBUtil.toJsonObject(entity);
240+
String key = db.collection(label).insertDocument(jsonObject).getKey();
241+
return new ArangoDBCommunicationEdge(key, source, target, label, properties);
242+
}
243+
244+
private CommunicationEntity ensureEntityExists(CommunicationEntity entity) {
245+
return extractKey(entity)
246+
.filter(key -> db.collection(entity.name()).documentExists(key))
247+
.map(id -> entity)
248+
.orElseGet(() -> insert(entity));
249+
}
250+
251+
@Override
252+
public void remove(CommunicationEntity source, String label, CommunicationEntity target) {
253+
throw new UnsupportedOperationException("TODO");
254+
}
255+
256+
@Override
257+
public <K> void deleteEdge(K id) {
258+
throw new UnsupportedOperationException("TODO");
259+
}
260+
261+
@Override
262+
public <K> Optional<CommunicationEdge> findEdgeById(K id) {
263+
throw new UnsupportedOperationException("TODO");
264+
}
265+
266+
private Optional<String> extractId(CommunicationEntity entity) {
267+
Objects.requireNonNull(entity, "entity is required");
268+
Objects.requireNonNull(entity.name(), "entity name is required");
269+
if (entity.name().isEmpty()) {
270+
throw new IllegalArgumentException("entity name cannot be empty");
271+
}
272+
return extractKey(entity).map(key -> entity.name() + "/" + key);
273+
}
274+
275+
private Optional<String> extractKey(CommunicationEntity entity) {
276+
return entity.find(KEY, String.class).or(() ->
277+
entity.find(ID, String.class)
278+
.map(id -> {
279+
var elements = id.split("/");
280+
if (elements.length == 2) {
281+
return elements[1];
282+
} else {
283+
return elements[0];
284+
}
285+
}));
286+
}
287+
228288
}

jnosql-arangodb/src/main/java/org/eclipse/jnosql/databases/arangodb/mapping/ArangoDBTemplate.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818

1919
import org.eclipse.jnosql.mapping.document.DocumentTemplate;
20+
import org.eclipse.jnosql.mapping.graph.GraphTemplate;
2021

2122
import java.util.Map;
2223
import java.util.stream.Stream;
@@ -27,7 +28,7 @@
2728
* This template allows executing AQL queries with named parameters and supports
2829
* result serialization either through Eclipse JNoSQL or directly via ArangoDB.
2930
*/
30-
public interface ArangoDBTemplate extends DocumentTemplate {
31+
public interface ArangoDBTemplate extends DocumentTemplate, GraphTemplate {
3132

3233
/**
3334
* Executes an ArangoDB query using the ArangoDB Query Language (AQL).

0 commit comments

Comments
 (0)