diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 5b70e462c..f1fbcc2ce 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -11,6 +11,7 @@ and this project adheres to https://semver.org/spec/v2.0.0.html[Semantic Version === Added - Include between query support at MongoDB +- Include Graph as Apache TinkerPop === Changed diff --git a/README.adoc b/README.adoc index b473b99bf..26ff8498f 100644 --- a/README.adoc +++ b/README.adoc @@ -1701,6 +1701,118 @@ SolrTemplate template; List people = template.solr("age:@age AND type:@type AND _entity:@entity", params); ---- + +=== Graph (Apache Tinkerpop) + +Currently, the Jakarta NoSQL doesn't define an API for Graph database types but Eclipse JNoSQL provides a Graph template to explore the specific behavior of this NoSQL type. + +Eclipse JNoSQL offers a mapping implementation for Graph NoSQL types: + +[source,xml] +---- + + org.eclipse.jnosql.mapping + jnosql-mapping-graph + 1.1.1 + +---- + +Despite the other three NoSQL types, Eclipse JNoSQL API does not offer a communication layer for Graph NoSQL types. Instead, it integrates with https://tinkerpop.apache.org/[Apache Tinkerpop 3.x]. + +[source,java] +---- +@Inject +GraphTemplate template; +... + +Category java = Category.of("Java"); +Book effectiveJava = Book.of("Effective Java"); + +template.insert(java); +template.insert(effectiveJava); +EdgeEntity edge = template.edge(java, "is", software); + +Stream books = template.getTraversalVertex() + .hasLabel("Category") + .has("name", "Java") + .in("is") + .hasLabel("Book") + .getResult(); +---- + +Apache TinkerPop is database agnostic. Thus, you can change the database in your application with no or minimal impact on source code. + +You can define the database settings using the https://microprofile.io/microprofile-config/[MicroProfile Config] specification, so you can add properties and overwrite it in the environment following the https://12factor.net/config[Twelve-Factor App]. + +[source,properties] +---- +jnosql.graph.provider= +jnosql.provider.host= +jnosql.provider.user= +jnosql.provider.password= +---- + +TIP: The ```jnosql.graph.provider``` property is necessary when you have more than one driver in the classpath. Otherwise, it will take the first one. + +These configuration settings are the default behavior. Nevertheless, there is an option to programmatically configure these settings. Create a class that implements the ```Supplier```, then define it using the ```@Alternative``` and ```@Priority``` annotations. + +[source,java] +---- +@Alternative +@Priority(Interceptor.Priority.APPLICATION) +public class ManagerSupplier implements Supplier { + + @Produces + public Graph get() { + Graph graph = ...; // from a provider + return graph; + } +} +---- + +You can work with several document database instances through CDI qualifier. To identify each database instance, make a `Graph` visible for CDI by putting the ```@Produces``` and the ```@Database``` annotations in the method. + +[source,java] +---- +@Inject +@Database(value = DatabaseType.GRAPH, provider = "databaseA") +private GraphTemplate templateA; + +@Inject +@Database(value = DatabaseType.GRAPH, provider = "databaseB") +private GraphTemplate templateB; + +// producers methods +@Produces +@Database(value = DatabaseType.GRAPH, provider = "databaseA") +public Graph getManagerA() { + return manager; +} + +@Produces +@Database(value = DatabaseType.GRAPH, provider = "databaseB") +public Graph getManagerB() { + return manager; +} +---- + + +Eclipse JNoSQL does not provide https://mvnrepository.com/artifact/org.apache.tinkerpop/gremlin-core[Apache Tinkerpop 3 dependency]; check if the provider does. Otherwise, do it manually. + +[source,xml] +---- + + org.apache.tinkerpop + jnosql-gremlin-core + ${tinkerpop.version} + + + org.apache.tinkerpop + jnosql-gremlin-groovy + ${tinkerpop.version} + +---- + == Getting Help Having trouble with Eclipse JNoSQL databases? We’d love to help! diff --git a/jnosql-tinkerpop/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 b/jnosql-tinkerpop/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 new file mode 100644 index 000000000..2cdb92cb8 --- /dev/null +++ b/jnosql-tinkerpop/antlr4/org/eclipse/jnosql/query/grammar/data/JDQL.g4 @@ -0,0 +1,160 @@ +grammar JDQL; + +statement : select_statement | update_statement | delete_statement; + +select_statement : select_clause? from_clause? where_clause? orderby_clause?; +update_statement : UPDATE entity_name set_clause where_clause?; +delete_statement : DELETE from_clause where_clause?; + +from_clause : FROM entity_name; + +where_clause : WHERE conditional_expression; + +set_clause : SET update_item (COMMA update_item)*; +update_item : state_field_path_expression EQ (scalar_expression | NULL); + +select_clause : SELECT select_list; +select_list + : state_field_path_expression (COMMA state_field_path_expression)* + | aggregate_expression + ; +aggregate_expression : COUNT '(' THIS ')'; + +orderby_clause : ORDER BY orderby_item (COMMA orderby_item)*; +orderby_item : state_field_path_expression (ASC | DESC)?; + +conditional_expression + // highest to lowest precedence + : LPAREN conditional_expression RPAREN + | null_comparison_expression + | in_expression + | between_expression + | like_expression + | comparison_expression + | NOT conditional_expression + | conditional_expression AND conditional_expression + | conditional_expression OR conditional_expression + ; + +comparison_expression : scalar_expression comparison_operator scalar_expression; +comparison_operator : EQ | GT | GTEQ | LT | LTEQ | NEQ; + +between_expression : scalar_expression NOT? BETWEEN scalar_expression AND scalar_expression; +like_expression : scalar_expression NOT? LIKE (STRING | input_parameter); + +in_expression : state_field_path_expression NOT? IN '(' in_item (',' in_item)* ')'; +in_item : literal | enum_literal | input_parameter; // could simplify to just literal + +null_comparison_expression : state_field_path_expression IS NOT? NULL; + +scalar_expression + // highest to lowest precedence + : LPAREN scalar_expression RPAREN + | primary_expression + | scalar_expression MUL scalar_expression + | scalar_expression DIV scalar_expression + | scalar_expression PLUS scalar_expression + | scalar_expression MINUS scalar_expression + | scalar_expression CONCAT scalar_expression + ; + +primary_expression + : function_expression + | special_expression + | state_field_path_expression + | enum_literal + | input_parameter + | literal + ; + +function_expression + : ('abs(' | 'ABS(') scalar_expression ')' + | ('length(' | 'LENGTH(') scalar_expression ')' + | ('lower(' | 'LOWER(') scalar_expression ')' + | ('upper(' | 'UPPER(') scalar_expression ')' + | ('left(' | 'LEFT(') scalar_expression ',' scalar_expression ')' + | ('right(' | 'RIGHT(') scalar_expression ',' scalar_expression ')' + ; + +special_expression + : LOCAL_DATE + | LOCAL_DATETIME + | LOCAL_TIME + | TRUE + | FALSE + ; + +state_field_path_expression : IDENTIFIER (DOT IDENTIFIER)* | FULLY_QUALIFIED_IDENTIFIER; + +entity_name : IDENTIFIER; // no ambiguity + +enum_literal : IDENTIFIER (DOT IDENTIFIER)* | FULLY_QUALIFIED_IDENTIFIER; // ambiguity with state_field_path_expression resolvable semantically + +input_parameter : COLON IDENTIFIER | QUESTION INTEGER; + +literal : STRING | INTEGER | DOUBLE; + +// Tokens defined to be case-insensitive using character classes +SELECT : [sS][eE][lL][eE][cC][tT]; +UPDATE : [uU][pP][dD][aA][tT][eE]; +DELETE : [dD][eE][lL][eE][tT][eE]; +FROM : [fF][rR][oO][mM]; +WHERE : [wW][hH][eE][rR][eE]; +SET : [sS][eE][tT]; +ORDER : [oO][rR][dD][eE][rR]; +BY : [bB][yY]; +NOT : [nN][oO][tT]; +IN : [iI][nN]; +IS : [iI][sS]; +NULL : [nN][uU][lL][lL]; +COUNT : [cC][oO][uU][nN][tT]; +TRUE : [tT][rR][uU][eE]; +FALSE : [fF][aA][lL][sS][eE]; +ASC : [aA][sS][cC]; +DESC : [dD][eE][sS][cC]; +AND : [aA][nN][dD]; +OR : [oO][rR]; +LOCAL_DATE : [lL][oO][cC][aA][lL] [dD][aA][tT][eE]; +LOCAL_DATETIME : [lL][oO][cC][aA][lL] [dD][aA][tT][eE][tT][iI][mM][eE]; +LOCAL_TIME : [lL][oO][cC][aA][lL] [tT][iI][mM][eE]; +BETWEEN : [bB][eE][tT][wW][eE][eE][nN]; +LIKE : [lL][iI][kK][eE]; +THIS : [tT][hH][iI][sS]; +LOCAL : [lL][oO][cC][aA][lL]; +DATE : [dD][aA][tT][eE]; +DATETIME : [dD][aA][tT][eE][tT][iI][mM][eE]; +TIME : [tT][iI][mM][eE]; + +// Operators +EQ : '='; +GT : '>'; +LT : '<'; +NEQ : '<>'; +GTEQ : '>='; +LTEQ : '<='; +PLUS : '+'; +MINUS : '-'; +MUL : '*'; +DIV : '/'; +CONCAT : '||'; + +// Special Characters +COMMA : ','; +DOT : '.'; +LPAREN : '('; +RPAREN : ')'; +COLON : ':'; +QUESTION : '?'; + +// Identifier and literals +FULLY_QUALIFIED_IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_]* (DOT [a-zA-Z_][a-zA-Z0-9_]*)+; +IDENTIFIER : [a-zA-Z_][a-zA-Z0-9_]*; +STRING : '\'' ( ~('\'' | '\\') | '\\' . | '\'\'' )* '\'' // single quoted strings with embedded single quotes handled + | '"' ( ~["\\] | '\\' . )* '"' ; // double quoted strings +INTEGER : '-'?[0-9]+; +DOUBLE : '-'?[0-9]+'.'[0-9]* | '-'?'.'[0-9]+; + +// Whitespace and Comments +WS : [ \t\r\n]+ -> skip ; +LINE_COMMENT : '//' ~[\r\n]* -> skip; +BLOCK_COMMENT : '/*' .*? '*/' -> skip; diff --git a/jnosql-tinkerpop/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 b/jnosql-tinkerpop/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 new file mode 100644 index 000000000..21f5c25a7 --- /dev/null +++ b/jnosql-tinkerpop/antlr4/org/eclipse/jnosql/query/grammar/method/Method.g4 @@ -0,0 +1,40 @@ +grammar Method; +select: selectStart where? order? EOF; +deleteBy: 'deleteBy' where? EOF; + +selectStart: 'find' limit 'By' | 'findBy' | 'countAll' | 'countBy' | 'existsBy'; +where: condition (and condition| or condition)* ; +condition: eq | gt | gte | lt | lte | between | in | like | truth | untruth | nullable | contains | endsWith | startsWith; +order: 'OrderBy' orderName (orderName)*; +orderName: variable | variable asc | variable desc; +limit: firstLimit | firstOne; +firstLimit : 'First' limitNumber; +firstOne: 'First'; +and: 'And'; +or: 'Or'; +asc: 'Asc'; +desc: 'Desc'; +truth: variable 'True'; +untruth: variable 'False'; +eq: variable | variable ignoreCase? not? 'Equals'?; +gt: variable ignoreCase? not? 'GreaterThan'; +gte: variable ignoreCase? not? 'GreaterThanEqual'; +lt: variable ignoreCase? not? 'LessThan'; +lte: variable ignoreCase? not? 'LessThanEqual'; +between: variable ignoreCase? not? 'Between'; +in: variable ignoreCase? not? 'In'; +like: variable ignoreCase? not? 'Like'; +contains: variable ignoreCase? not? 'Contains'; +endsWith: variable ignoreCase? not? 'EndsWith'; +startsWith: variable ignoreCase? not? 'StartsWith'; +nullable: variable ignoreCase? not? 'Null'; +ignoreCase: 'IgnoreCase'; +not: 'Not'; +variable: ANY_NAME; +limitNumber: INT; +ANY_NAME: [a-zA-Z_.][a-zA-Z_.0-9-]*; +WS: [ \t\r\n]+ -> skip ; +INT: [0-9]+; +fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ; +fragment UNICODE : 'u' HEX HEX HEX HEX ; +fragment HEX : [0-9a-fA-F] ; \ No newline at end of file diff --git a/jnosql-tinkerpop/pom.xml b/jnosql-tinkerpop/pom.xml new file mode 100644 index 000000000..dad72346c --- /dev/null +++ b/jnosql-tinkerpop/pom.xml @@ -0,0 +1,86 @@ + + + + + 4.0.0 + + org.eclipse.jnosql.mapping + jnosql-mapping-parent + 1.1.2-SNAPSHOT + + + jnosql-mapping-graph + + + 4.9.3 + + --add-opens java.base/java.lang=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED + + + + + + ${project.groupId} + jnosql-mapping-semistructured + ${project.version} + + + org.antlr + antlr4-runtime + + + + + org.apache.tinkerpop + gremlin-core + ${tinkerpop.version} + provided + + + org.apache.tinkerpop + neo4j-gremlin + ${tinkerpop.version} + test + + + org.neo4j + neo4j-tinkerpop-api-impl + ${neo4j.connector.version} + test + + + + + + + org.antlr + antlr4-maven-plugin + ${antlr4.version} + + antlr4 + + + + + antlr4 + + + + + + + diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/CommunicationEntityConverter.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/CommunicationEntityConverter.java new file mode 100644 index 000000000..8f6a3893a --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/CommunicationEntityConverter.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.semistructured.CommunicationEntity; + +import java.util.function.Function; + +public enum CommunicationEntityConverter implements Function{ + INSTANCE; + + + @Override + public CommunicationEntity apply(Vertex vertex) { + var entity = CommunicationEntity.of(vertex.label()); + vertex.properties().forEachRemaining(p -> entity.add(p.key(), p.value())); + entity.add(DefaultGraphDatabaseManager.ID_PROPERTY, vertex.id()); + return entity; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java new file mode 100644 index 000000000..62e99d3fb --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import jakarta.data.exceptions.EmptyResultException; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.CommunicationException; +import org.eclipse.jnosql.communication.ValueUtil; +import org.eclipse.jnosql.communication.semistructured.CommunicationEntity; +import org.eclipse.jnosql.communication.semistructured.DeleteQuery; +import org.eclipse.jnosql.communication.semistructured.SelectQuery; + +import java.time.Duration; +import java.util.Iterator; +import java.util.Objects; +import java.util.stream.Stream; + +import static org.apache.tinkerpop.gremlin.process.traversal.Order.asc; +import static org.apache.tinkerpop.gremlin.process.traversal.Order.desc; + +/** + * Default implementation of {@link GraphDatabaseManager} that serves as an adapter to the TinkerPop + * graph database provided by the Apache TinkerPop framework. + *

+ * This implementation wraps a TinkerPop {@link Graph} instance and provides methods to interact with + * the underlying graph database, execute graph traversals, and perform other graph-related operations. + *

+ *

+ * Note that this implementation does not support certain operations such as insertions with a duration, + * as indicated by the UnsupportedOperationException thrown by those methods. + *

+ */ +public class DefaultGraphDatabaseManager implements GraphDatabaseManager { + + public static final String ID_PROPERTY = "_id"; + private final Graph graph; + + DefaultGraphDatabaseManager(Graph graph) { + this.graph = graph; + } + + @Override + public Graph get() { + return graph; + } + + @Override + public String name() { + return "The tinkerpop graph database manager"; + } + + @Override + public CommunicationEntity insert(CommunicationEntity entity) { + + Objects.requireNonNull(entity, "entity is required"); + Vertex vertex = graph.addVertex(entity.name()); + entity.elements().forEach(e -> vertex.property(e.name(), ValueUtil.convert(e.value()))); + entity.add(ID_PROPERTY, vertex.id()); + vertex.property(ID_PROPERTY, vertex.id()); + GraphTransactionUtil.transaction(graph); + return entity; + } + + @Override + public CommunicationEntity insert(CommunicationEntity entity, Duration duration) { + throw new UnsupportedOperationException("There is no support to insert with duration"); + } + + @Override + public Iterable insert(Iterable entities) { + Objects.requireNonNull(entities, "entities is required"); + entities.forEach(this::insert); + return entities; + } + + @Override + public Iterable insert(Iterable iterable, Duration duration) { + throw new UnsupportedOperationException("There is no support to insert with duration"); + } + + @Override + public CommunicationEntity update(CommunicationEntity entity) { + Objects.requireNonNull(entity, "entity is required"); + entity.find(ID_PROPERTY).ifPresent(id -> { + Iterator vertices = graph.vertices(id.get()); + if(!vertices.hasNext()) { + throw new EmptyResultException("The entity does not exist with the id: " + id); + } + Vertex vertex = vertices.next(); + entity.elements().forEach(e -> vertex.property(e.name(), ValueUtil.convert(e.value()))); + }); + GraphTransactionUtil.transaction(graph); + return entity; + } + + @Override + public Iterable update(Iterable entities) { + Objects.requireNonNull(entities, "entities is required"); + Stream.of(entities).forEach(this::update); + return entities; + } + + @Override + public void delete(DeleteQuery query) { + Objects.requireNonNull(query, "delete is required"); + GraphTraversal traversal = graph.traversal().V().hasLabel(query.name()); + query.condition().ifPresent(c ->{ + GraphTraversal predicate = TraversalExecutor.getPredicate(c); + traversal.filter(predicate); + }); + + traversal.drop().iterate(); + GraphTransactionUtil.transaction(graph); + } + + @Override + public Stream select(SelectQuery query) { + Objects.requireNonNull(query, "query is required"); + GraphTraversal traversal = graph.traversal().V().hasLabel(query.name()); + query.condition().ifPresent(c ->{ + GraphTraversal predicate = TraversalExecutor.getPredicate(c); + traversal.filter(predicate); + }); + + if(query.limit()> 0) { + traversal.limit(query.limit()); + } else if(query.skip() > 0) { + traversal.skip(query.skip()); + } + query.sorts().forEach( + s -> { + if (s.isAscending()) { + traversal.order().by(s.property(), asc); + } else { + traversal.order().by(s.property(), desc); + } + }); + return traversal.toStream().map(CommunicationEntityConverter.INSTANCE); + } + + @Override + public long count(String entity) { + Objects.requireNonNull(entity, "entity is required"); + GraphTraversal count = graph.traversal().V().hasLabel(entity).count(); + return count.next(); + } + + @Override + public void close() { + try { + graph.close(); + } catch (Exception e) { + throw new CommunicationException("There is an issue when close the Graph connection", e); + } + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphConfiguration.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphConfiguration.java new file mode 100644 index 000000000..db0e9cc20 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.communication.CommunicationException; +import org.eclipse.jnosql.communication.Settings; + +import java.util.ServiceLoader; +import java.util.function.Function; + +/** + * The Configuration that creates an instance of {@link Graph} that given a {@link Settings} make an {@link Graph} instance. + */ +public interface GraphConfiguration extends Function { + + + /** + * creates and returns a {@link GraphConfiguration} instance from {@link ServiceLoader} + * + * @param the configuration type + * @return {@link GraphConfiguration} instance + */ + static T getConfiguration() { + return (T) ServiceLoader.load(GraphConfiguration.class) + .stream() + .map(ServiceLoader.Provider::get) + .findFirst().orElseThrow(() -> new CommunicationException("It does not find GraphConfiguration")); + } + + /** + * creates and returns a {@link GraphConfiguration} instance from {@link ServiceLoader} + * for a particular provider implementation. + * + * @param the configuration type + * @param type the particular provider + * @return {@link GraphConfiguration} instance + */ + static T getConfiguration(Class type) { + return (T) ServiceLoader.load(GraphConfiguration.class) + .stream() + .map(ServiceLoader.Provider::get) + .filter(type::isInstance) + .findFirst().orElseThrow(() -> new CommunicationException("It does not find GraphConfiguration")); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphDatabaseManager.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphDatabaseManager.java new file mode 100644 index 000000000..ee9422390 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphDatabaseManager.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.communication.semistructured.DatabaseManager; + +import java.util.Objects; +import java.util.function.Supplier; + +/** + * A specialized extension of {@link DatabaseManager} that provides access to a graph database represented by the + * {@link org.apache.tinkerpop.gremlin.structure.Graph} interface from Apache TinkerPop. + *

+ * Implementations of this interface are expected to provide methods for interacting with the underlying graph + * database, such as retrieving vertices, edges, and properties, executing graph traversals, and performing other + * graph-related operations. + *

+ * In addition to the functionality inherited from {@link DatabaseManager}, implementations of this interface + * also act as suppliers of the underlying {@link org.apache.tinkerpop.gremlin.structure.Graph} instance. + *

+ */ +public interface GraphDatabaseManager extends DatabaseManager, Supplier { + + /** + * Creates a new instance of DefaultGraphDatabaseManager with the specified TinkerPop Graph. + * + * @param graph the TinkerPop Graph instance to be managed + * @return a new DefaultGraphDatabaseManager instance + * @throws NullPointerException if the graph parameter is null + */ + static GraphDatabaseManager of(Graph graph) { + Objects.requireNonNull(graph, "graph is required"); + return new DefaultGraphDatabaseManager(graph); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphTransactionUtil.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphTransactionUtil.java new file mode 100644 index 000000000..89de00f84 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/GraphTransactionUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.eclipse.jnosql.mapping.core.config.MicroProfileSettings; + +import java.util.Objects; +import java.util.logging.Logger; + +import static org.eclipse.jnosql.mapping.core.config.MappingConfigurations.GRAPH_TRANSACTION_AUTOMATIC; + +/** + * Utility class providing methods to manage transactions in a graph database. + * This class offers functionality to lock and unlock transactions, as well as automatic transaction management. + */ +public final class GraphTransactionUtil { + + private static final Logger LOGGER = Logger.getLogger(GraphTransactionUtil.class.getName()); + private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + private GraphTransactionUtil() { + } + + /** + * Locks the current transaction, preventing it from being committed. + * + * @param transaction the transaction to lock + */ + public static void lock(Transaction transaction) { + THREAD_LOCAL.set(transaction); + } + + /** + * Unlocks the current transaction. + * Allows the transaction to be committed. + */ + public static void unlock() { + THREAD_LOCAL.remove(); + } + + /** + * Automatically commits a transaction if enabled and not locked. + * + * @param graph the graph instance + */ + public synchronized static void transaction(Graph graph) { + if (isAutomatic() && isNotLock() && Objects.nonNull(graph)) { + try { + Transaction transaction = graph.tx(); + if (transaction != null) { + transaction.commit(); + } + } catch (Exception exception) { + LOGGER.info("Unable to do transaction automatically in the graph, reason: " + + exception.getMessage()); + } + + } + } + + /** + * Checks if automatic transaction management is enabled. + * + * @return true if automatic transaction management is enabled, false otherwise + */ + public static boolean isAutomatic() { + return MicroProfileSettings.INSTANCE.get(GRAPH_TRANSACTION_AUTOMATIC, String.class) + .map(Boolean::valueOf) + .orElse(true); + } + + /** + * Checks if the current transaction is not locked. + * + * @return true if the current transaction is not locked, false otherwise + */ + private static boolean isNotLock() { + return THREAD_LOCAL.get() == null; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/TraversalExecutor.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/TraversalExecutor.java new file mode 100644 index 000000000..461ab8835 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/TraversalExecutor.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.Condition; +import org.eclipse.jnosql.communication.TypeReference; +import org.eclipse.jnosql.communication.ValueUtil; +import org.eclipse.jnosql.communication.semistructured.CriteriaCondition; +import org.eclipse.jnosql.communication.semistructured.Element; + +import java.util.List; + +final class TraversalExecutor { + + private TraversalExecutor() { + } + + static GraphTraversal getPredicate(CriteriaCondition condition) { + Condition operator = condition.condition(); + Element element = condition.element(); + String name = element.name(); + var value = ValueUtil.convert(element.value()); + + switch (operator) { + case EQUALS -> { + return __.has(name, P.eq(value)); + } + case GREATER_THAN -> { + return __.has(name, P.gt(value)); + } + case GREATER_EQUALS_THAN -> { + return __.has(name, P.gte(value)); + } + case LESSER_THAN -> { + return __.has(name, P.lt(value)); + } + case LESSER_EQUALS_THAN -> { + return __.has(name, P.lte(value)); + } + case BETWEEN -> { + List values = ValueUtil.convertToList(element.value()); + if(values.size() == 2) { + return __.has(name, P.between(values.get(0), values.get(1))); + } + throw new IllegalStateException("The between condition requires two parameters"); + } + case IN -> { + return __.has(name, P.within(ValueUtil.convertToList(element.value()))); + } + case NOT -> { + var notCondition = element.value().get(CriteriaCondition.class); + return __.not(getPredicate(notCondition)); + } + case AND -> { + return condition.element().value().get(new TypeReference>() { + }).stream().map(TraversalExecutor::getPredicate) + .reduce(GraphTraversal::and) + .orElseThrow(() -> new UnsupportedOperationException("There is an inconsistency at the AND operator")); + } + case OR -> { + return condition.element().value().get(new TypeReference>() { + }).stream().map(TraversalExecutor::getPredicate) + .reduce(GraphTraversal::or) + .orElseThrow(() -> new UnsupportedOperationException("There is an inconsistency at the OR operator")); + } + default -> + throw new UnsupportedOperationException("There is not support to the type " + operator + " in graph"); + } + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/package-info.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/package-info.java new file mode 100644 index 000000000..11ea3780b --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/communication/graph/package-info.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ + +/** + * This package provides communication facilities and abstractions for interacting with graph databases + * in the JNoSQL project. It contains interfaces, classes, and utilities that enable developers to + * communicate with and manage graph databases. + *

+ * The core interface in this package is {@link org.eclipse.jnosql.communication.graph.GraphDatabaseManager}, + * which extends {@link org.eclipse.jnosql.communication.semistructured.DatabaseManager} + * and acts as a specialized extension for managing graph databases. Implementations of this interface + * provide methods for interacting with the underlying graph database, executing graph traversals, + * and performing other graph-related operations. + *

+ * Additionally, this package may contain specific implementations, adapters, or utilities tailored + * for working with different graph database technologies supported by JNoSQL. + *

+ */ +package org.eclipse.jnosql.communication.graph; \ No newline at end of file diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractEdgeTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractEdgeTraversal.java new file mode 100644 index 000000000..36930fc7f --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractEdgeTraversal.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * The base class to {@link EdgeTraversal} + */ +abstract class AbstractEdgeTraversal { + + protected final Supplier> supplier; + protected final Function, GraphTraversal> flow; + protected final EntityConverter converter; + + AbstractEdgeTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + this.supplier = supplier; + this.flow = flow; + this.converter = converter; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java new file mode 100644 index 000000000..6b2ac1a6e --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.EmptyResultException; +import jakarta.data.exceptions.NonUniqueResultException; +import org.eclipse.jnosql.mapping.PreparedStatement; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +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.Transaction; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.graph.CommunicationEntityConverter; +import org.eclipse.jnosql.communication.graph.GraphDatabaseManager; +import org.eclipse.jnosql.communication.graph.GraphTransactionUtil; +import org.eclipse.jnosql.mapping.IdNotFoundException; +import org.eclipse.jnosql.mapping.metadata.EntityMetadata; +import org.eclipse.jnosql.mapping.metadata.FieldMetadata; +import org.eclipse.jnosql.mapping.semistructured.AbstractSemiStructuredTemplate; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static java.util.Objects.isNull; +import static java.util.Objects.requireNonNull; +import static org.apache.tinkerpop.gremlin.structure.T.id; + +abstract class AbstractGraphTemplate extends AbstractSemiStructuredTemplate implements GraphTemplate { + + private static final Function, GraphTraversal> INITIAL_VERTEX = + g -> (GraphTraversal) g; + + private static final Function, GraphTraversal> INITIAL_EDGE = + g -> (GraphTraversal) g; + + + /** + * Retrieves the {@link GraphDatabaseManager} associated with this graph template. + * + * @return the {@link GraphDatabaseManager} associated with this graph template + */ + protected abstract GraphDatabaseManager manager(); + + /** + * Retrieves the {@link GraphTraversalSource} associated with this graph template. + * + * @return the {@link GraphTraversalSource} associated with this graph template + */ + protected abstract GraphTraversalSource traversal(); + + /** + * Retrieves the {@link Graph} associated with this graph template. + * + * @return the {@link Graph} associated with this graph template + */ + protected abstract Graph graph(); + + private GremlinExecutor gremlinExecutor; + + + @Override + public Optional find(K idValue) { + requireNonNull(idValue, "id is required"); + Optional vertex = traversal().V(idValue).tryNext(); + return vertex.map(v -> converter().toEntity(CommunicationEntityConverter.INSTANCE.apply(v))); + } + + @Override + public void delete(T id) { + requireNonNull(id, "id is required"); + traversal().V(id).toStream().forEach(Vertex::remove); + } + + @Override + public void deleteEdge(T id) { + requireNonNull(id, "id is required"); + traversal().E(id).toStream().forEach(Edge::remove); + } + + @Override + public void deleteEdge(Iterable ids) { + requireNonNull(ids, "ids is required"); + final Object[] edgeIds = StreamSupport.stream(ids.spliterator(), false).toArray(Object[]::new); + traversal().E(edgeIds).toStream().forEach(Edge::remove); + } + + @Override + public EdgeEntity edge(O outgoing, String label, I incoming) { + requireNonNull(incoming, "incoming is required"); + requireNonNull(label, "label is required"); + requireNonNull(outgoing, "outgoing is required"); + + checkId(outgoing); + checkId(incoming); + + if (isIdNull(outgoing)) { + throw new IllegalStateException("outgoing Id field is required"); + } + + if (isIdNull(incoming)) { + throw new IllegalStateException("incoming Id field is required"); + } + + Vertex outVertex = vertex(outgoing).orElseThrow(() -> new EmptyResultException("Outgoing entity does not found")); + Vertex inVertex = vertex(incoming).orElseThrow(() -> new EmptyResultException("Incoming entity does not found")); + + final Predicate> predicate = t -> { + Edge e = t.get(); + return e.inVertex().id().equals(inVertex.id()) + && e.outVertex().id().equals(outVertex.id()); + }; + + Optional edge = traversal().V(outVertex.id()) + .out(label).has(id, inVertex.id()).inE(label).filter(predicate).tryNext(); + + return edge.map(edge1 -> new DefaultEdgeEntity<>(edge1, incoming, outgoing)) + .orElseGet(() -> new DefaultEdgeEntity<>(getEdge(label, outVertex, inVertex), incoming, outgoing)); + } + + @Override + public Collection edgesById(K id, Direction direction, String... labels) { + requireNonNull(id, "id is required"); + requireNonNull(direction, "direction is required"); + + Iterator vertices = vertices(id); + if (vertices.hasNext()) { + List edges = new ArrayList<>(); + vertices.next().edges(direction, labels).forEachRemaining(edges::add); + return edges.stream().map(e ->EdgeEntity.of(converter(), e)).toList(); + } + return Collections.emptyList(); + } + + @SafeVarargs + @Override + public final Collection edgesById(K id, Direction direction, Supplier... labels) { + checkLabelsSupplier(labels); + return edgesByIdImpl(id, direction, Stream.of(labels).map(Supplier::get).toArray(String[]::new)); + } + + @Override + public Collection edgesById(K id, Direction direction) { + return edgesByIdImpl(id, direction); + } + + @Override + public Collection edges(T entity, Direction direction, String... labels) { + return edgesImpl(entity, direction, labels); + } + + @SafeVarargs + @Override + public final Collection edges(T entity, Direction direction, Supplier... labels) { + checkLabelsSupplier(labels); + return edgesImpl(entity, direction, Stream.of(labels).map(Supplier::get).toArray(String[]::new)); + } + + @Override + public Collection edges(T entity, Direction direction) { + return edgesImpl(entity, direction); + } + + @Override + public Optional edge(E edgeId) { + requireNonNull(edgeId, "edgeId is required"); + + Optional edgeOptional = traversal().E(edgeId).tryNext(); + + if (edgeOptional.isPresent()) { + Edge edge = edgeOptional.get(); + return Optional.of(EdgeEntity.of(converter(), edge)); + } + + return Optional.empty(); + } + + @Override + public VertexTraversal traversalVertex(Object... vertexIds) { + if (Stream.of(vertexIds).anyMatch(Objects::isNull)) { + throw new IllegalStateException("No one vertexId element cannot be null"); + } + return new DefaultVertexTraversal(() -> traversal().V(vertexIds), INITIAL_VERTEX, converter()); + } + + @Override + public EdgeTraversal traversalEdge(Object... edgeIds) { + if (Stream.of(edgeIds).anyMatch(Objects::isNull)) { + throw new IllegalStateException("No one edgeId element cannot be null"); + } + return new DefaultEdgeTraversal(() -> traversal().E(edgeIds), INITIAL_EDGE, converter()); + } + + @Override + public Transaction transaction() { + return graph().tx(); + } + + @Override + public Stream gremlin(String gremlin) { + requireNonNull(gremlin, "gremlin is required"); + return executor().executeGremlin(traversal(), gremlin); + } + + @Override + public Optional gremlinSingleResult(String gremlin) { + Stream entities = gremlin(gremlin); + final Iterator iterator = entities.iterator(); + if (!iterator.hasNext()) { + return Optional.empty(); + } + final T entity = iterator.next(); + if (!iterator.hasNext()) { + return Optional.ofNullable(entity); + } + throw new NonUniqueResultException("The gremlin query returns more than one result: " + gremlin); + } + + @Override + public PreparedStatement gremlinPrepare(String gremlin){ + requireNonNull(gremlin, "query is required"); + return new DefaultPreparedStatement(executor(), gremlin, traversal()); + } + + private void checkId(T entity) { + EntityMetadata entityMetadata = entities().get(entity.getClass()); + entityMetadata.id().orElseThrow(() -> IdNotFoundException.newInstance(entity.getClass())); + } + + private boolean isIdNull(T entity) { + EntityMetadata entityMetadata = entities().get(entity.getClass()); + FieldMetadata field = entityMetadata.id().orElseThrow(() -> IdNotFoundException.newInstance(entity.getClass())); + return isNull(field.read(entity)); + + } + + private Optional vertex(T entity) { + EntityMetadata entityMetadata = entities().get(entity.getClass()); + FieldMetadata field = entityMetadata.id().orElseThrow(() -> IdNotFoundException.newInstance(entity.getClass())); + Object id = field.read(entity); + Iterator vertices = vertices(id); + if (vertices.hasNext()) { + return Optional.of(vertices.next()); + } + return Optional.empty(); + } + + protected Iterator vertices(Object id) { + return graph().vertices(id); + } + + private Edge getEdge(String label, Vertex outVertex, Vertex inVertex) { + final Edge edge = outVertex.addEdge(label, inVertex); + GraphTransactionUtil.transaction(graph()); + return edge; + } + + private void checkLabelsSupplier(Supplier[] labels) { + if (Stream.of(labels).anyMatch(Objects::isNull)) { + throw new IllegalStateException("Item cannot be null"); + } + } + + private Collection edgesByIdImpl(K id, Direction direction, String... labels) { + + requireNonNull(id, "id is required"); + requireNonNull(direction, "direction is required"); + + Iterator vertices = vertices(id); + if (vertices.hasNext()) { + List edges = new ArrayList<>(); + vertices.next().edges(direction, labels).forEachRemaining(edges::add); + return edges.stream().map(e -> EdgeEntity.of(converter(), e)).toList(); + } + return Collections.emptyList(); + } + + private Collection edgesImpl(T entity, Direction direction, String... labels) { + requireNonNull(entity, "entity is required"); + + if (isIdNull(entity)) { + throw new IllegalStateException("Entity id is required"); + } + + Optional vertex = vertex(entity); + if (vertex.isEmpty()) { + return Collections.emptyList(); + } + + Object id = vertex.orElseThrow().id(); + return edgesByIdImpl(id, direction, labels); + } + + private GremlinExecutor executor() { + if (Objects.isNull(gremlinExecutor)) { + this.gremlinExecutor = new GremlinExecutor(converter()); + } + return gremlinExecutor; + } + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractVertexTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractVertexTraversal.java new file mode 100644 index 000000000..465dab0dc --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractVertexTraversal.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * The base class to {@link VertexTraversal} + */ +abstract class AbstractVertexTraversal { + + protected final Supplier> supplier; + protected final Function, GraphTraversal> flow; + protected final EntityConverter converter; + + AbstractVertexTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + this.supplier = supplier; + this.flow = flow; + this.converter = converter; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java new file mode 100644 index 000000000..6d4f33413 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.eclipse.jnosql.communication.Value; +import org.eclipse.jnosql.communication.graph.CommunicationEntityConverter; +import org.eclipse.jnosql.communication.semistructured.Element; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +class DefaultEdgeEntity implements EdgeEntity { + + private final O outgoing; + + private final Edge edge; + + private final I incoming; + + DefaultEdgeEntity(Edge edge, I incoming, O outgoing) { + this.edge = edge; + this.incoming = incoming; + this.outgoing = outgoing; + } + + @Override + public Object id() { + return edge.id(); + } + + @Override + public T id(Class type) { + Objects.requireNonNull(type, "type is required"); + return Value.of(edge.id()).get(type); + } + + @Override + public String label() { + return edge.label(); + } + + @Override + public I incoming() { + return incoming; + } + + @Override + public O outgoing() { + return outgoing; + } + + @Override + public List properties() { + return edge.keys() + .stream() + .map(k -> Element.of(k, edge.value(k))) + .collect(collectingAndThen(toList(), Collections::unmodifiableList)); + } + + @Override + public void add(String key, Object value) { + requireNonNull(key, "key is required"); + requireNonNull(value, "value is required"); + edge.property(key, value); + + } + + @Override + public void add(String key, Value value) { + requireNonNull(key, "key is required"); + requireNonNull(value, "value is required"); + edge.property(key, value.get()); + } + + @Override + public void remove(String key) { + requireNonNull(key, "key is required"); + Property property = edge.property(key); + property.ifPresent(o -> property.remove()); + } + + @Override + public Optional get(String key) { + requireNonNull(key, "key is required"); + Property property = edge.property(key); + if (property.isPresent()) { + return Optional.of(Value.of(property.value())); + } + return Optional.empty(); + } + + @Override + public boolean isEmpty() { + return edge.keys().isEmpty(); + } + + @Override + public int size() { + return edge.keys().size(); + } + + @Override + public void delete() { + edge.remove(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DefaultEdgeEntity that = (DefaultEdgeEntity) o; + return Objects.equals(edge.id(), that.edge.id()); + } + + @Override + public int hashCode() { + return Objects.hashCode(edge.id()); + } + + @Override + public String toString() { + return outgoing + + "---" + edge.label() + + " --->" + incoming; + } + + + public static EdgeEntity of(EntityConverter converter, Edge edge) { + Objects.requireNonNull(converter, "converter is required"); + Objects.requireNonNull(edge, "edge is required"); + var entityConverter = CommunicationEntityConverter.INSTANCE; + return new DefaultEdgeEntity<>(edge, converter.toEntity(entityConverter.apply(edge.outVertex())), + converter.toEntity(entityConverter.apply(edge.inVertex()))); + } + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeRepeatStepTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeRepeatStepTraversal.java new file mode 100644 index 000000000..2d4f3207d --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeRepeatStepTraversal.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +class DefaultEdgeRepeatStepTraversal extends AbstractEdgeTraversal implements EdgeRepeatStepTraversal { + + DefaultEdgeRepeatStepTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public EdgeTraversal times(int times) { + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.times(times)), converter); + } + + @Override + public EdgeUntilTraversal until() { + return new DefaultEdgeUntilTraversal(supplier, flow, converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeRepeatTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeRepeatTraversal.java new file mode 100644 index 000000000..ad9767a81 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeRepeatTraversal.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +class DefaultEdgeRepeatTraversal extends AbstractEdgeTraversal implements EdgeRepeatTraversal { + + + DefaultEdgeRepeatTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public EdgeRepeatStepTraversal has(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + + Traversal condition = __.has(propertyKey); + return new DefaultEdgeRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public EdgeRepeatStepTraversal has(String propertyKey, Object value) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(propertyKey, value); + return new DefaultEdgeRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public EdgeRepeatStepTraversal has(String propertyKey, P predicate) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(propertyKey, predicate); + return new DefaultEdgeRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public EdgeRepeatStepTraversal has(T accessor, Object value) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(accessor, value); + return new DefaultEdgeRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public EdgeRepeatStepTraversal has(T accessor, P predicate) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(accessor, predicate); + return new DefaultEdgeRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public EdgeRepeatStepTraversal hasNot(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + Traversal condition = __.hasNot(propertyKey); + return new DefaultEdgeRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } +} + + diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversal.java new file mode 100644 index 000000000..4397c8020 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversal.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.Iterator; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +class DefaultEdgeTraversal extends AbstractEdgeTraversal implements EdgeTraversal { + + + DefaultEdgeTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public EdgeTraversal has(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.has(propertyKey)), converter); + + } + + @Override + public EdgeTraversal has(String propertyKey, Object value) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(value, "value is required"); + + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.has(propertyKey, value)), converter); + } + + @Override + public EdgeTraversal has(String propertyKey, P predicate) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(predicate, "predicate is required"); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.has(propertyKey, predicate)), converter); + } + + @Override + public EdgeTraversal has(T accessor, Object value) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(value, "value is required"); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.has(accessor, value)), converter); + } + + @Override + public EdgeTraversal has(T accessor, P predicate) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(predicate, "predicate is required"); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.has(accessor, predicate)), converter); + } + + @Override + public EdgeTraversal hasNot(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.hasNot(propertyKey)), converter); + } + + @Override + public EdgeTraversal filter(Predicate predicate) { + requireNonNull(predicate, "predicate is required"); + + Predicate> p = e -> predicate.test(EdgeEntity.of(converter, e.get())); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.filter(p)), converter); + } + + @Override + public EdgeTraversal limit(long limit) { + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.limit(limit)), converter); + } + + @Override + public EdgeTraversal range(long start, long end) { + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.range(start, end)), converter); + } + + @Override + public EdgeRepeatTraversal repeat() { + return new DefaultEdgeRepeatTraversal(supplier, flow, converter); + } + + + @Override + public VertexTraversal inV() { + return new DefaultVertexTraversal(supplier, flow.andThen(GraphTraversal::inV), converter); + } + + @Override + public VertexTraversal outV() { + return new DefaultVertexTraversal(supplier, flow.andThen(GraphTraversal::outV), converter); + } + + @Override + public VertexTraversal bothV() { + return new DefaultVertexTraversal(supplier, flow.andThen(GraphTraversal::bothV), converter); + } + + @Override + public EdgeTraversal dedup(String... labels) { + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.dedup(labels)), converter); + } + + + @Override + public Optional next() { + Optional edgeOptional = flow.apply(supplier.get()).tryNext(); + return edgeOptional.map(edge -> EdgeEntity.of(converter, edge)); + } + + @Override + public Optional singleResult() { + Stream result = result(); + final Iterator iterator = result.iterator(); + + if (!iterator.hasNext()) { + return Optional.empty(); + } + final EdgeEntity entity = iterator.next(); + if (!iterator.hasNext()) { + return Optional.of(entity); + } + throw new NonUniqueResultException("The Edge traversal query returns more than one result"); + } + + @Override + public Stream result() { + return stream(); + } + + @Override + public Stream stream() { + return flow.apply(supplier.get()).toList().stream() + .map(edge -> EdgeEntity.of(converter, edge)); + } + + @Override + public Stream next(int limit) { + return flow.apply(supplier.get()).next(limit).stream() + .map(edge -> EdgeEntity.of(converter, edge)); + } + + @Override + public ValueMapTraversal valueMap(String... propertyKeys) { + return new DefaultValueMapTraversal(supplier, flow.andThen(g -> g.valueMap(false, propertyKeys))); + } + + @Override + public EdgeTraversalOrder orderBy(String property) { + requireNonNull(property, "property is required"); + return new DefaultEdgeTraversalOrder(supplier, flow, converter, property); + } + + @Override + public long count() { + return flow.apply(supplier.get()).count().tryNext().orElse(0L); + } + + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversalOrder.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversalOrder.java new file mode 100644 index 000000000..88d4e445b --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversalOrder.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.apache.tinkerpop.gremlin.process.traversal.Order.asc; +import static org.apache.tinkerpop.gremlin.process.traversal.Order.desc; + +final class DefaultEdgeTraversalOrder extends AbstractEdgeTraversal implements EdgeTraversalOrder { + + + private final String property; + + DefaultEdgeTraversalOrder(Supplier> supplier, Function, + GraphTraversal> flow, EntityConverter converter, String property) { + super(supplier, flow, converter); + this.property = property; + } + + @Override + public EdgeTraversal asc() { + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.order().by(property, asc)), converter); + } + + @Override + public EdgeTraversal desc() { + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.order().by(property, desc)), converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeUntilTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeUntilTraversal.java new file mode 100644 index 000000000..dbd2e190c --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeUntilTraversal.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +class DefaultEdgeUntilTraversal extends AbstractEdgeTraversal implements EdgeUntilTraversal { + + DefaultEdgeUntilTraversal(Supplier> supplier, Function, + GraphTraversal> flow, EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public EdgeTraversal has(String propertyKey) { + + requireNonNull(propertyKey, "propertyKey is required"); + Traversal condition = __.has(propertyKey); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public EdgeTraversal has(String propertyKey, Object value) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(propertyKey, value); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public EdgeTraversal has(String propertyKey, P predicate) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(propertyKey, predicate); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public EdgeTraversal has(T accessor, Object value) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(accessor, value); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public EdgeTraversal has(T accessor, P predicate) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(accessor, predicate); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public EdgeTraversal hasNot(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + Traversal condition = __.hasNot(propertyKey); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java new file mode 100644 index 000000000..e4c981d05 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Default; +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.communication.graph.GraphDatabaseManager; +import org.eclipse.jnosql.mapping.Database; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.eclipse.jnosql.mapping.semistructured.EventPersistManager; + +import static org.eclipse.jnosql.mapping.DatabaseType.GRAPH; + +@Default +@ApplicationScoped +@Database(GRAPH) +class DefaultGraphTemplate extends AbstractGraphTemplate { + + private final EntityConverter converter; + + private final GraphDatabaseManager manager; + + private final EventPersistManager eventManager; + + private final EntitiesMetadata entities; + + private final Converters converters; + private final Graph graph; + + + @Inject + DefaultGraphTemplate(EntityConverter converter, Graph graph, + EventPersistManager eventManager, + EntitiesMetadata entities, Converters converters) { + this.converter = converter; + this.graph = graph; + this.eventManager = eventManager; + this.entities = entities; + this.converters = converters; + this.manager = GraphDatabaseManager.of(graph); + } + + DefaultGraphTemplate() { + this(null, null, null, null, null); + } + + @Override + protected EntityConverter converter() { + return converter; + } + + @Override + protected GraphDatabaseManager manager() { + return manager; + } + + @Override + protected GraphTraversalSource traversal() { + return graph.traversal(); + } + + @Override + protected Graph graph() { + return graph; + } + + @Override + protected EventPersistManager eventManager() { + return eventManager; + } + + @Override + protected EntitiesMetadata entities() { + return entities; + } + + @Override + protected Converters converters() { + return converters; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultPreparedStatement.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultPreparedStatement.java new file mode 100644 index 000000000..dde6c862e --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultPreparedStatement.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import org.eclipse.jnosql.mapping.PreparedStatement; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +final class DefaultPreparedStatement implements PreparedStatement { + + private final GremlinExecutor executor; + + private final String gremlin; + + private final Map params = new HashMap<>(); + + private final GraphTraversalSource traversalSource; + + DefaultPreparedStatement(GremlinExecutor executor, String gremlin, GraphTraversalSource traversalSource) { + this.executor = executor; + this.gremlin = gremlin; + this.traversalSource = traversalSource; + } + + + @Override + public PreparedStatement bind(String name, Object value) { + requireNonNull(name, "name is required"); + requireNonNull(value, "value is required"); + params.put(name, value); + return this; + } + + @Override + public Stream result() { + return executor.executeGremlin(traversalSource, gremlin, params); + } + + @Override + public Optional singleResult() { + Stream entities = result(); + final Iterator iterator = entities.iterator(); + if (!iterator.hasNext()) { + return Optional.empty(); + } + final T entity = iterator.next(); + if (!iterator.hasNext()) { + return Optional.of(entity); + } + throw new NonUniqueResultException("There is more than one result found in the gremlin query: " + gremlin); + } + + @Override + public long count() { + throw new UnsupportedOperationException("There is no count operation in the graph database with Gremlin"); + } + + @Override + public boolean isCount() { + return false; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultValueMapTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultValueMapTraversal.java new file mode 100644 index 000000000..838ff1a4a --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultValueMapTraversal.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toList; + +/** + * The default implementation of {@link ValueMapTraversal} + */ +class DefaultValueMapTraversal implements ValueMapTraversal { + + private final Supplier> supplier; + private final Function, GraphTraversal>> flow; + + DefaultValueMapTraversal(Supplier> supplier, Function, + GraphTraversal>> flow) { + this.supplier = supplier; + this.flow = flow; + } + + + @Override + public Stream> stream() { + return flow.apply(supplier.get()).toList().stream().map(toMap()); + } + + + @Override + public Stream> next(int limit) { + return flow.apply(supplier.get()).next(limit).stream().map(toMap()); + } + + @Override + public Map next() { + return flow.apply(supplier.get()).tryNext().map(toMap()).orElse(emptyMap()); + } + + @Override + public Optional> singleResult() { + List> result = resultList(); + if (result.isEmpty()) { + return Optional.empty(); + } + if (result.size() == 1) { + return Optional.of(result.get(0)); + } + throw new NonUniqueResultException("The Edge traversal query returns more than one result"); + } + + @Override + public List> resultList() { + return stream().collect(toList()); + } + + @Override + public long count() { + return flow.apply(supplier.get()).count().tryNext().orElse(0L); + } + + private Function, Map> toMap() { + return map -> map + .entrySet() + .stream() + .collect(Collectors.toMap(k -> k.getKey().toString(), Map.Entry::getValue)); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexRepeatStepTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexRepeatStepTraversal.java new file mode 100644 index 000000000..f9b7eb011 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexRepeatStepTraversal.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +class DefaultVertexRepeatStepTraversal extends AbstractVertexTraversal implements VertexRepeatStepTraversal { + + + DefaultVertexRepeatStepTraversal(Supplier> supplier, Function, + GraphTraversal> flow, EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public VertexTraversal times(int times) { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.times(times)), converter); + } + + @Override + public VertexUntilTraversal until() { + return new DefaultVertexUntilTraversal(supplier, flow, converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexRepeatTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexRepeatTraversal.java new file mode 100644 index 000000000..15227d6f5 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexRepeatTraversal.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +class DefaultVertexRepeatTraversal extends AbstractVertexTraversal implements VertexRepeatTraversal { + + DefaultVertexRepeatTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public VertexRepeatStepTraversal has(String propertyKey, Object value) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(propertyKey, value); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal has(String propertyKey, P predicate) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(propertyKey, predicate); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal has(T accessor, Object value) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(accessor, value); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal has(T accessor, P predicate) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(accessor, predicate); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal hasNot(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + Traversal condition = __.hasNot(propertyKey); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal out(String... labels) { + Stream.of(labels).forEach(l -> Objects.requireNonNull(l, "label is required")); + Traversal condition = __.out(labels); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal in(String... labels) { + Stream.of(labels).forEach(l -> Objects.requireNonNull(l, "label is required")); + Traversal condition = __.in(labels); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } + + @Override + public VertexRepeatStepTraversal both(String... labels) { + Stream.of(labels).forEach(l -> Objects.requireNonNull(l, "label is required")); + Traversal condition = __.both(labels); + return new DefaultVertexRepeatStepTraversal(supplier, flow.andThen(g -> g.repeat(condition)), converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversal.java new file mode 100644 index 000000000..c81ebd1a9 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversal.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import jakarta.nosql.Entity; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.graph.CommunicationEntityConverter; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.Iterator; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * The default implementation of {@link VertexTraversal} + */ +class DefaultVertexTraversal extends AbstractVertexTraversal implements VertexTraversal { + + + private static final Predicate IS_EMPTY = String::isEmpty; + private static final Predicate NOT_EMPTY = IS_EMPTY.negate(); + + DefaultVertexTraversal(Supplier> supplier, + Function, GraphTraversal> flow, + EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public VertexTraversal has(String propertyKey, Object value) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(value, "value is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.has(propertyKey, value)), converter); + } + + @Override + public VertexTraversal has(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.has(propertyKey)), converter); + } + + @Override + public VertexTraversal has(String propertyKey, P predicate) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(predicate, "predicate is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.has(propertyKey, predicate)), converter); + } + + @Override + public VertexTraversal has(T accessor, Object value) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(value, "value is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.has(accessor, value)), converter); + } + + @Override + public VertexTraversal has(T accessor, P predicate) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(predicate, "predicate is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.has(accessor, predicate)), converter); + } + + @Override + public VertexTraversal out(String... labels) { + Stream.of(labels).forEach(l -> requireNonNull(l, "label is required")); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.out(labels)), converter); + } + + @Override + public VertexTraversal filter(Predicate predicate) { + requireNonNull(predicate, "predicate is required"); + + Predicate> p = v -> predicate.test(GraphEntityConverter.INSTANCE.toEntity(converter, v.get())); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.filter(p)), converter); + } + + @Override + public EdgeTraversal outE(String... edgeLabels) { + Stream.of(edgeLabels).forEach(l -> requireNonNull(l, "label is required")); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.outE(edgeLabels)), converter); + } + + @Override + public VertexTraversal in(String... labels) { + Stream.of(labels).forEach(l -> requireNonNull(l, "label is required")); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.in(labels)), converter); + } + + @Override + public EdgeTraversal inE(String... edgeLabels) { + Stream.of(edgeLabels).forEach(l -> requireNonNull(l, "edgeLabel is required")); + + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.inE(edgeLabels)), converter); + } + + @Override + public VertexTraversal both(String... labels) { + Stream.of(labels).forEach(l -> requireNonNull(l, "labels is required")); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.both(labels)), converter); + } + + @Override + public EdgeTraversal bothE(String... edgeLabels) { + Stream.of(edgeLabels).forEach(l -> requireNonNull(l, "edgeLabel is required")); + return new DefaultEdgeTraversal(supplier, flow.andThen(g -> g.bothE(edgeLabels)), converter); + } + + @Override + public VertexTraversal dedup(String... labels) { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.dedup(labels)), converter); + } + + @Override + public VertexRepeatTraversal repeat() { + return new DefaultVertexRepeatTraversal(supplier, flow, converter); + } + + @Override + public VertexTraversal limit(long limit) { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.limit(limit)), converter); + } + + @Override + public VertexTraversal skip(long skip) { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.skip(skip)), converter); + } + + @Override + public VertexTraversal range(long start, long end) { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.range(start, end)), converter); + } + + + @Override + public VertexTraversal hasLabel(String label) { + requireNonNull(label, "label is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.hasLabel(label)), converter); + } + + @Override + public VertexTraversal hasLabel(Class type) { + requireNonNull(type, "type is required"); + Entity entity = type.getAnnotation(Entity.class); + String label = Optional.ofNullable(entity).map(Entity::value) + .filter(NOT_EMPTY) + .orElse(type.getSimpleName()); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.hasLabel(label)), converter); + } + + @Override + public VertexTraversal hasLabel(P predicate) { + requireNonNull(predicate, "predicate is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.hasLabel(predicate)), converter); + } + + @Override + public VertexTraversal hasNot(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.hasNot(propertyKey)), converter); + } + + @Override + public Optional next() { + Optional vertex = flow.apply(supplier.get()).tryNext(); + return vertex.map(CommunicationEntityConverter.INSTANCE).map(converter::toEntity); + } + + @Override + public Stream result() { + return flow.apply(supplier.get()) + .toStream() + .map(CommunicationEntityConverter.INSTANCE) + .map(converter::toEntity); + } + + @Override + public Optional singleResult() { + final Stream stream = result(); + final Iterator iterator = stream.iterator(); + + if (!iterator.hasNext()) { + return Optional.empty(); + } + final T entity = iterator.next(); + if (!iterator.hasNext()) { + return Optional.of(entity); + } + throw new NonUniqueResultException("The Vertex traversal query returns more than one result"); + } + + @Override + public Stream next(int limit) { + return flow.apply(supplier.get()) + .next(limit) + .stream() + .map(CommunicationEntityConverter.INSTANCE) + .map(converter::toEntity); + } + + @Override + public ValueMapTraversal valueMap(String... propertyKeys) { + return new DefaultValueMapTraversal(supplier, flow.andThen(g -> g.valueMap(false, propertyKeys))); + } + + @Override + public long count() { + return flow.apply(supplier.get()).count().tryNext().orElse(0L); + } + + @Override + public VertexTraversalOrder orderBy(String property) { + requireNonNull(property, "property is required"); + return new DefaultVertexTraversalOrder(supplier, flow, converter, property); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversalOrder.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversalOrder.java new file mode 100644 index 000000000..2cfd8543b --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversalOrder.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.apache.tinkerpop.gremlin.process.traversal.Order.asc; +import static org.apache.tinkerpop.gremlin.process.traversal.Order.desc; + +final class DefaultVertexTraversalOrder extends AbstractVertexTraversal implements VertexTraversalOrder { + + private final String property; + + DefaultVertexTraversalOrder(Supplier> supplier, Function, + GraphTraversal> flow, EntityConverter converter, String property) { + super(supplier, flow, converter); + this.property = property; + } + + @Override + public VertexTraversal asc() { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.order().by(property, asc)), converter); + } + + @Override + public VertexTraversal desc() { + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.order().by(property, desc)), converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexUntilTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexUntilTraversal.java new file mode 100644 index 000000000..da9b28377 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultVertexUntilTraversal.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.nosql.Entity; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.T; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +class DefaultVertexUntilTraversal extends AbstractVertexTraversal implements VertexUntilTraversal { + + + DefaultVertexUntilTraversal(Supplier> supplier, Function, + GraphTraversal> flow, EntityConverter converter) { + super(supplier, flow, converter); + } + + @Override + public VertexTraversal has(String propertyKey, Object value) { + + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(propertyKey, value); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal has(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + Traversal condition = __.has(propertyKey); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal has(String propertyKey, P predicate) { + requireNonNull(propertyKey, "propertyKey is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(propertyKey, predicate); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal has(T accessor, Object value) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(value, "value is required"); + Traversal condition = __.has(accessor, value); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal has(T accessor, P predicate) { + requireNonNull(accessor, "accessor is required"); + requireNonNull(predicate, "predicate is required"); + Traversal condition = __.has(accessor, predicate); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal hasNot(String propertyKey) { + requireNonNull(propertyKey, "propertyKey is required"); + Traversal condition = __.hasNot(propertyKey); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal out(String... labels) { + Stream.of(labels).forEach(l -> Objects.requireNonNull(l, "label is required")); + Traversal condition = __.out(labels); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal in(String... labels) { + Stream.of(labels).forEach(l -> Objects.requireNonNull(l, "label is required")); + Traversal condition = __.in(labels); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal both(String... labels) { + Stream.of(labels).forEach(l -> Objects.requireNonNull(l, "label is required")); + Traversal condition = __.both(labels); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal hasLabel(String label) { + requireNonNull(label, "label is required"); + Traversal condition = __.hasLabel(label); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.until(condition)), converter); + } + + @Override + public VertexTraversal hasLabel(Class type) { + requireNonNull(type, "type is required"); + Entity entity = type.getAnnotation(Entity.class); + String label = Optional.ofNullable(entity).map(Entity::value).orElse(type.getName()); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.hasLabel(label)), converter); + } + + @Override + public VertexTraversal hasLabel(P predicate) { + requireNonNull(predicate, "predicate is required"); + return new DefaultVertexTraversal(supplier, flow.andThen(g -> g.hasLabel(predicate)), converter); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeConditionTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeConditionTraversal.java new file mode 100644 index 000000000..1971422af --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeConditionTraversal.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.T; + +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +/** + * The base conditions to {@link EdgeTraversal} and {@link EdgeUntilTraversal} + */ +public interface EdgeConditionTraversal { + + /** + * Map the {@link org.apache.tinkerpop.gremlin.structure.Element} to the values of the associated properties given the provide property keys. + * If no property keys are provided, then all property values are emitted. + * + * @param propertyKey the key + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + EdgeTraversal has(String propertyKey); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + EdgeTraversal has(String propertyKey, Object value); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + EdgeTraversal has(String propertyKey, P predicate); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + default EdgeTraversal has(Supplier propertyKey, Object value) { + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), value); + } + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + default EdgeTraversal has(Supplier propertyKey, P predicate) { + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), predicate); + } + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param value the value to the condition + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + EdgeTraversal has(T accessor, Object value); + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param predicate the predicate condition + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + EdgeTraversal has(T accessor, P predicate); + + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + EdgeTraversal hasNot(String propertyKey); + + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + default EdgeTraversal hasNot(Supplier propertyKey) { + requireNonNull(propertyKey, "the supplier is required"); + return hasNot(propertyKey.get()); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java new file mode 100644 index 000000000..1395ae87c --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.eclipse.jnosql.communication.Value; +import org.eclipse.jnosql.communication.graph.CommunicationEntityConverter; +import org.eclipse.jnosql.communication.semistructured.Element; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * It is a wrapper of {@link org.apache.tinkerpop.gremlin.structure.Edge} that links two Entity. + * Along with its Property objects, an Edge has both a Direction and a label. + * Any Change at the Edge is automatically continued in the database. However, any, change in the Entity will be ignored. + * {@link GraphTemplate#update(Object)} + * + *
outVertex ---label---> inVertex.
+ */ +public interface EdgeEntity { + /** + * Returns the identifier of this edge. + * + * @return the identifier of this edge + */ + Object id(); + + /** + * Returns the identifier of this edge converted to the specified type. + * + * @param type the class type to convert the identifier to + * @param the type to convert the identifier to + * @return the identifier of this edge converted to the specified type + */ + T id(Class type); + + /** + * Returns the label of this edge. + * + * @return the label of this edge + */ + String label(); + + /** + * Retrieves the incoming entity connected to this edge. + * + * @param the type of the incoming entity + * @return the incoming entity connected to this edge + */ + T incoming(); + + /** + * Retrieves the outgoing entity connected to this edge. + * + * @param the type of the outgoing entity + * @return the outgoing entity connected to this edge + */ + T outgoing(); + + /** + * Returns the properties of this edge. + * + * @return the properties of this edge + */ + List properties(); + + /** + * Adds a new property to this edge with the specified key and value. + * + * @param key the key of the property + * @param value the value of the property + * @throws NullPointerException if either key or value is null + */ + void add(String key, Object value); + + /** + * Adds a new property to this edge with the specified key and value. + * + * @param key the key of the property + * @param value the value of the property + * @throws NullPointerException if either key or value is null + */ + void add(String key, Value value); + + /** + * Removes the property with the specified key from this edge. + * + * @param key the key of the property to be removed + * @throws NullPointerException if the key is null + */ + void remove(String key); + + /** + * Returns the property value associated with the specified key, if present. + * + * @param key the key of the property to retrieve + * @return the value associated with the specified key, or {@link Optional#empty()} if the key is not present + * @throws NullPointerException if the key is null + */ + Optional get(String key); + + /** + * Returns true if this edge contains no properties. + * + * @return true if this edge contains no properties + */ + boolean isEmpty(); + + /** + * Returns the number of properties in this edge. + * + * @return the number of properties in this edge + */ + int size(); + + /** + * Deletes this edge from the database. After this operation, any modification attempts such as adding or removing properties will result in an illegal state. + */ + void delete(); + + /** + * Creates an {@link EdgeEntity} instance from the provided {@link EntityConverter} and {@link Edge}. + * + * @param converter the entity converter to use + * @param edge the edge to create the entity from + * @return the created {@link EdgeEntity} instance + * @throws NullPointerException if either converter or edge is null + */ + static EdgeEntity of(EntityConverter converter, Edge edge) { + Objects.requireNonNull(converter, "converter is required"); + Objects.requireNonNull(edge, "edge is required"); + var entityConverter = CommunicationEntityConverter.INSTANCE; + return new DefaultEdgeEntity<>(edge, converter.toEntity(entityConverter.apply(edge.inVertex())), + converter.toEntity(entityConverter.apply(edge.outVertex()))); + } + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeRepeatStepTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeRepeatStepTraversal.java new file mode 100644 index 000000000..343af8070 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeRepeatStepTraversal.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +/** + * After the {@link EdgeRepeatTraversal} condition the next step is + * {@link EdgeRepeatStepTraversal#times(int)} and {@link EdgeRepeatStepTraversal#until()} + */ +public interface EdgeRepeatStepTraversal { + + /** + * Starts a loop traversal n times + * @param times the repeat times that is required + * @return a {@link EdgeTraversal} instance + */ + EdgeTraversal times(int times); + + /** + * Define the loop traversal until a defined condition + * @return {@link EdgeUntilTraversal} + */ + EdgeUntilTraversal until(); + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeRepeatTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeRepeatTraversal.java new file mode 100644 index 000000000..661177497 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeRepeatTraversal.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.T; + +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +/** + * The wrapper step to + * {@link org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal#repeat(org.apache.tinkerpop.gremlin.process.traversal.Traversal)} + * in the Edge type. + */ +public interface EdgeRepeatTraversal { + + + /** + * Map the {@link org.apache.tinkerpop.gremlin.structure.Element} to the values of the associated properties given the provide property keys. + * If no property keys are provided, then all property values are emitted. + * + * @param propertyKey the key + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + EdgeRepeatStepTraversal has(String propertyKey); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + EdgeRepeatStepTraversal has(String propertyKey, Object value); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + EdgeRepeatStepTraversal has(String propertyKey, P predicate); + // + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + default EdgeRepeatStepTraversal has(Supplier propertyKey, Object value) { + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), value); + } + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + default EdgeRepeatStepTraversal has(Supplier propertyKey, P predicate) { + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), predicate); + } + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param value the value to the condition + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + EdgeRepeatStepTraversal has(T accessor, Object value); + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param predicate the predicate condition + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + EdgeRepeatStepTraversal has(T accessor, P predicate); + + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + EdgeRepeatStepTraversal hasNot(String propertyKey); + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link EdgeRepeatStepTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + default EdgeRepeatStepTraversal hasNot(Supplier propertyKey) { + requireNonNull(propertyKey, "the supplier is required"); + return hasNot(propertyKey.get()); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeTraversal.java new file mode 100644 index 000000000..0f45ce897 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeTraversal.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupGlobalStep; + +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * The Graph Traversal that maps {@link org.apache.tinkerpop.gremlin.structure.Edge}. + * This Traversal is lazy, in other words, that just run after any finalizing method. + */ +public interface EdgeTraversal extends EdgeConditionTraversal { + + /** + * Does a filter predicate based + * @param predicate a predicate to apply to each element to determine if it should be included + * @return a with the Vertex predicate + * @throws NullPointerException when predicate is null + */ + EdgeTraversal filter(Predicate predicate); + + /** + * Filter the objects in the traversal by the number of them to pass through the next, where only the first + * {@code n} objects are allowed as defined by the {@code limit} argument. + * + * @param limit the number at which to end the next + * @return a with the limit + */ + EdgeTraversal limit(long limit); + + /** + * Returns an EdgeTraversal with range defined + * + * @param start the start inclusive + * @param end the end exclusive + * @return a with the range set + */ + EdgeTraversal range(long start, long end); + + + /** + * Starts the loop traversal graph + * + * @return a {@link EdgeRepeatTraversal} + */ + EdgeRepeatTraversal repeat(); + + + /** + * Returns the next elements in the traversal. + * If the traversal is empty, then an {@link Optional#empty()} is returned. + * + * @return the EdgeEntity result otherwise {@link Optional#empty()} + */ + Optional next(); + + /** + * Concludes the traversal that returns a single {@link EdgeEntity} result + * @return the EdgeEntity result otherwise {@link Optional#empty()} + */ + Optional singleResult(); + + /** + * Concludes the traversal then returns the result as list. + * @return the entities result + */ + Stream result(); + + + /** + * Converts to vertex traversal taking the incoming Vertex + * + * @return {@link VertexTraversal} + */ + VertexTraversal inV(); + + /** + * Converts to vertex traversal taking the outgoing Vertex + * + * @return {@link VertexTraversal} + */ + VertexTraversal outV(); + + /** + * Converts to vertex traversal taking both incoming and outgoing Vertex + * + * @return {@link VertexTraversal} + */ + VertexTraversal bothV(); + + /** + * Remove all duplicates in the traversal stream up to this point. + * + * @param labels if labels are provided, then the scoped object's labels determine de-duplication. No labels implies current object. + * @return the traversal with an appended {@link DedupGlobalStep}. + */ + EdgeTraversal dedup(final String... labels); + + /** + * Get all the result in the traversal as Stream + * + * @return the entity result as {@link Stream} + */ + Stream stream(); + + /** + * Get the next n elements result as next, the number of elements is limit based + * + * @param limit the limit to result + * @return the entity result as {@link Stream} + */ + Stream next(int limit); + + /** + * Map the {@link org.apache.tinkerpop.gremlin.structure.Element} to a {@link java.util.Map} of the properties key'd according + * to their {@link org.apache.tinkerpop.gremlin.structure.Property#key}. + * If no property keys are provided, then all properties are retrieved. + * + * @param propertyKeys the properties to retrieve + * @return a {@link ValueMapTraversal} instance + */ + ValueMapTraversal valueMap(final String... propertyKeys); + + /** + * Defines the order of the Edge, the property must have in all elements. + * Otherwise, it'll return an exception. As recommendation use + * {@link EdgeTraversal#has(String)} before this method + * + * @param property the property to be order + * @return the {@link EdgeTraversalOrder} to define the order way + * @throws NullPointerException when the property is null + * @throws IllegalStateException when there any Edge that does not have the property + */ + EdgeTraversalOrder orderBy(String property); + + /** + * Map the traversal next to its reduction as a sum of the elements + * + * @return the sum + */ + long count(); +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeTraversalOrder.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeTraversalOrder.java new file mode 100644 index 000000000..abfa3000e --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeTraversalOrder.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +/** + * The traversal order to Edge + */ +public interface EdgeTraversalOrder { + + /** + * Defines the ascending order + * @return the {@link EdgeTraversal} ordered ascending + */ + EdgeTraversal asc(); + + /** + * Defines the descending order + * @return the {@link EdgeTraversal} ordered descending + */ + EdgeTraversal desc(); +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeUntilTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeUntilTraversal.java new file mode 100644 index 000000000..d6902c8bf --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeUntilTraversal.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + + +/** + * The Edge until wrapper of + * {@link org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal#until(java.util.function.Predicate)} + */ +public interface EdgeUntilTraversal extends EdgeConditionTraversal { +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphEntityConverter.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphEntityConverter.java new file mode 100644 index 000000000..ea722bb51 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphEntityConverter.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.graph.CommunicationEntityConverter; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +enum GraphEntityConverter { + + INSTANCE; + + public T toEntity(EntityConverter converter, Vertex vertex) { + var entity = CommunicationEntityConverter.INSTANCE.apply(vertex); + return converter.toEntity(entity); + } + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTemplate.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTemplate.java new file mode 100644 index 000000000..7416b1d53 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTemplate.java @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.eclipse.jnosql.mapping.PreparedStatement; +import org.apache.tinkerpop.gremlin.structure.Direction; +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.eclipse.jnosql.mapping.semistructured.SemiStructuredTemplate; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + + +/** + * GraphTemplate is a helper class that increases productivity when performing common Graph operations. + * Includes integrated object mapping between documents and POJOs {@link org.apache.tinkerpop.gremlin.structure.Vertex} + * and {@link org.apache.tinkerpop.gremlin.structure.Edge}. + * It represents the common operation between an entity and {@link org.apache.tinkerpop.gremlin.structure.Graph} + * + * @see org.apache.tinkerpop.gremlin.structure.Graph + */ +public interface GraphTemplate extends SemiStructuredTemplate { + + /** + * Deletes a {@link org.apache.tinkerpop.gremlin.structure.Vertex} + * + * @param id the id to be used in the query {@link org.apache.tinkerpop.gremlin.structure.T#id} + * @param the id type + * @throws NullPointerException when id is null + */ + void delete(T id); + + /** + * Deletes a {@link org.apache.tinkerpop.gremlin.structure.Edge} + * + * @param id the id to be used in the query {@link org.apache.tinkerpop.gremlin.structure.T#id} + * @param the id type + * @throws NullPointerException when either label and id are null + */ + void deleteEdge(T id); + + /** + * Deletes {@link org.apache.tinkerpop.gremlin.structure.Edge} instances + * + * @param ids the ids to be used in the query {@link org.apache.tinkerpop.gremlin.structure.T#id} + * @param the id type + * @throws NullPointerException when either label and id are null + */ + void deleteEdge(Iterable ids); + + /** + * Either find or create an Edge between this two entities. + * {@link org.apache.tinkerpop.gremlin.structure.Edge} + *
entityOUT ---label---> entityIN.
+ * + * @param incoming the incoming entity + * @param label the Edge label + * @param outgoing the outgoing entity + * @param the incoming type + * @param the outgoing type + * @return the {@link EdgeEntity} of these two entities + * @throws NullPointerException Either when any elements are null or the entity is null + */ + EdgeEntity edge(O outgoing, String label, I incoming); + + /** + * Either find or create an Edge between this two entities. + * {@link org.apache.tinkerpop.gremlin.structure.Edge} + *
entityOUT ---label---> entityIN.
+ * + * @param incoming the incoming entity + * @param label the Edge label + * @param outgoing the outgoing entity + * @param the incoming type + * @param the outgoing type + * @return the {@link EdgeEntity} of these two entities + * @throws NullPointerException Either when any elements are null or the entity is null + */ + default EdgeEntity edge(O outgoing, Supplier label, I incoming) { + Objects.requireNonNull(label, "supplier is required"); + return edge(outgoing, label.get(), incoming); + } + + /** + * Find an entity given {@link org.apache.tinkerpop.gremlin.structure.T#label} and + * {@link org.apache.tinkerpop.gremlin.structure.T#id} + * + * @param id the id to be used in the query {@link org.apache.tinkerpop.gremlin.structure.T#id} + * @param the entity type + * @param the id type + * @return the entity found otherwise {@link Optional#empty()} + * @throws NullPointerException when id is null + */ + Optional find(K id); + + /** + * returns the edges of from a vertex id + * + * @param id the id + * @param direction the direction + * @param labels the edge labels + * @param the K type + * @return the Edges + * @throws NullPointerException where there is any parameter null + */ + Collection edgesById(K id, Direction direction, String... labels); + + /** + * returns the edges of from a vertex id + * + * @param id the id + * @param direction the direction + * @param labels the edge labels + * @param the K type + * @return the Edges + * @throws NullPointerException where there is any parameter null + */ + Collection edgesById(K id, Direction direction, Supplier... labels); + + /** + * returns the edges of from a vertex id + * + * @param id the id + * @param direction the direction + * @param the K type + * @return the Edges + * @throws NullPointerException where there is any parameter null + */ + Collection edgesById(K id, Direction direction); + + /** + * returns the edges of from an entity + * + * @param entity the entity + * @param direction the direction + * @param labels the edge labels + * @param the entity type + * @return the Edges + * @throws NullPointerException where there is any parameter null + */ + Collection edges(T entity, Direction direction, String... labels); + + /** + * returns the edges of from an entity + * + * @param entity the entity + * @param direction the direction + * @param labels the edge labels + * @param the entity type + * @return the Edges + * @throws NullPointerException where there is any parameter null + */ + Collection edges(T entity, Direction direction, Supplier... labels); + + /** + * returns the edges of from an entity + * + * @param entity the entity + * @param direction the direction + * @param the entity type + * @return the Edges + * @throws NullPointerException where there is any parameter null + */ + Collection edges(T entity, Direction direction); + + /** + * Finds an {@link EdgeEntity} from the Edge Id + * + * @param edgeId the edge id + * @param the edge id type + * @return the {@link EdgeEntity} otherwise {@link Optional#empty()} + * @throws IllegalStateException when edgeId is null + */ + Optional edge(E edgeId); + + + /** + * Gets a {@link VertexTraversal} to run a query in the graph + * + * @param vertexIds get ids + * @return a {@link VertexTraversal} instance + * @throws IllegalStateException if any id element is null + */ + VertexTraversal traversalVertex(Object... vertexIds); + + + /** + * Gets a {@link EdgeTraversal} to run a query in the graph + * + * @param edgeIds get ids + * @return a {@link VertexTraversal} instance + * @throws IllegalStateException if any id element is null + */ + EdgeTraversal traversalEdge(Object... edgeIds); + + /** + * Gets the current transaction + * + * @return the current {@link Transaction} + */ + Transaction transaction(); + + /** + * Executes a Gremlin then bring the result as a {@link Stream} + * + * @param gremlin the query gremlin + * @param the entity type + * @return the result as {@link Stream} + * @throws NullPointerException when the gremlin is null + */ + Stream gremlin(String gremlin); + + /** + * Executes a Gremlin query then bring the result as a unique result + * + * @param gremlin the gremlin query + * @param the entity type + * @return the result as {@link List} + * @throws NullPointerException when the query is null + */ + Optional gremlinSingleResult(String gremlin); + + /** + * Creates a {@link PreparedStatement} from the query + * + * @param gremlin the gremlin query + * @return a {@link PreparedStatement} instance + * @throws NullPointerException when the query is null + */ + PreparedStatement gremlinPrepare(String gremlin); + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTemplateProducer.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTemplateProducer.java new file mode 100644 index 000000000..dc2603221 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTemplateProducer.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Vetoed; +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.communication.graph.GraphDatabaseManager; +import org.eclipse.jnosql.communication.semistructured.DatabaseManager; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.eclipse.jnosql.mapping.semistructured.EventPersistManager; + +import java.util.Objects; +import java.util.function.Function; + +/** + * An {@code ApplicationScoped} producer class responsible for creating instances of {@link GraphTemplate}. + * It implements the {@link Function} interface with {@link DatabaseManager} as input and {@link GraphTemplate} as output. + */ +@ApplicationScoped +public class GraphTemplateProducer implements Function { + + @Inject + private EntityConverter converter; + + @Inject + private EventPersistManager eventManager; + + @Inject + private EntitiesMetadata entities; + + @Inject + private Converters converters; + + + @Override + public GraphTemplate apply(Graph graph) { + Objects.requireNonNull(graph, "graph is required"); + return new ProducerGraphTemplate(converter, graph, + eventManager, entities, converters); + } + + @Vetoed + static class ProducerGraphTemplate extends AbstractGraphTemplate { + + private final EntityConverter converter; + + + private final EventPersistManager eventManager; + + private final EntitiesMetadata entities; + + private final Converters converters; + + private final Graph graph; + + private final GraphDatabaseManager manager; + + public ProducerGraphTemplate(EntityConverter converter, Graph graph, + EventPersistManager eventManager, + EntitiesMetadata entities, Converters converters) { + this.converter = converter; + this.graph = graph; + this.manager = GraphDatabaseManager.of(graph); + this.eventManager = eventManager; + this.entities = entities; + this.converters = converters; + } + + ProducerGraphTemplate() { + this(null, null, null, null, null); + } + + @Override + protected EntityConverter converter() { + return converter; + } + + @Override + protected GraphDatabaseManager manager() { + return manager; + } + + @Override + protected EventPersistManager eventManager() { + return eventManager; + } + + @Override + protected EntitiesMetadata entities() { + return entities; + } + + @Override + protected Converters converters() { + return converters; + } + + @Override + protected GraphTraversalSource traversal() { + return graph.traversal(); + } + + @Override + protected Graph graph() { + return graph; + } + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinExecutor.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinExecutor.java new file mode 100644 index 000000000..bafbc4c39 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinExecutor.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.jsr223.GremlinLangScriptEngine; +import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngine; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.graph.CommunicationEntityConverter; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; + +import javax.script.Bindings; +import javax.script.ScriptException; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +final class GremlinExecutor { + private final EntityConverter converter; + + private static final GremlinScriptEngine ENGINE = new GremlinLangScriptEngine(); + + GremlinExecutor(EntityConverter converter) { + this.converter = converter; + } + + Stream executeGremlin(GraphTraversalSource traversalSource, String gremlin) { + return executeGremlin(traversalSource, gremlin, Collections.emptyMap()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + Stream executeGremlin(GraphTraversalSource traversalSource, String gremlin, Map params) { + try { + Bindings bindings = ENGINE.createBindings(); + bindings.put("g", traversalSource); + + + String query = GremlinParamParser.INSTANCE.apply(gremlin, params); + Object eval = ENGINE.eval(query, bindings); + if (eval instanceof GraphTraversal graphTraversal) { + return convertToStream(graphTraversal.toStream()); + } + if (eval instanceof Iterable iterable) { + return convertToStream(StreamSupport.stream(iterable.spliterator(), false)); + } + if (eval instanceof Stream stream) { + return convertToStream(stream); + } + return Stream.of((T) eval); + } catch (ScriptException e) { + throw new GremlinQueryException("There is an error when executed the gremlin query: " + gremlin, e); + } + } + + @SuppressWarnings("unchecked") + private Stream convertToStream(Stream stream) { + return stream.map(this::getElement).map(e -> (T) e); + } + + private Object getElement(Object entity) { + if (entity instanceof Vertex vertex) { + return converter.toEntity(CommunicationEntityConverter.INSTANCE.apply(vertex)); + } + + if (entity instanceof Edge edge) { + return EdgeEntity.of(converter, edge); + } + return entity; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinParamParser.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinParamParser.java new file mode 100644 index 000000000..f50897290 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinParamParser.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This singleton has the goal to interpolate params inside the Gremlin query. + * Thus, given the query: + * "g.V().hasLabel(@param)" where the params is {"param":"Otavio"} + * It should return the query to: g.V().hasLabel("Otavio") + * It should check the Gremlin query options: + * Gremlin.g4 + *

+ * Thus, given a current query with params it should convert to Gremlin compatible syntax. + */ +enum GremlinParamParser implements BiFunction, String> { + INSTANCE; + + private final Pattern pattern = Pattern.compile("@\\w+"); + + @Override + public String apply(String query, Map params) { + Objects.requireNonNull(query, "query is required"); + Objects.requireNonNull(query, "params is required"); + Matcher matcher = pattern.matcher(query); + List leftParams = new ArrayList<>(params.keySet()); + StringBuilder gremlin = new StringBuilder(); + while (matcher.find()) { + String param = matcher.group().substring(1); + leftParams.remove(param); + Object value = params.get(param); + if (value == null) { + throw new GremlinQueryException("The param is " + param + " is required on the query " + query); + } + matcher.appendReplacement(gremlin, toString(value)); + } + matcher.appendTail(gremlin); + if (leftParams.isEmpty()) { + return gremlin.toString(); + } + + throw new GremlinQueryException("There are params missing on the parser: " + leftParams + " on the query" + query); + } + + private String toString(Object value) { + if (value instanceof Number) { + return value.toString(); + } + return '\'' + value.toString() + '\''; + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinQueryException.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinQueryException.java new file mode 100644 index 000000000..f72835550 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/GremlinQueryException.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + + +import jakarta.data.exceptions.MappingException; + +/** + * An exception that provides information when executing Gremlin in the database. + */ +public class GremlinQueryException extends MappingException { + + /** + * A new instance with both the cause of the error and a message + * + * @param message the message + * @param cause the cause + */ + public GremlinQueryException(String message, Throwable cause) { + super(message, cause); + } + + /** + * A new instance with the message + * + * @param message the message + */ + public GremlinQueryException(String message) { + super(message); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/Transactional.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/Transactional.java new file mode 100644 index 000000000..5fa099c78 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/Transactional.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + + +import jakarta.interceptor.InterceptorBinding; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * The Transactional annotation provides the application the ability to declaratively + * control transaction boundaries on CDI managed beans. {@link org.apache.tinkerpop.gremlin.structure.Transaction} + */ +@InterceptorBinding +@Target({METHOD, TYPE}) +@Retention(RUNTIME) +public @interface Transactional { +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/TransactionalInterceptor.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/TransactionalInterceptor.java new file mode 100644 index 000000000..5eece4be2 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/TransactionalInterceptor.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; +import org.eclipse.jnosql.communication.graph.GraphTransactionUtil; + + +@Transactional +@Interceptor +@Alternative +@Priority(Interceptor.Priority.APPLICATION) +class TransactionalInterceptor { + + @Inject + private Instance graph; + + @AroundInvoke + public Object manageTransaction(InvocationContext context) throws Exception { + Transaction transaction = graph.get().tx(); + GraphTransactionUtil.lock(transaction); + if (!transaction.isOpen()) { + transaction.open(); + } + try { + Object proceed = context.proceed(); + transaction.commit(); + return proceed; + } catch (Exception exception) { + transaction.rollback(); + throw exception; + }finally { + GraphTransactionUtil.unlock(); + } + + } + + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/ValueMapTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/ValueMapTraversal.java new file mode 100644 index 000000000..eb556edb5 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/ValueMapTraversal.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +/** + * The Graph Traversal that maps {@link org.apache.tinkerpop.gremlin.structure.Element}. + * This Traversal is lazy, in other words, that just run after the + */ +public interface ValueMapTraversal { + + + /** + * Get all the result in the traversal as Stream + * + * @return the entity result as {@link Stream} + */ + Stream> stream(); + + /** + * Get the next n-number of results from the traversal. + * + * @param limit the limit to result + * @return the entity result as {@link Stream} + */ + Stream> next(int limit); + + /** + * Returns the next elements in the traversal. + * + * @return the map + */ + Map next(); + + /** + * Concludes the traversal that returns a single result + * + * @return the entity result otherwise {@link Optional#empty()} + */ + Optional> singleResult(); + + /** + * Concludes the traversal then returns the result as list. + * + * @return the maps result + */ + List> resultList(); + + /** + * Map the traversal next to its reduction as a sum of the elements + * + * @return the sum + */ + long count(); +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexConditionTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexConditionTraversal.java new file mode 100644 index 000000000..e4b66a52c --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexConditionTraversal.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.T; + +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +/** + * The base conditions to {@link VertexTraversal} and {@link VertexUntilTraversal} + */ +public interface VertexConditionTraversal { + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + VertexTraversal has(String propertyKey, Object value); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when the propertyKey is null + */ + VertexTraversal has(String propertyKey); + + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + VertexTraversal has(String propertyKey, P predicate); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + default VertexTraversal has(Supplier propertyKey, Object value) { + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), value); + } + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + default VertexTraversal has(Supplier propertyKey, P predicate) { + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), predicate); + } + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param value the value to the condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + VertexTraversal has(T accessor, Object value); + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param predicate the predicate condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + VertexTraversal has(T accessor, P predicate); + + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + VertexTraversal hasNot(String propertyKey); + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + default VertexTraversal hasNot(Supplier propertyKey) { + requireNonNull(propertyKey, "the supplier is required"); + return hasNot(propertyKey.get()); + } + + /** + * Map the {@link VertexTraversal} to its outgoing adjacent vertices given the edge labels. + * + * @param labels the edge labels to traverse + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + VertexTraversal out(String... labels); + + + /** + * Map the {@link VertexTraversal} to its adjacent vertices given the edge labels. + * + * @param labels the edge labels to traverse + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + VertexTraversal in(String... labels); + + + /** + * Map the {@link VertexTraversal} to its incoming adjacent vertices given the edge labels. + * + * @param labels the edge labels to traverse + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + VertexTraversal both(String... labels); + + /** + * Map the {@link VertexTraversal} to its outgoing adjacent vertices given the edge labels. + * + * @param label the edge labels to traverse + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default VertexTraversal out(Supplier label) { + requireNonNull(label, "the supplier is required"); + return out(label.get()); + } + + /** + * Map the {@link VertexTraversal} to its adjacent vertices given the edge labels. + * + * @param label the edge labels to traverse + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default VertexTraversal in(Supplier label) { + requireNonNull(label, "the supplier is required"); + return in(label.get()); + } + + + /** + * Map the {@link VertexTraversal} to its incoming adjacent vertices given the edge labels. + * + * @param label the edge labels to traverse + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default VertexTraversal both(Supplier label) { + requireNonNull(label, "the supplier is required"); + return both(label.get()); + } + + /** + * Defines Vertex as label condition + * + * @param label the labels in the condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when label is null + */ + VertexTraversal hasLabel(String label); + + /** + * Defines Vertex as label condition + * + * @param the entity type + * @param type reads the {@link jakarta.nosql.Entity} annotation otherwise the {@link Class#getSimpleName()} + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when entityClazz is null + */ + VertexTraversal hasLabel(Class type); + + /** + * Defines Vertex as label condition + * + * @param the entity type + * @param predicate the predicate + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when predicate is null + */ + VertexTraversal hasLabel(P predicate); + + /** + * Defines Vertex as label condition + * + * @param label the labels in the condition + * @return a {@link VertexTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default VertexTraversal hasLabel(Supplier label) { + requireNonNull(label, "the supplier is required"); + return hasLabel(label.get()); + } + + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexRepeatStepTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexRepeatStepTraversal.java new file mode 100644 index 000000000..78f3e4b98 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexRepeatStepTraversal.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +/** + * After the {@link VertexRepeatTraversal} condition the next step is + * {@link VertexRepeatStepTraversal#times(int)} and {@link VertexRepeatStepTraversal#until()} + */ +public interface VertexRepeatStepTraversal { + + /** + * Starts a loop traversal n times + * @param times the repeat times that is required + * @return a {@link VertexTraversal} instance + */ + VertexTraversal times(int times); + + /** + * Define the loop traversal until a defined condition + * @return {@link VertexUntilTraversal} + */ + VertexUntilTraversal until(); +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexRepeatTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexRepeatTraversal.java new file mode 100644 index 000000000..f1ae9e23f --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexRepeatTraversal.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.T; + +import java.util.function.Supplier; + +import static java.util.Objects.requireNonNull; + +/** + * The wrapper step to + * {@link org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal#repeat(org.apache.tinkerpop.gremlin.process.traversal.Traversal)} + * in the Vertex type. + */ +public interface VertexRepeatTraversal { + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + VertexRepeatStepTraversal has(String propertyKey, Object value); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + VertexRepeatStepTraversal has(String propertyKey, P predicate); + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param value the value to the condition + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + default VertexRepeatStepTraversal has(Supplier propertyKey, Object value){ + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), value); + } + + /** + * Adds a equals condition to a query + * + * @param propertyKey the key + * @param predicate the predicate condition + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or predicate condition are null + */ + default VertexRepeatStepTraversal has(Supplier propertyKey, P predicate){ + requireNonNull(propertyKey, "the supplier is required"); + return has(propertyKey.get(), predicate); + } + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param value the value to the condition + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + VertexRepeatStepTraversal has(T accessor, Object value); + + /** + * Adds a equals condition to a query + * + * @param accessor the key + * @param predicate the predicate condition + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when either key or value are null + */ + VertexRepeatStepTraversal has(T accessor, P predicate); + + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + VertexRepeatStepTraversal hasNot(String propertyKey); + + /** + * Defines Vertex has not a property + * + * @param propertyKey the property key + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when propertyKey is null + */ + default VertexRepeatStepTraversal hasNot(Supplier propertyKey){ + requireNonNull(propertyKey, "the supplier is required"); + return hasNot(propertyKey.get()); + } + + /** + * Map the {@link VertexTraversal} to its outgoing adjacent vertices given the edge labels. + * + * @param labels the edge labels to traverse + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + VertexRepeatStepTraversal out(String... labels); + + + /** + * Map the {@link VertexTraversal} to its adjacent vertices given the edge labels. + * + * @param labels the edge labels to traverse + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + VertexRepeatStepTraversal in(String... labels); + + + /** + * Map the {@link VertexTraversal} to its incoming adjacent vertices given the edge labels. + * + * @param labels the edge labels to traverse + * @return a {@link VertexRepeatStepTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + VertexRepeatStepTraversal both(String... labels); + +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexTraversal.java new file mode 100644 index 000000000..e71f95146 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexTraversal.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupGlobalStep; + +import java.util.Optional; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * The Graph Traversal that maps {@link org.apache.tinkerpop.gremlin.structure.Vertex}. + * This Traversal is lazy, in other words, that just run after any finalizing method. + */ +public interface VertexTraversal extends VertexConditionTraversal { + + + /** + * Does a filter predicate based + * + * @param predicate a predicate to apply to each element to determine if it should be included + * @param the type + * @return a {@link EdgeTraversal} with the Vertex predicate + * @throws NullPointerException when predicate is null + */ + VertexTraversal filter(Predicate predicate); + + /** + * Map the {@link EdgeTraversal} to its outgoing incident edges given the edge labels. + * + * @param edgeLabels the edge labels to traverse + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + EdgeTraversal outE(String... edgeLabels); + + + /** + * Map the {@link EdgeTraversal} to its incoming incident edges given the edge labels. + * + * @param edgeLabels the edge labels to traverse + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + EdgeTraversal inE(String... edgeLabels); + + /** + * Map the {@link EdgeTraversal} to its either incoming or outgoing incident edges given the edge labels. + * + * @param edgeLabels the edge labels to traverse + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + EdgeTraversal bothE(String... edgeLabels); + + /** + * Remove all duplicates in the traversal stream up to this point. + * + * @param labels if labels are provided, then the scoped object's labels determine de-duplication. No labels implies current object. + * @return the traversal with an appended {@link DedupGlobalStep}. + */ + VertexTraversal dedup(final String... labels); + + + /** + * Starts the loop traversal graph + * + * @return a {@link VertexRepeatTraversal} + */ + VertexRepeatTraversal repeat(); + + + /** + * Map the {@link EdgeTraversal} to its outgoing incident edges given the edge labels. + * + * @param label the edge labels to traverse + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default EdgeTraversal outE(Supplier label) { + requireNonNull(label, "the supplier is required"); + return outE(label.get()); + } + + + /** + * Map the {@link EdgeTraversal} to its incoming incident edges given the edge labels. + * + * @param label the edge labels to traverse + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default EdgeTraversal inE(Supplier label) { + requireNonNull(label, "the supplier is required"); + return inE(label.get()); + } + + + /** + * Map the {@link EdgeTraversal} to its either incoming or outgoing incident edges given the edge labels. + * + * @param label the edge labels to traverse + * @return a {@link EdgeTraversal} with the new condition + * @throws NullPointerException when has any null element + */ + default EdgeTraversal bothE(Supplier label) { + requireNonNull(label, "the supplier is required"); + return bothE(label.get()); + } + + + /** + * Filter the objects in the traversal by the number of them to pass through the next, where only the first + * {@code n} objects are allowed as defined by the {@code limit} argument. + * + * @param limit the number at which to end the next + * @return a VertexTraversal with the limit + */ + VertexTraversal limit(long limit); + + /** + * Filters out the first {@code n} objects in the traversal. + * + * @param skip the number of objects to skip + * @return a VertexTraversal with skip + */ + VertexTraversal skip(long skip); + + /** + * Returns a VertexTraversal with range defined + * + * @param start the start inclusive + * @param end the end exclusive + * @return a with the range set + */ + VertexTraversal range(long start, long end); + + /** + * Returns the next elements in the traversal. + * If the traversal is empty, then an {@link Optional#empty()} is returned. + * + * @param the entity type + * @return the entity result otherwise {@link Optional#empty()} + */ + Optional next(); + + /** + * Get all the result in the traversal as Stream + * + * @param the entity type + * @return the entity result as {@link Stream} + */ + Stream result(); + + /** + * Concludes the traversal that returns a single result + * + * @param the type + * @return the entity result otherwise {@link Optional#empty()} + */ + Optional singleResult(); + + + /** + * Get the next n-number of results from the traversal. + * + * @param the entity type + * @param limit the limit to result + * @return the entity result as {@link Stream} + */ + Stream next(int limit); + + /** + * Map the {@link org.apache.tinkerpop.gremlin.structure.Element} to a {@link java.util.Map} of the properties key'd according + * to their {@link org.apache.tinkerpop.gremlin.structure.Property#key}. + * If no property keys are provided, then all properties are retrieved. + * + * @param propertyKeys the properties to retrieve + * @return a {@link ValueMapTraversal} instance + */ + ValueMapTraversal valueMap(final String... propertyKeys); + + + /** + * Map the traversal next to its reduction as a sum of the elements + * + * @return the sum + */ + long count(); + + /** + * Defines the order of the Vertex, the property must have in all elements. + * Otherwise, it'll return an exception, as recommendation use + * {@link VertexTraversal#has(String)} before this method + * + * @param property the property to be order + * @return the {@link VertexTraversalOrder} to define the order way + * @throws NullPointerException when the property is null + * @throws IllegalStateException when there any Edge that does not have the property + */ + VertexTraversalOrder orderBy(String property); +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexTraversalOrder.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexTraversalOrder.java new file mode 100644 index 000000000..bfc023bc9 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexTraversalOrder.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +/** + * The traversal order to Vertex + */ +public interface VertexTraversalOrder { + + /** + * Defines the ascending order + * @return the {@link VertexTraversal} ordered ascending + */ + VertexTraversal asc(); + + /** + * Defines the descending order + * @return the {@link VertexTraversal} ordered descending + */ + VertexTraversal desc(); +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexUntilTraversal.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexUntilTraversal.java new file mode 100644 index 000000000..0850da007 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/VertexUntilTraversal.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +/** + * The Vertex until wrapper of + * {@link org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal#until(java.util.function.Predicate)} + */ +public interface VertexUntilTraversal extends VertexConditionTraversal { +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/configuration/GraphSupplier.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/configuration/GraphSupplier.java new file mode 100644 index 000000000..26389f875 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/configuration/GraphSupplier.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.configuration; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.CDI; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.communication.Settings; +import org.eclipse.jnosql.mapping.core.config.MicroProfileSettings; +import org.eclipse.jnosql.communication.graph.GraphConfiguration; +import org.eclipse.jnosql.mapping.reflection.Reflections; + +import java.util.function.Supplier; + +import static org.eclipse.jnosql.mapping.core.config.MappingConfigurations.GRAPH_PROVIDER; + + +@ApplicationScoped +class GraphSupplier implements Supplier { + + @Override + @Produces + @ApplicationScoped + public Graph get(){ + Settings settings = MicroProfileSettings.INSTANCE; + + GraphConfiguration configuration = settings.get(GRAPH_PROVIDER, Class.class) + .filter(GraphConfiguration.class::isAssignableFrom) + .map(c -> { + final Reflections reflections = CDI.current().select(Reflections.class).get(); + return (GraphConfiguration) reflections.newInstance(c); + }).orElseGet(GraphConfiguration::getConfiguration); + + return configuration.apply(settings); + } + + public void close(@Disposes Graph graph) throws Exception { + graph.close(); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/query/RepositoryGraphBean.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/query/RepositoryGraphBean.java new file mode 100644 index 000000000..263f582c1 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/query/RepositoryGraphBean.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.query; + +import jakarta.data.repository.DataRepository; +import jakarta.enterprise.context.spi.CreationalContext; +import org.eclipse.jnosql.mapping.DatabaseQualifier; +import org.eclipse.jnosql.mapping.DatabaseType; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.AbstractBean; +import org.eclipse.jnosql.mapping.core.util.AnnotationLiteralUtil; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata; +import org.eclipse.jnosql.mapping.semistructured.query.SemiStructuredRepositoryProxy; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + +/** + * This class serves as a JNoSQL discovery bean for CDI extension, responsible for registering Repository instances. + * It extends {@link AbstractBean} and is parameterized with type {@code T} representing the repository type. + *

+ * Upon instantiation, it initializes with the provided repository type, provider name, and qualifiers. + * The provider name specifies the database provider for the repository. + *

+ * + * @param the type of the repository + * @see AbstractBean + */ +public class RepositoryGraphBean> extends AbstractBean { + + private final Class type; + + private final Set types; + + private final String provider; + + private final Set qualifiers; + + /** + * Constructor + * + * @param type the tye + * @param provider the provider name, that must be a + */ + @SuppressWarnings("unchecked") + public RepositoryGraphBean(Class type, String provider) { + this.type = (Class) type; + this.types = Collections.singleton(type); + this.provider = provider; + if (provider.isEmpty()) { + this.qualifiers = new HashSet<>(); + qualifiers.add(DatabaseQualifier.ofGraph()); + qualifiers.add(AnnotationLiteralUtil.DEFAULT_ANNOTATION); + qualifiers.add(AnnotationLiteralUtil.ANY_ANNOTATION); + } else { + this.qualifiers = Collections.singleton(DatabaseQualifier.ofGraph(provider)); + } + } + + @Override + public Class getBeanClass() { + return type; + } + + @Override + @SuppressWarnings("unchecked") + public T create(CreationalContext context) { + EntitiesMetadata entities = getInstance(EntitiesMetadata.class); + var template = provider.isEmpty() ? getInstance(GraphTemplate.class) : + getInstance(GraphTemplate.class, DatabaseQualifier.ofGraph(provider)); + + Converters converters = getInstance(Converters.class); + + var handler = new SemiStructuredRepositoryProxy<>(template, + entities, type, converters); + return (T) Proxy.newProxyInstance(type.getClassLoader(), + new Class[]{type}, + handler); + } + + + @Override + public Set getTypes() { + return types; + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + @Override + public String getId() { + return type.getName() + '@' + DatabaseType.GRAPH + "-" + provider; + } + +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/CustomRepositoryGraphBean.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/CustomRepositoryGraphBean.java new file mode 100644 index 000000000..59f89d70e --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/CustomRepositoryGraphBean.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.spi; + +import jakarta.enterprise.context.spi.CreationalContext; +import org.eclipse.jnosql.mapping.DatabaseQualifier; +import org.eclipse.jnosql.mapping.DatabaseType; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.AbstractBean; +import org.eclipse.jnosql.mapping.core.util.AnnotationLiteralUtil; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata; +import org.eclipse.jnosql.mapping.semistructured.query.CustomRepositoryHandler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + + +/** + * This class serves as a JNoSQL discovery bean for CDI extension, responsible for registering Custom Repository instances. + * It extends {@link AbstractBean} and is parameterized with type {@code T} representing the repository type. + *

+ * Upon instantiation, it initializes with the provided repository type, provider name, and qualifiers. + * The provider name specifies the database provider for the repository. + *

+ * + * @param the type of the repository + * @see AbstractBean + */ +public class CustomRepositoryGraphBean extends AbstractBean { + + private final Class type; + + private final Set types; + + private final String provider; + + private final Set qualifiers; + + /** + * Constructor + * + * @param type the tye + * @param provider the provider name, that must be a + */ + @SuppressWarnings("unchecked") + public CustomRepositoryGraphBean(Class type, String provider) { + this.type = (Class) type; + this.types = Collections.singleton(type); + this.provider = provider; + if (provider.isEmpty()) { + this.qualifiers = new HashSet<>(); + qualifiers.add(DatabaseQualifier.ofGraph()); + qualifiers.add(AnnotationLiteralUtil.DEFAULT_ANNOTATION); + qualifiers.add(AnnotationLiteralUtil.ANY_ANNOTATION); + } else { + this.qualifiers = Collections.singleton(DatabaseQualifier.ofGraph(provider)); + } + } + + @Override + public Class getBeanClass() { + return type; + } + + @SuppressWarnings("unchecked") + @Override + public T create(CreationalContext context) { + var entities = getInstance(EntitiesMetadata.class); + var template = provider.isEmpty() ? getInstance(GraphTemplate.class) : + getInstance(GraphTemplate.class, DatabaseQualifier.ofGraph(provider)); + + var converters = getInstance(Converters.class); + + var handler = CustomRepositoryHandler.builder() + .entitiesMetadata(entities) + .template(template) + .customRepositoryType(type) + .converters(converters) + .build(); + + return (T) Proxy.newProxyInstance(type.getClassLoader(), + new Class[]{type}, + handler); + } + + + @Override + public Set getTypes() { + return types; + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + @Override + public String getId() { + return type.getName() + '@' + DatabaseType.GRAPH + "-" + provider; + } + +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/GraphExtension.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/GraphExtension.java new file mode 100644 index 000000000..88dc3e6cf --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/GraphExtension.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.spi; + +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterBeanDiscovery; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessProducer; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.mapping.DatabaseMetadata; +import org.eclipse.jnosql.mapping.Databases; +import org.eclipse.jnosql.mapping.graph.query.RepositoryGraphBean; +import org.eclipse.jnosql.mapping.metadata.ClassScanner; + +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Logger; + +import static org.eclipse.jnosql.mapping.DatabaseType.GRAPH; + +/** + * Extension to start up the GraphTemplate, Repository + * from the {@link org.eclipse.jnosql.mapping.Database} qualifier + */ +public class GraphExtension implements Extension { + + private static final Logger LOGGER = Logger.getLogger(GraphExtension.class.getName()); + + private final Set databases = new HashSet<>(); + + void observes(@Observes final ProcessProducer pp) { + Databases.addDatabase(pp, GRAPH, databases); + } + + void onAfterBeanDiscovery(@Observes final AfterBeanDiscovery afterBeanDiscovery) { + + ClassScanner scanner = ClassScanner.load(); + Set> crudTypes = scanner.repositoriesStandard(); + + + Set> customRepositories = scanner.customRepositories(); + + LOGGER.info(String.format("Processing graph extension: %d databases crud %d found, custom repositories: %d", + databases.size(), crudTypes.size(), customRepositories.size())); + + LOGGER.info("Processing repositories as a Graph implementation: " + crudTypes); + databases.forEach(type -> { + if (!type.getProvider().isBlank()) { + final TemplateBean bean = new TemplateBean(type.getProvider()); + afterBeanDiscovery.addBean(bean); + } + }); + + + crudTypes.forEach(type -> { + if (!databases.contains(DatabaseMetadata.DEFAULT_GRAPH)) { + afterBeanDiscovery.addBean(new RepositoryGraphBean<>(type, "")); + } + databases.forEach(database -> afterBeanDiscovery + .addBean(new RepositoryGraphBean<>(type, database.getProvider()))); + }); + + customRepositories.forEach(type -> { + if (!databases.contains(DatabaseMetadata.DEFAULT_DOCUMENT)) { + afterBeanDiscovery.addBean(new CustomRepositoryGraphBean<>(type, "")); + } + databases.forEach(database -> + afterBeanDiscovery.addBean(new CustomRepositoryGraphBean<>(type, database.getProvider()))); + }); + } +} diff --git a/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/TemplateBean.java b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/TemplateBean.java new file mode 100644 index 000000000..cfbcd1e44 --- /dev/null +++ b/jnosql-tinkerpop/src/main/java/org/eclipse/jnosql/mapping/graph/spi/TemplateBean.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.spi; + +import jakarta.nosql.Template; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.mapping.DatabaseQualifier; +import org.eclipse.jnosql.mapping.DatabaseType; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.graph.GraphTemplateProducer; +import org.eclipse.jnosql.mapping.core.spi.AbstractBean; + +import jakarta.enterprise.context.spi.CreationalContext; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Set; + +class TemplateBean extends AbstractBean { + + private static final Set TYPES = Set.of(GraphTemplate.class, Template.class); + + private final String provider; + + private final Set qualifiers; + + /** + * Constructor + * + * @param provider the provider name, that must be a + */ + public TemplateBean(String provider) { + this.provider = provider; + this.qualifiers = Collections.singleton(DatabaseQualifier.ofGraph(provider)); + } + + @Override + public Class getBeanClass() { + return GraphTemplate.class; + } + + + @Override + public GraphTemplate create(CreationalContext context) { + + GraphTemplateProducer producer = getInstance(GraphTemplateProducer.class); + Graph graph = getGraph(); + return producer.apply(graph); + } + + private Graph getGraph() { + return getInstance(Graph.class, DatabaseQualifier.ofGraph(provider)); + } + + @Override + public Set getTypes() { + return TYPES; + } + + @Override + public Set getQualifiers() { + return qualifiers; + } + + + @Override + public String getId() { + return GraphTemplate.class.getName() + DatabaseType.GRAPH + "-" + provider; + } + +} diff --git a/jnosql-tinkerpop/src/main/resources/META-INF/beans.xml b/jnosql-tinkerpop/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..0340ecdbc --- /dev/null +++ b/jnosql-tinkerpop/src/main/resources/META-INF/beans.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/jnosql-tinkerpop/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension b/jnosql-tinkerpop/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension new file mode 100644 index 000000000..6f10bdf2d --- /dev/null +++ b/jnosql-tinkerpop/src/main/resources/META-INF/services/jakarta.enterprise.inject.spi.Extension @@ -0,0 +1,15 @@ +# +# Copyright (c) 2022 Contributors to the Eclipse Foundation +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# and Apache License v2.0 which accompanies this distribution. +# The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html +# and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. +# +# You may elect to redistribute this code under either of these licenses. +# +# Contributors: +# +# Otavio Santana +# +org.eclipse.jnosql.mapping.graph.spi.GraphExtension \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManagerTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManagerTest.java new file mode 100644 index 000000000..c829cbc62 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManagerTest.java @@ -0,0 +1,465 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import net.datafaker.Faker; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.SoftAssertions; +import org.eclipse.jnosql.communication.semistructured.CommunicationEntity; +import org.eclipse.jnosql.communication.semistructured.DeleteQuery; +import org.eclipse.jnosql.communication.semistructured.Element; +import org.eclipse.jnosql.communication.semistructured.Elements; +import org.eclipse.jnosql.communication.semistructured.SelectQuery; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.jnosql.communication.semistructured.DeleteQuery.delete; +import static org.eclipse.jnosql.communication.semistructured.SelectQuery.select; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DefaultGraphDatabaseManagerTest { + + public static final String COLLECTION_NAME = "person"; + + private GraphDatabaseManager entityManager; + + private final Faker faker = new Faker(); + + @BeforeEach + void setUp(){ + Graph graph = GraphSupplier.INSTANCE.get(); + this.entityManager = GraphDatabaseManager.of(graph); + } + + @BeforeEach + void beforeEach() { + delete().from(COLLECTION_NAME).delete(entityManager); + } + + @Test + void shouldInsertEntity(){ + String name = faker.name().fullName(); + var age = faker.number().randomDigit(); + var entity = CommunicationEntity.of("Person"); + entity.add("name", name); + entity.add("age", age); + var communicationEntity = entityManager.insert(entity); + assertNotNull(communicationEntity); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(communicationEntity.find("name", String.class)).get().isEqualTo(name); + softly.assertThat(communicationEntity.find("age", int.class)).get().isEqualTo(age); + softly.assertThat(communicationEntity.find(DefaultGraphDatabaseManager.ID_PROPERTY)).isPresent(); + }); + } + + @Test + void shouldInsertEntities(){ + String name = faker.name().fullName(); + var age = faker.number().randomDigit(); + var entity = CommunicationEntity.of("Person"); + entity.add("name", name); + entity.add("age", age); + + String name2 = faker.name().fullName(); + var age2 = faker.number().randomDigit(); + var entity2 = CommunicationEntity.of("Person"); + entity2.add("name", name2); + entity2.add("age", age2); + + var communicationEntities = StreamSupport + .stream(entityManager.insert(List.of(entity, entity2)).spliterator(), false).toList(); + + assertNotNull(communicationEntities); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(communicationEntities).hasSize(2); + softly.assertThat(communicationEntities.get(0).find("name", String.class)).get().isEqualTo(name); + softly.assertThat(communicationEntities.get(0).find("age", int.class)).get().isEqualTo(age); + softly.assertThat(communicationEntities.get(0).find(DefaultGraphDatabaseManager.ID_PROPERTY)).isPresent(); + + softly.assertThat(communicationEntities.get(1).find("name", String.class)).get().isEqualTo(name2); + softly.assertThat(communicationEntities.get(1).find("age", int.class)).get().isEqualTo(age2); + softly.assertThat(communicationEntities.get(1).find(DefaultGraphDatabaseManager.ID_PROPERTY)).isPresent(); + }); + + } + + @Test + void shouldInsert() { + var entity = getEntity(); + var documentEntity = entityManager.insert(entity); + assertTrue(documentEntity.elements().stream().map(Element::name).anyMatch(s -> s.equals("_id"))); + } + + @Test + void shouldThrowExceptionWhenInsertWithTTL() { + var entity = getEntity(); + var ttl = Duration.ofSeconds(10); + assertThrows(UnsupportedOperationException.class, () -> entityManager.insert(entity, ttl)); + } + + @Test + void shouldUpdate() { + var entity = entityManager.insert(getEntity()); + var newField = Elements.of("newField", "10"); + entity.add(newField); + var updated = entityManager.update(entity); + assertEquals(newField, updated.find("newField").orElseThrow()); + } + + @Test + void shouldRemoveEntity() { + var documentEntity = entityManager.insert(getEntity()); + + Optional id = documentEntity.find("_id"); + var query = select().from(COLLECTION_NAME) + .where("_id").eq(id.orElseThrow().get()) + .build(); + var deleteQuery = delete().from(COLLECTION_NAME).where("_id") + .eq(id.get().get()) + .build(); + + entityManager.delete(deleteQuery); + assertTrue(entityManager.select(query).findAny().isEmpty()); + } + + @Test + void shouldFindDocument() { + var entity = entityManager.insert(getEntity()); + Optional id = entity.find("_id"); + + var query = select().from(COLLECTION_NAME) + .where("_id").eq(id.orElseThrow().get()) + .build(); + + var entities = entityManager.select(query).collect(Collectors.toList()); + assertFalse(entities.isEmpty()); + assertThat(entities).contains(entity); + } + + @Test + void shouldFindDocument2() { + var entity = entityManager.insert(getEntity()); + Optional id = entity.find("_id"); + + var query = select().from(COLLECTION_NAME) + .where("name").eq("Poliana") + .and("city").eq("Salvador").and("_id").eq(id.orElseThrow().get()) + .build(); + + List entities = entityManager.select(query).collect(Collectors.toList()); + assertFalse(entities.isEmpty()); + assertThat(entities).contains(entity); + } + + @Test + void shouldFindDocument3() { + var entity = entityManager.insert(getEntity()); + Optional id = entity.find("_id"); + var query = select().from(COLLECTION_NAME) + .where("name").eq("Poliana") + .or("city").eq("Salvador") + .and(id.orElseThrow().name()).eq(id.get().get()) + .build(); + + List entities = entityManager.select(query).collect(Collectors.toList()); + assertFalse(entities.isEmpty()); + assertThat(entities).contains(entity); + } + + @Test + void shouldFindDocumentGreaterThan() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false).toList(); + + var query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(2, entitiesFound.size()); + assertThat(entitiesFound).isNotIn(entities.get(0)); + } + + @Test + void shouldFindDocumentGreaterEqualsThan() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false).toList(); + + var query = select().from(COLLECTION_NAME) + .where("age").gte(23) + .and("type").eq("V") + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(2, entitiesFound.size()); + assertThat(entitiesFound).isNotIn(entities.get(0)); + } + + @Test + void shouldFindDocumentLesserThan() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false) + .toList(); + + var query = select().from(COLLECTION_NAME) + .where("age").lt(23) + .and("type").eq("V") + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(1, entitiesFound.size()); + assertThat(entitiesFound).contains(entities.get(0)); + } + + @Test + void shouldFindDocumentLesserEqualsThan() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false).toList(); + + SelectQuery query = select().from(COLLECTION_NAME) + .where("age").lte(23) + .and("type").eq("V") + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(2, entitiesFound.size()); + assertThat(entitiesFound).contains(entities.get(0), entities.get(2)); + } + + + @Test + void shouldFindDocumentIn() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false).toList(); + + SelectQuery query = select().from(COLLECTION_NAME) + .where("location").in(asList("BR", "US")) + .and("type").eq("V") + .build(); + + Assertions.assertThat(entityManager.select(query).toList()).containsAll(entities); + } + + @Test + void shouldFindDocumentBetween() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + entityManager.insert(getEntitiesWithValues()); + + SelectQuery query = select().from(COLLECTION_NAME) + .where("age").between(22, 25) + .build(); + + + var entities = entityManager.select(query).toList(); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(entities).hasSize(2); + softly.assertThat(entities).extracting(e -> e.find("age").orElseThrow().get(Integer.class)) + .contains(22, 23); + softly.assertThat(entities).extracting(e -> e.find("name").orElseThrow().get(String.class)) + .contains("Luna", "Lucas"); + }); + + + + } + + @Test + void shouldFindDocumentStart() { + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false).toList(); + + SelectQuery query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .skip(1L) + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(1, entitiesFound.size()); + assertThat(entitiesFound).isNotIn(entities.get(0)); + + query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .skip(2L) + .build(); + + entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertTrue(entitiesFound.isEmpty()); + + } + + @Test + void shouldFindDocumentLimit() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + List entities = StreamSupport.stream(entitiesSaved.spliterator(), false).toList(); + + SelectQuery query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .limit(1L) + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(1, entitiesFound.size()); + assertThat(entitiesFound).isNotIn(entities.get(0)); + + query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .limit(2L) + .build(); + + entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(2, entitiesFound.size()); + + } + + @Test + void shouldFindDocumentSort() { + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).where("type").eq("V").build(); + entityManager.delete(deleteQuery); + Iterable entitiesSaved = entityManager.insert(getEntitiesWithValues()); + + SelectQuery query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .orderBy("age").asc() + .build(); + + List entitiesFound = entityManager.select(query).collect(Collectors.toList()); + assertEquals(2, entitiesFound.size()); + List ages = entitiesFound.stream() + .map(e -> e.find("age").orElseThrow().get(Integer.class)) + .collect(Collectors.toList()); + + assertThat(ages).contains(23, 25); + + query = select().from(COLLECTION_NAME) + .where("age").gt(22) + .and("type").eq("V") + .orderBy("age").desc() + .build(); + + entitiesFound = entityManager.select(query).toList(); + ages = entitiesFound.stream() + .map(e -> e.find("age").orElseThrow().get(Integer.class)) + .collect(Collectors.toList()); + assertEquals(2, entitiesFound.size()); + assertThat(ages).contains(25, 23); + + } + + @Test + void shouldFindAll() { + entityManager.insert(getEntity()); + SelectQuery query = select().from(COLLECTION_NAME).build(); + List entities = entityManager.select(query).toList(); + assertFalse(entities.isEmpty()); + } + + @Test + void shouldDeleteAll() { + entityManager.insert(getEntity()); + SelectQuery query = select().from(COLLECTION_NAME).build(); + List entities = entityManager.select(query).collect(Collectors.toList()); + assertFalse(entities.isEmpty()); + DeleteQuery deleteQuery = delete().from(COLLECTION_NAME).build(); + entityManager.delete(deleteQuery); + entities = entityManager.select(query).toList(); + assertTrue(entities.isEmpty()); + } + + @Test + void shouldFindAllByFields() { + entityManager.insert(getEntity()); + SelectQuery query = select("name").from(COLLECTION_NAME).build(); + List entities = entityManager.select(query).toList(); + assertFalse(entities.isEmpty()); + final CommunicationEntity entity = entities.get(0); + assertEquals(3, entity.size()); + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(entity.find("name")).isPresent(); + softly.assertThat(entity.find("_id")).isPresent(); + softly.assertThat(entity.find("city")).isPresent(); + }); + } + + + + + private CommunicationEntity getEntity() { + CommunicationEntity entity = CommunicationEntity.of(COLLECTION_NAME); + Map map = new HashMap<>(); + map.put("name", "Poliana"); + map.put("city", "Salvador"); + List documents = Elements.of(map); + documents.forEach(entity::add); + return entity; + } + + private List getEntitiesWithValues() { + CommunicationEntity lucas = CommunicationEntity.of(COLLECTION_NAME); + lucas.add(Element.of("name", "Lucas")); + lucas.add(Element.of("age", 22)); + lucas.add(Element.of("location", "BR")); + lucas.add(Element.of("type", "V")); + + CommunicationEntity luna = CommunicationEntity.of(COLLECTION_NAME); + luna.add(Element.of("name", "Luna")); + luna.add(Element.of("age", 23)); + luna.add(Element.of("location", "US")); + luna.add(Element.of("type", "V")); + + CommunicationEntity otavio = CommunicationEntity.of(COLLECTION_NAME); + otavio.add(Element.of("name", "Otavio")); + otavio.add(Element.of("age", 25)); + otavio.add(Element.of("location", "BR")); + otavio.add(Element.of("type", "V")); + + + return asList(lucas, otavio, luna); + } +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/communication/graph/GraphSupplier.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/communication/graph/GraphSupplier.java new file mode 100644 index 000000000..b87bf999e --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/communication/graph/GraphSupplier.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.communication.graph; + +import org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraph; +import org.apache.tinkerpop.gremlin.structure.Graph; + +import java.io.File; +import java.util.function.Supplier; +import java.util.logging.Logger; + +import static java.lang.System.currentTimeMillis; + +public enum GraphSupplier implements Supplier { + INSTANCE; + + private static final Logger LOGGER = Logger.getLogger(GraphSupplier.class.getName()); + + private final String directory; + + private final Graph graph; + + { + this.directory = new File("").getAbsolutePath() + "/target/jnosql-communication-graph/" + currentTimeMillis() + "/"; + graph = Neo4jGraph.open(directory); + } + + @Override + public Graph get() { + LOGGER.info("Starting Graph database at directory: " + directory); + return graph; + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplateTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplateTest.java new file mode 100644 index 000000000..629d1c219 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplateTest.java @@ -0,0 +1,548 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.EmptyResultException; +import jakarta.data.exceptions.NonUniqueResultException; +import org.eclipse.jnosql.mapping.PreparedStatement; +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.Transaction; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.assertj.core.api.SoftAssertions; +import org.eclipse.jnosql.mapping.IdNotFoundException; +import org.eclipse.jnosql.mapping.graph.entities.Animal; +import org.eclipse.jnosql.mapping.graph.entities.Book; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.graph.entities.WrongEntity; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public abstract class AbstractGraphTemplateTest { + + protected abstract Graph getGraph(); + + protected abstract GraphTemplate getGraphTemplate(); + + @AfterEach + void after() { + getGraph().traversal().V().toList().forEach(Vertex::remove); + getGraph().traversal().E().toList().forEach(Edge::remove); + } + + @Test + void shouldReturnErrorWhenEntityIsNull() { + assertThrows(NullPointerException.class, () -> getGraphTemplate().insert(null)); + } + + @Test + void shouldInsertAnEntity() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Person updated = getGraphTemplate().insert(person); + + getGraphTemplate().delete(updated.getId()); + } + + @Test + void shouldReturnErrorWhenInsertWithTTL() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> getGraphTemplate().insert(person, Duration.ZERO)); + } + + @Test + void shouldReturnErrorWhenInsertIterableWithTTL() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Assertions.assertThrows(UnsupportedOperationException.class, + () -> getGraphTemplate().insert(Collections.singleton(person), Duration.ZERO)); + } + + @Test + void shouldInsertEntities() { + Person otavio = Person.builder().withAge() + .withName("Otavio").build(); + + Person poliana = Person.builder().withAge() + .withName("Poliana").build(); + + final Iterable people = getGraphTemplate() + .insert(Arrays.asList(otavio, poliana)); + + final boolean allHasId = StreamSupport.stream(people.spliterator(), false) + .map(Person::getId) + .allMatch(Objects::nonNull); + assertTrue(allHasId); + } + + @Test + void shouldMergeOnInsert() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Person updated = getGraphTemplate().insert(person); + assertSame(person, updated); + } + + @Test + void shouldGetErrorWhenIdIsNullWhenUpdate() { + assertThrows(EmptyResultException.class, () -> { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + getGraphTemplate().update(person); + }); + } + + @Test + void shouldGetErrorWhenEntityIsNotSavedYet() { + assertThrows(EmptyResultException.class, () -> { + Person person = Person.builder().withAge() + .withId(10L) + .withName("Otavio").build(); + + getGraphTemplate().update(person); + }); + } + + @Test + void shouldUpdate() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Person updated = getGraphTemplate().insert(person); + Person newPerson = Person.builder() + .withAge() + .withId(updated.getId()) + .withName("Otavio Updated").build(); + + Person update = getGraphTemplate().update(newPerson); + + assertEquals(newPerson, update); + + getGraphTemplate().delete(update.getId()); + } + + @Test + void shouldUpdateEntities() { + Person otavio = Person.builder().withAge() + .withName("Otavio").build(); + + Person poliana = Person.builder().withAge() + .withName("Poliana").build(); + + final Iterable insertPeople = getGraphTemplate().insert(Arrays.asList(otavio, poliana)); + + final List newPeople = StreamSupport.stream(insertPeople.spliterator(), false) + .map(p -> Person.builder().withAge().withId(p.getId()).withName(p.getName() + " updated").build()) + .collect(toList()); + + final Iterable update = getGraphTemplate().update(newPeople); + + final boolean allUpdated = StreamSupport.stream(update.spliterator(), false) + .map(Person::getName).allMatch(name -> name.contains(" updated")); + + assertTrue(allUpdated); + } + + + @Test + void shouldMergeOnUpdate() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Person updated = getGraphTemplate().insert(person); + Person newPerson = Person.builder() + .withAge() + .withId(updated.getId()) + .withName("Otavio Updated").build(); + + Person update = getGraphTemplate().update(newPerson); + + assertSame(update, newPerson); + } + + @Test + void shouldReturnErrorInFindWhenIdIsNull() { + assertThrows(NullPointerException.class, () -> getGraphTemplate().find(null)); + } + + @Test + void shouldFindAnEntity() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + Person updated = getGraphTemplate().insert(person); + Optional personFound = getGraphTemplate().find(updated.getId()); + + assertTrue(personFound.isPresent()); + assertEquals(updated, personFound.get()); + + getGraphTemplate().delete(updated.getId()); + } + + @Test + void shouldNotFindAnEntity() { + Optional personFound = getGraphTemplate().find(0L); + assertFalse(personFound.isPresent()); + } + + @Test + void shouldDeleteById() { + + Person person = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + assertTrue(getGraphTemplate().find(person.getId()).isPresent()); + getGraphTemplate().delete(person.getId()); + assertFalse(getGraphTemplate().find(person.getId()).isPresent()); + } + + + @Test + void shouldDeleteAnEntityFromTemplate() { + + Person person = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + assertTrue(getGraphTemplate().find(person.getId()).isPresent()); + getGraphTemplate().delete(Person.class, person.getId()); + assertFalse(getGraphTemplate().find(person.getId()).isPresent()); + } + + @Test + void shouldNotDeleteAnEntityFromTemplate() { + + Person person = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + assertTrue(getGraphTemplate().find(person.getId()).isPresent()); + getGraphTemplate().delete(Book.class, person.getId()); + assertTrue(getGraphTemplate().find(person.getId()).isPresent()); + getGraphTemplate().delete(Person.class, person.getId()); + assertFalse(getGraphTemplate().find(person.getId()).isPresent()); + } + + + @Test + void shouldDeleteEntitiesById() { + + Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + Person poliana = getGraphTemplate().insert(Person.builder().withAge() + .withName("Poliana").build()); + + assertTrue(getGraphTemplate().find(otavio.getId()).isPresent()); + getGraphTemplate().delete(Arrays.asList(otavio.getId(), poliana.getId())); + assertFalse(getGraphTemplate().find(otavio.getId()).isPresent()); + assertFalse(getGraphTemplate().find(poliana.getId()).isPresent()); + } + + @Test + void shouldReturnErrorWhenGetEdgesIdHasNullId() { + assertThrows(NullPointerException.class, () -> getGraphTemplate().edgesById(null, Direction.BOTH)); + } + + @Test + void shouldReturnErrorWhenGetEdgesIdHasNullDirection() { + assertThrows(NullPointerException.class, () -> getGraphTemplate().edgesById(10, null)); + } + + @Test + void shouldReturnEmptyWhenVertexDoesNotExist() { + Collection edges = getGraphTemplate().edgesById(10, Direction.BOTH); + assertTrue(edges.isEmpty()); + } + + @Test + void shouldReturnEdgesById() { + Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + Animal dog = getGraphTemplate().insert(new Animal("dog")); + Book cleanCode = getGraphTemplate().insert(Book.builder().withName("Clean code").build()); + + EdgeEntity likes = getGraphTemplate().edge(otavio, "likes", dog); + EdgeEntity reads = getGraphTemplate().edge(otavio, "reads", cleanCode); + + Collection edgesById = getGraphTemplate().edgesById(otavio.getId(), Direction.BOTH); + Collection edgesById1 = getGraphTemplate().edgesById(otavio.getId(), Direction.BOTH, "reads"); + Collection edgesById2 = getGraphTemplate().edgesById(otavio.getId(), Direction.BOTH, () -> "likes"); + Collection edgesById3 = getGraphTemplate().edgesById(otavio.getId(), Direction.OUT); + Collection edgesById4 = getGraphTemplate().edgesById(cleanCode.getId(), Direction.IN); + + assertEquals(edgesById, edgesById3); + assertThat(edgesById).contains(likes, reads); + assertThat(edgesById1).contains(reads); + assertThat(edgesById2).contains(likes); + assertThat(edgesById4).contains(reads); + + } + + @Test + void shouldDeleteEdge() { + Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + Animal dog = getGraphTemplate().insert(new Animal("Ada")); + + EdgeEntity likes = getGraphTemplate().edge(otavio, "likes", dog); + + final Optional edge = getGraphTemplate().edge(likes.id()); + Assertions.assertTrue(edge.isPresent()); + + getGraphTemplate().deleteEdge(likes.id()); + assertFalse(getGraphTemplate().edge(likes.id()).isPresent()); + } + + @Test + void shouldDeleteEdges() { + Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + Animal dog = getGraphTemplate().insert(new Animal("Ada")); + Book cleanCode = getGraphTemplate().insert(Book.builder().withName("Clean code").build()); + + EdgeEntity likes = getGraphTemplate().edge(otavio, "likes", dog); + EdgeEntity reads = getGraphTemplate().edge(otavio, "reads", cleanCode); + + final Optional edge = getGraphTemplate().edge(likes.id()); + Assertions.assertTrue(edge.isPresent()); + + getGraphTemplate().deleteEdge(Arrays.asList(likes.id(), reads.id())); + assertFalse(getGraphTemplate().edge(likes.id()).isPresent()); + assertFalse(getGraphTemplate().edge(reads.id()).isPresent()); + } + + @Test + void shouldReturnErrorWhenGetEdgesHasNullId() { + assertThrows(NullPointerException.class, () -> getGraphTemplate().edges(null, Direction.BOTH)); + } + + @Test + void shouldReturnErrorWhenGetEdgesHasNullId2() { + Person otavio = Person.builder().withAge().withName("Otavio").build(); + Collection edges = getGraphTemplate().edges(otavio, Direction.BOTH); + assertThat(edges).isEmpty(); + } + + @Test + void shouldReturnErrorWhenGetEdgesHasNullDirection() { + assertThrows(NullPointerException.class, () -> { + Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + getGraphTemplate().edges(otavio, null); + }); + } + + @Test + void shouldReturnEmptyWhenEntityDoesNotExist() { + Person otavio = Person.builder().withAge().withName("Otavio").withId(10L).build(); + Collection edges = getGraphTemplate().edges(otavio, Direction.BOTH); + assertTrue(edges.isEmpty()); + } + + + @Test + void shouldReturnEdges() { + Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + Animal dog = getGraphTemplate().insert(new Animal("dog")); + Book cleanCode = getGraphTemplate().insert(Book.builder().withName("Clean code").build()); + + EdgeEntity likes = getGraphTemplate().edge(otavio, "likes", dog); + EdgeEntity reads = getGraphTemplate().edge(otavio, "reads", cleanCode); + + Collection edgesById = getGraphTemplate().edges(otavio, Direction.BOTH); + Collection edgesById1 = getGraphTemplate().edges(otavio, Direction.BOTH, "reads"); + Collection edgesById2 = getGraphTemplate().edges(otavio, Direction.BOTH, () -> "likes"); + Collection edgesById3 = getGraphTemplate().edges(otavio, Direction.OUT); + Collection edgesById4 = getGraphTemplate().edges(cleanCode, Direction.IN); + + SoftAssertions.assertSoftly(soft ->{ + soft.assertThat(edgesById).contains(likes, reads); + soft.assertThat(edgesById1).contains(reads); + soft.assertThat(edgesById2).contains(likes); + soft.assertThat(edgesById3).contains(likes, reads); + soft.assertThat(edgesById4).contains(reads); + }); + } + + @Test + void shouldGetTransaction() { + Transaction transaction = getGraphTemplate().transaction(); + assertNotNull(transaction); + } + + @Test + void shouldExecuteQuery() { + Person person = Person.builder().withAge() + .withName("Otavio").build(); + getGraphTemplate().insert(person); + List people = getGraphTemplate() + .gremlin("g.V().hasLabel('Person')") + .toList(); + assertThat(people.stream().map(Person::getName).collect(toList())).contains("Otavio"); + } + + @Test + void shouldReturnEmpty() { + Optional person = getGraphTemplate().gremlinSingleResult("g.V().hasLabel('Person')"); + assertFalse(person.isPresent()); + } + + @Test + void shouldReturnOneElement() { + Person otavio = Person.builder().withAge() + .withName("Otavio").build(); + getGraphTemplate().insert(otavio); + Optional person = getGraphTemplate().gremlinSingleResult("g.V().hasLabel('Person')"); + assertTrue(person.isPresent()); + } + + @Test + void shouldReturnErrorWhenHasNoneThanOneElement() { + + getGraphTemplate().insert(Person.builder().withAge().withName("Otavio").build()); + getGraphTemplate().insert(Person.builder().withAge().withName("Poliana").build()); + assertThrows(NonUniqueResultException.class, () -> getGraphTemplate().gremlinSingleResult("g.V().hasLabel('Person')")); + } + + @Test + void shouldExecutePrepareStatement() { + getGraphTemplate().insert(Person.builder().withAge().withName("Otavio").build()); + PreparedStatement prepare = getGraphTemplate().gremlinPrepare("g.V().hasLabel(@param)"); + prepare.bind("param", "Person"); + List people = prepare.result().toList(); + assertThat(people.stream().map(Person::getName).collect(toList())).contains("Otavio"); + } + + @Test + void shouldExecutePrepareStatementSingleton() { + getGraphTemplate().insert(Person.builder().withAge().withName("Otavio").build()); + PreparedStatement prepare = getGraphTemplate().gremlinPrepare("g.V().hasLabel(@param)"); + prepare.bind("param", "Person"); + Optional otavio = prepare.singleResult(); + assertTrue(otavio.isPresent()); + } + + @Test + void shouldExecutePrepareStatementSingletonEmpty() { + PreparedStatement prepare = getGraphTemplate().gremlinPrepare("g.V().hasLabel(@param)"); + prepare.bind("param", "Person"); + Optional otavio = prepare.singleResult(); + assertFalse(otavio.isPresent()); + } + + @Test + void shouldExecutePrepareStatementWithErrorWhenThereIsMoreThanOneResult() { + getGraphTemplate().insert(Person.builder().withAge().withName("Otavio").build()); + getGraphTemplate().insert(Person.builder().withAge().withName("Poliana").build()); + PreparedStatement prepare = getGraphTemplate().gremlinPrepare("g.V().hasLabel(@param)"); + prepare.bind("param", "Person"); + assertThrows(NonUniqueResultException.class, prepare::singleResult); + } + + @Test + void shouldCount() { + getGraphTemplate().insert(Person.builder().withAge().withName("Otavio").build()); + getGraphTemplate().insert(Person.builder().withAge().withName("Poliana").build()); + assertEquals(2L, getGraphTemplate().count("Person")); + } + + @Test + void shouldCountFromEntity() { + getGraphTemplate().insert(Person.builder().withAge().withName("Otavio").build()); + getGraphTemplate().insert(Person.builder().withAge().withName("Poliana").build()); + assertEquals(2L, getGraphTemplate().count(Person.class)); + } + + + @Test + void shouldFindById() { + final Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + final Optional person = getGraphTemplate().find(Person.class, otavio.getId()); + assertNotNull(person); + assertTrue(person.isPresent()); + assertEquals(otavio.getName(), person.map(Person::getName).get()); + } + + @Test + void shouldFindAll() { + final Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + List people = getGraphTemplate().findAll(Person.class).toList(); + + assertThat(people).hasSize(1) + .map(Person::getName) + .contains("Otavio"); + } + + @Test + void shouldDeleteAll() { + final Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + List people = getGraphTemplate().findAll(Person.class).toList(); + + assertThat(people).hasSize(1) + .map(Person::getName) + .contains("Otavio"); + + getGraphTemplate().deleteAll(Person.class); + people = getGraphTemplate().findAll(Person.class).toList(); + + assertThat(people).isEmpty(); + } + + @Test + void shouldReturnEmptyWhenFindByIdNotFound() { + + final Optional person = getGraphTemplate().find(Person.class, -2L); + assertNotNull(person); + assertFalse(person.isPresent()); + } + + @Test + void shouldUpdateNullValues(){ + final Person otavio = getGraphTemplate().insert(Person.builder().withAge() + .withName("Otavio").build()); + + assertEquals("Otavio", otavio.getName()); + otavio.setName(null); + final Person person = getGraphTemplate().update(otavio); + assertNull(person.getName()); + + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/AbstractTraversalTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/AbstractTraversalTest.java new file mode 100644 index 000000000..6b379ce51 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/AbstractTraversalTest.java @@ -0,0 +1,96 @@ +/* + * + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + * + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.graph.entities.Book; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +public abstract class AbstractTraversalTest { + + static final String READS = "reads"; + + @Inject + protected GraphTemplate graphTemplate; + + @Inject + protected Graph graph; + + + protected Person otavio; + protected Person poliana; + protected Person paulo; + + protected Book shack; + protected Book license; + protected Book effectiveJava; + + protected EdgeEntity reads; + protected EdgeEntity reads2; + protected EdgeEntity reads3; + + @BeforeEach + public void setUp() { + + graph.traversal().V().toList().forEach(Vertex::remove); + graph.traversal().E().toList().forEach(Edge::remove); + + otavio = graphTemplate.insert(Person.builder().withAge(27) + .withName("Otavio").build()); + poliana = graphTemplate.insert(Person.builder().withAge(26) + .withName("Poliana").build()); + paulo = graphTemplate.insert(Person.builder().withAge(50) + .withName("Paulo").build()); + + shack = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + license = graphTemplate.insert(Book.builder().withAge(2013).withName("Software License").build()); + effectiveJava = graphTemplate.insert(Book.builder().withAge(2001).withName("Effective Java").build()); + + + reads = graphTemplate.edge(otavio, READS, effectiveJava); + reads2 = graphTemplate.edge(poliana, READS, shack); + reads3 = graphTemplate.edge(paulo, READS, license); + + reads.add("motivation", "hobby"); + reads.add("language", "Java"); + reads2.add("motivation", "love"); + reads3.add("motivation", "job"); + } + + @AfterEach + public void after() { + graphTemplate.delete(otavio.getId()); + graphTemplate.delete(poliana.getId()); + graphTemplate.delete(paulo.getId()); + + graphTemplate.deleteEdge(shack.getId()); + graphTemplate.deleteEdge(license.getId()); + graphTemplate.deleteEdge(effectiveJava.getId()); + + reads.delete(); + reads2.delete(); + reads3.delete(); + + graph.traversal().V().toList().forEach(Vertex::remove); + graph.traversal().E().toList().forEach(Edge::remove); + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/BookRepository.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/BookRepository.java new file mode 100644 index 000000000..5fd67689b --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/BookRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + + +import jakarta.data.repository.BasicRepository; +import jakarta.data.repository.Repository; +import org.eclipse.jnosql.mapping.graph.entities.Book; + +@Repository +public interface BookRepository extends BasicRepository { +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/BookTemplateTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/BookTemplateTest.java new file mode 100644 index 000000000..f44e44e89 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/BookTemplateTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.apache.tinkerpop.gremlin.structure.Transaction.Status; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.entities.Book; +import org.eclipse.jnosql.mapping.graph.entities.BookTemplate; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicReference; + +import static org.apache.tinkerpop.gremlin.structure.Transaction.Status.COMMIT; +import static org.apache.tinkerpop.gremlin.structure.Transaction.Status.ROLLBACK; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class BookTemplateTest { + + @Inject + private BookTemplate template; + + @Inject + private Graph graph; + + @Test + void shouldSaveWithTransaction() { + AtomicReference status = new AtomicReference<>(); + + Book book = Book.builder().withName("The Book").build(); + Transaction transaction = graph.tx(); + transaction.addTransactionListener(status::set); + template.insert(book); + assertFalse(transaction.isOpen()); + assertEquals(COMMIT, status.get()); + } + + @Test + void shouldSaveWithRollback() { + AtomicReference status = new AtomicReference<>(); + + Book book = Book.builder().withName("The Book").build(); + Transaction transaction = graph.tx(); + transaction.addTransactionListener(status::set); + try { + template.insertException(book); + assert false; + }catch (Exception ignored){ + + } + + assertFalse(transaction.isOpen()); + assertEquals(ROLLBACK, status.get()); + } + + @Test + void shouldUseAutomaticNormalTransaction() { + AtomicReference status = new AtomicReference<>(); + + Book book = Book.builder().withName("The Book").build(); + Transaction transaction = graph.tx(); + transaction.addTransactionListener(status::set); + assertNull(status.get()); + template.normalInsertion(book); + assertEquals(COMMIT, status.get()); + assertFalse(transaction.isOpen()); + } +} + diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversalTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversalTest.java new file mode 100644 index 000000000..44acf592d --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeTraversalTest.java @@ -0,0 +1,540 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.T; +import org.assertj.core.api.SoftAssertions; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.entities.Animal; +import org.eclipse.jnosql.mapping.graph.entities.Book; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class DefaultEdgeTraversalTest extends AbstractTraversalTest { + + @Test + void shouldReturnErrorWhenEdgeIdIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalEdge(null)); + } + + @Test + void shouldReturnEdgeId() { + Optional edgeEntity = graphTemplate.traversalEdge(reads.id()) + .next(); + + assertTrue(edgeEntity.isPresent()); + assertEquals(reads.id(), edgeEntity.get().id()); + } + + @Test + void shouldReturnOutE() { + List edges = graphTemplate.traversalVertex().outE(READS) + .stream() + .collect(toList()); + + assertEquals(3, edges.size()); + assertThat(edges).contains(reads, reads2, reads3); + } + + @Test + void shouldReturnOutEWithSupplier() { + List edges = graphTemplate.traversalVertex().outE(() -> READS) + .stream() + .collect(toList()); + + assertEquals(3, edges.size()); + assertThat(edges).contains(reads, reads2, reads3); + } + + @Test + void shouldReturnErrorOutEWhenIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().outE((String) null) + .stream() + .collect(toList())); + } + + @Test + void shouldReturnInE() { + List edges = graphTemplate.traversalVertex().inE(READS) + .stream() + .collect(toList()); + + assertEquals(3, edges.size()); + assertThat(edges).contains(reads, reads2, reads3); + } + + @Test + void shouldReturnInEWitSupplier() { + List edges = graphTemplate.traversalVertex().inE(() -> READS) + .stream() + .collect(toList()); + + assertEquals(3, edges.size()); + assertThat(edges).contains(reads, reads2, reads3); + } + + + @Test + void shouldReturnErrorWhenInEIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().inE((String) null) + .stream() + .collect(toList())); + + } + + @Test + void shouldReturnBothE() { + List edges = graphTemplate.traversalVertex().bothE(READS) + .stream() + .toList(); + + assertEquals(6, edges.size()); + } + + @Test + void shouldReturnBothEWithSupplier() { + List edges = graphTemplate.traversalVertex().bothE(() -> READS) + .stream() + .toList(); + + assertEquals(6, edges.size()); + } + + @Test + void shouldReturnErrorWhenBothEIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().bothE((String) null) + .stream() + .collect(toList())); + } + + + @Test + void shouldReturnOut() { + List people = graphTemplate.traversalVertex().outE(READS).outV().result().collect(toList()); + assertEquals(3, people.size()); + assertThat(people).contains(poliana, otavio, paulo); + } + + @Test + void shouldReturnIn() { + List books = graphTemplate.traversalVertex().outE(READS).inV().result().collect(toList()); + assertEquals(3, books.size()); + assertThat(books).contains(shack, effectiveJava, license); + } + + + @Test + void shouldReturnBoth() { + List entities = graphTemplate.traversalVertex().outE(READS).bothV().result().collect(toList()); + assertEquals(6, entities.size()); + assertThat(entities).contains(shack, effectiveJava, license, paulo, otavio, poliana); + } + + + @Test + void shouldHasPropertyFromAccessor() { + + Optional edgeEntity = graphTemplate.traversalVertex() + .outE(READS) + .has(T.id, "notFound").next(); + + assertFalse(edgeEntity.isPresent()); + } + + + @Test + void shouldHasProperty() { + Optional edgeEntity = graphTemplate.traversalVertex() + .outE(READS) + .has("motivation", "hobby").next(); + + assertTrue(edgeEntity.isPresent()); + assertEquals(reads.id(), edgeEntity.get().id()); + } + + @Test + void shouldHasSupplierProperty() { + Optional edgeEntity = graphTemplate.traversalVertex() + .outE(READS) + .has(() -> "motivation", "hobby").next(); + + assertTrue(edgeEntity.isPresent()); + assertEquals(reads.id(), edgeEntity.get().id()); + } + + @Test + void shouldHasPropertyPredicate() { + + Optional edgeEntity = graphTemplate.traversalVertex() + .outE(READS) + .has("motivation", P.eq("hobby")).next(); + + assertTrue(edgeEntity.isPresent()); + assertEquals(reads.id(), edgeEntity.get().id()); + } + + + @Test + void shouldHasPropertyKeySupplierPredicate() { + + Optional edgeEntity = graphTemplate.traversalVertex() + .outE(READS) + .has(() -> "motivation", P.eq("hobby")).next(); + + assertTrue(edgeEntity.isPresent()); + assertEquals(reads.id(), edgeEntity.get().id()); + } + + + @Test + void shouldReturnErrorWhenHasPropertyWhenKeyIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex() + .outE(READS) + .has((String) null, "hobby").next()); + } + + @Test + void shouldReturnErrorWhenHasPropertyWhenValueIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex() + .outE(READS) + .has("motivation", null).next()); + } + + @Test + void shouldHasNot() { + List edgeEntities = graphTemplate.traversalVertex() + .outE(READS).hasNot("language") + .stream() + .toList(); + + assertEquals(2, edgeEntities.size()); + } + + @Test + void shouldCount() { + long count = graphTemplate.traversalVertex().outE(READS).count(); + assertEquals(3L, count); + } + + @Test + void shouldReturnZeroWhenCountIsEmpty() { + long count = graphTemplate.traversalVertex().outE("WRITES").count(); + assertEquals(0L, count); + } + + @Test + void shouldReturnErrorWhenHasNotIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().outE(READS).hasNot((String) null)); + } + + + @Test + void shouldDefinesLimit() { + long count = graphTemplate.traversalEdge().limit(1L).count(); + assertEquals(1L, count); + assertNotEquals(graphTemplate.traversalEdge().count(), count); + } + + @Test + void shouldDefinesRange() { + long count = graphTemplate.traversalEdge().range(1, 3).count(); + assertEquals(2L, count); + assertNotEquals(graphTemplate.traversalEdge().count(), count); + } + + @Test + void shouldMapValuesAsStream() { + List> maps = graphTemplate.traversalVertex().inE("reads") + .valueMap("motivation").stream().toList(); + + assertFalse(maps.isEmpty()); + assertEquals(3, maps.size()); + + List names = new ArrayList<>(); + + maps.forEach(m -> names.add(m.get("motivation").toString())); + + assertThat(names).contains("hobby", "love", "job"); + } + + @Test + void shouldMapValuesAsStreamLimit() { + List> maps = graphTemplate.traversalVertex().inE("reads") + .valueMap("motivation").next(2).toList(); + + assertFalse(maps.isEmpty()); + assertEquals(2, maps.size()); + } + + + @Test + void shouldReturnMapValueAsEmptyStream() { + Stream> stream = graphTemplate.traversalVertex().inE("reads") + .valueMap("noFoundProperty").stream(); + assertTrue(stream.allMatch(m -> Objects.isNull(m.get("noFoundProperty")))); + } + + @Test + void shouldReturnNext() { + Map map = graphTemplate.traversalVertex().inE("reads") + .valueMap("motivation").next(); + + assertNotNull(map); + assertFalse(map.isEmpty()); + } + + + @Test + void shouldReturnHas() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + + + Optional result = graphTemplate.traversalEdge().has("when").next(); + assertNotNull(result); + + graphTemplate.deleteEdge(lion.getId()); + } + + @Test + void shouldRepeatTimesTraversal() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + Optional result = graphTemplate.traversalEdge().repeat().has("when").times(2).next(); + assertNotNull(result); + assertEquals(snake, result.get().incoming()); + assertEquals(lion, result.get().outgoing()); + } + + @Test + void shouldRepeatUntilTraversal() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + + Optional result = graphTemplate.traversalEdge().repeat().has("when") + .until().has("when").next(); + + assertTrue(result.isPresent()); + + assertEquals(snake, result.get().incoming()); + assertEquals(lion, result.get().outgoing()); + + } + + @Test + void shouldRepeatUntilHasValueTraversal() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + + Optional result = graphTemplate.traversalEdge().repeat().has("when") + .until().has("when", "night").next(); + + assertTrue(result.isPresent()); + + assertEquals(snake, result.get().incoming()); + assertEquals(lion, result.get().outgoing()); + + } + + @Test + void shouldRepeatUntilHasPredicateTraversal() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + + EdgeEntity result = graphTemplate.traversalEdge().repeat().has("when") + .until().has("when", new P((a, b) -> true, "night")).next().orElseThrow(); + + + SoftAssertions.assertSoftly(softly -> { + Animal incoming = result.incoming(); + Animal outgoing = result.outgoing(); + softly.assertThat(incoming).isEqualTo(snake); + softly.assertThat(outgoing).isEqualTo(lion); + }); + + } + + + @Test + void shouldReturnErrorWhenTheOrderIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalEdge().orderBy(null)); + } + + @Test + void shouldReturnErrorWhenThePropertyDoesNotExist() { + assertThrows(NoSuchElementException.class, () -> + graphTemplate.traversalEdge().orderBy("wrong property").asc().next().get()); + } + + @Test + void shouldOrderAsc() { + String property = "motivation"; + + List properties = graphTemplate.traversalEdge() + .has(property) + .orderBy(property) + .asc().stream() + .map(e -> e.get(property)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(v -> v.get(String.class)) + .collect(toList()); + + assertThat(properties).contains("hobby", "job", "love"); + } + + @Test + void shouldOrderDesc() { + String property = "motivation"; + + List properties = graphTemplate.traversalEdge() + .has(property) + .orderBy(property) + .desc().stream() + .map(e -> e.get(property)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(v -> v.get(String.class)) + .collect(toList()); + + assertThat(properties).contains("love", "job", "hobby"); + } + + + @Test + void shouldReturnResultAsList() { + List entities = graphTemplate.traversalEdge().result() + .toList(); + assertEquals(3, entities.size()); + } + + @Test + void shouldReturnErrorWhenThereAreMoreThanOneInGetSingleResult() { + assertThrows(NonUniqueResultException.class, () -> graphTemplate.traversalEdge().singleResult()); + } + + @Test + void shouldReturnOptionalEmptyWhenThereIsNotResultInSingleResult() { + Optional entity = graphTemplate.traversalEdge(-1L).singleResult(); + assertFalse(entity.isPresent()); + } + + @Test + void shouldReturnSingleResult() { + String name = "Poliana"; + Optional entity = graphTemplate.traversalEdge(reads.id()).singleResult(); + assertEquals(reads, entity.get()); + } + + @Test + void shouldReturnErrorWhenPredicateIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalEdge().filter(null)); + } + + @Test + void shouldReturnFromPredicate() { + long count = graphTemplate.traversalEdge().filter(reads::equals).count(); + assertEquals(1L, count); + } + + @Test + void shouldDedup() { + + graphTemplate.edge(otavio, "knows", paulo); + graphTemplate.edge(paulo, "knows", otavio); + graphTemplate.edge(otavio, "knows", poliana); + graphTemplate.edge(poliana, "knows", otavio); + graphTemplate.edge(poliana, "knows", paulo); + graphTemplate.edge(paulo, "knows", poliana); + + List edges = graphTemplate.traversalVertex() + .hasLabel(Person.class) + .inE("knows").result() + .collect(Collectors.toList()); + + assertEquals(6, edges.size()); + + edges = graphTemplate.traversalVertex() + .hasLabel(Person.class) + .inE("knows") + .dedup() + .result() + .collect(Collectors.toList()); + + assertEquals(6, edges.size()); + } +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplateProducerTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplateProducerTest.java new file mode 100644 index 000000000..2cc000699 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplateProducerTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.communication.graph.GraphDatabaseManager; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class DefaultGraphTemplateProducerTest { + + @Inject + private GraphTemplateProducer producer; + + + @Test + void shouldReturnErrorWhenManagerNull() { + Assertions.assertThrows(NullPointerException.class, () -> producer.apply(null)); + } + + @Test + void shouldReturn() { + var graph = Mockito.mock(Graph.class); + var template = producer.apply(graph); + assertNotNull(template); + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTraversalSourceTemplateTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTraversalSourceTemplateTest.java new file mode 100644 index 000000000..2df77ec7b --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTraversalSourceTemplateTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, Transactional.class}) +@AddPackages({BookRepository.class, Reflections.class, GraphProducer.class}) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class DefaultGraphTraversalSourceTemplateTest extends AbstractGraphTemplateTest { + + @Inject + private GraphTemplate graphTemplate; + + @Inject + private Graph graph; + + @Override + protected Graph getGraph() { + return graph; + } + + @Override + protected GraphTemplate getGraphTemplate() { + return graphTemplate; + } +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultValueMapTraversalTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultValueMapTraversalTest.java new file mode 100644 index 000000000..b6eaa0d14 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultValueMapTraversalTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class DefaultValueMapTraversalTest extends AbstractTraversalTest { + + + @Test + void shouldCount() { + long count = graphTemplate.traversalVertex() + .hasLabel(Person.class).valueMap("name").count(); + assertEquals(3L, count); + } + + + @Test + void shouldReturnMapValues() { + List names = graphTemplate.traversalVertex() + .hasLabel(Person.class).valueMap("name") + .stream() + .map(m -> m.getOrDefault("name", "").toString()).collect(Collectors.toList()); + + + assertThat(names).contains("[Poliana]", "[Otavio]", "[Paulo]"); + } + + @Test + void shouldReturnStream() { + Stream> stream = graphTemplate.traversalVertex() + .hasLabel(Person.class).valueMap("name") + .stream(); + assertNotNull(stream); + assertEquals(3L, stream.count()); + } + + + @Test + void shouldReturnResultAsList() { + List> maps = graphTemplate.traversalVertex() + .hasLabel(Person.class).valueMap("name") + .resultList(); + assertEquals(3, maps.size()); + } + + @Test + void shouldReturnErrorWhenThereAreMoreThanOneInGetSingleResult() { + assertThrows(NonUniqueResultException.class, () -> graphTemplate.traversalVertex() + .hasLabel(Person.class).valueMap("name") + .singleResult()); + } + + @Test + void shouldReturnOptionalEmptyWhenThereIsNotResultInSingleResult() { + Optional> entity = graphTemplate.traversalVertex() + .hasLabel("not_found").valueMap("name").singleResult(); + assertFalse(entity.isPresent()); + } + + @Test + void shouldReturnSingleResult() { + String name = "Poliana"; + Optional> poliana = graphTemplate.traversalVertex().hasLabel("Person"). + has("name", name).valueMap("name").singleResult(); + assertEquals(name, poliana.map(m -> ((List) m.get("name")).get(0)).orElse("")); + } +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversalTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversalTest.java new file mode 100644 index 000000000..efceae962 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/DefaultVertexTraversalTest.java @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.NonUniqueResultException; +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.structure.T; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.entities.Animal; +import org.eclipse.jnosql.mapping.graph.entities.Book; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class DefaultVertexTraversalTest extends AbstractTraversalTest { + + + @Test + void shouldReturnErrorWhenVertexIdIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex(null)); + } + + @Test + void shouldGetVertexFromId() { + List people = graphTemplate.traversalVertex(otavio.getId(), poliana.getId()).result() + .collect(toList()); + + assertThat(people).contains(otavio, poliana); + } + + @Test + void shouldDefineLimit() { + List people = graphTemplate.traversalVertex(otavio.getId(), poliana.getId(), + paulo.getId()).limit(1) + .result() + .collect(toList()); + + assertEquals(1, people.size()); + assertThat(people).contains(otavio); + } + + @Test + void shouldDefineLimit2() { + List people = graphTemplate.traversalVertex(otavio.getId(), poliana.getId(), paulo.getId()). + next(2) + .collect(toList()); + + assertEquals(2, people.size()); + assertThat(people).contains(otavio, poliana); + } + + @Test + void shouldNext() { + Optional next = graphTemplate.traversalVertex().next(); + assertTrue(next.isPresent()); + } + + @Test + void shouldEmptyNext() { + Optional next = graphTemplate.traversalVertex(-12).next(); + assertFalse(next.isPresent()); + } + + + @Test + void shouldHave() { + Optional person = graphTemplate.traversalVertex().has("name", "Poliana").next(); + assertTrue(person.isPresent()); + assertEquals(person.get(), poliana); + } + + @Test + void shouldReturnErrorWhenHasNullKey() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex() + .has((String) null, "Poliana") + .next()); + } + + + @Test + void shouldReturnErrorWhenHasNullValue() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().has("name", null) + .next()); + } + + @Test + void shouldHaveId() { + Optional person = graphTemplate.traversalVertex().has(T.id, poliana.getId()).next(); + assertTrue(person.isPresent()); + assertEquals(person.get(), poliana); + } + + @Test + void shouldReturnErrorWhenHasIdHasNullValue() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().has(T.id, null).next()); + } + + @Test + void shouldReturnErrorWhenHasIdHasNullAccessor() { + assertThrows(NullPointerException.class, () -> { + T id = null; + graphTemplate.traversalVertex().has(id, poliana.getId()).next(); + }); + } + + + @Test + void shouldHavePredicate() { + List result = graphTemplate.traversalVertex().has("age", P.gt(26)) + .result() + .toList(); + assertEquals(5, result.size()); + } + + @Test + void shouldReturnErrorWhenHasPredicateIsNull() { + assertThrows(NullPointerException.class, () -> { + P gt = null; + graphTemplate.traversalVertex().has("age", gt) + .result() + .collect(toList()); + }); + } + + @Test + void shouldReturnErrorWhenHasKeyIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().has((String) null, + P.gt(26)) + .result() + .collect(toList())); + } + + @Test + void shouldHaveLabel() { + List books = graphTemplate.traversalVertex().hasLabel("Book").result().collect(toList()); + assertEquals(3, books.size()); + assertThat(books).contains(shack, license, effectiveJava); + } + + @Test + void shouldHaveLabel2() { + + List entities = graphTemplate.traversalVertex() + .hasLabel(P.eq("Book").or(P.eq("Person"))) + .result().collect(toList()); + assertThat(entities).hasSize(6).contains(shack, license, effectiveJava, otavio, poliana, paulo); + } + + @Test + void shouldReturnErrorWhenHasLabelHasNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().hasLabel((String) null) + .result().collect(toList())); + } + + @Test + void shouldIn() { + List books = graphTemplate.traversalVertex().out(READS).result().collect(toList()); + assertEquals(3, books.size()); + assertThat(books).contains(shack, license, effectiveJava); + } + + @Test + void shouldReturnErrorWhenInIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().out((String) null).result().collect(toList())); + } + + @Test + void shouldOut() { + List people = graphTemplate.traversalVertex().in(READS).result().collect(toList()); + assertEquals(3, people.size()); + assertThat(people).contains(otavio, poliana, paulo); + } + + @Test + void shouldReturnErrorWhenOutIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().in((String) null).result().collect(toList())); + } + + @Test + void shouldBoth() { + List entities = graphTemplate.traversalVertex().both(READS) + .result().toList(); + assertEquals(6, entities.size()); + } + + @Test + void shouldReturnErrorWhenBothIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().both((String) null) + .result().collect(toList())); + } + + @Test + void shouldNot() { + List result = graphTemplate.traversalVertex().hasNot("year").result().toList(); + assertEquals(6, result.size()); + } + + @Test + void shouldReturnErrorWhenHasNotIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().hasNot((String) null) + .result().collect(toList())); + } + + @Test + void shouldCount() { + long count = graphTemplate.traversalVertex().both(READS).count(); + assertEquals(6L, count); + } + + @Test + void shouldReturnZeroWhenCountIsEmpty() { + long count = graphTemplate.traversalVertex().both("WRITES").count(); + assertEquals(0L, count); + } + + @Test + void shouldDefinesLimit() { + long count = graphTemplate.traversalVertex().limit(1L).count(); + assertEquals(1L, count); + assertNotEquals(graphTemplate.traversalVertex().count(), count); + } + + @Test + void shouldDefinesRange() { + long count = graphTemplate.traversalVertex().range(1, 3).count(); + assertEquals(2L, count); + assertNotEquals(graphTemplate.traversalVertex().count(), count); + } + + @Test + void shouldMapValuesAsStream() { + List> maps = graphTemplate.traversalVertex().hasLabel("Person") + .valueMap("name").stream().toList(); + + assertFalse(maps.isEmpty()); + assertEquals(3, maps.size()); + + List names = new ArrayList<>(); + + maps.forEach(m -> names.add(((List) m.get("name")).get(0).toString())); + + assertThat(names).contains("Otavio", "Poliana", "Paulo"); + } + + @Test + void shouldMapValuesAsStreamLimit() { + List> maps = graphTemplate.traversalVertex().hasLabel("Person") + .valueMap("name").next(2).toList(); + + assertFalse(maps.isEmpty()); + assertEquals(2, maps.size()); + } + + + @Test + void shouldReturnMapValueAsEmptyStream() { + Stream> stream = graphTemplate.traversalVertex().hasLabel("Person") + .valueMap("noField").stream(); + assertTrue(stream.allMatch(m -> Objects.isNull(m.get("noFoundProperty")))); + } + + @Test + void shouldReturnNext() { + Map map = graphTemplate.traversalVertex().hasLabel("Person") + .valueMap("name").next(); + + assertNotNull(map); + assertFalse(map.isEmpty()); + } + + + @Test + void shouldRepeatTimesTraversal() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + Optional animal = graphTemplate.traversalVertex().repeat().out("eats").times(3).next(); + assertTrue(animal.isPresent()); + assertEquals(plant, animal.get()); + + } + + @Test + void shouldRepeatTimesTraversal2() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake).add("when", "night"); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + Optional animal = graphTemplate.traversalVertex().repeat().in("eats").times(3).next(); + assertTrue(animal.isPresent()); + assertEquals(lion, animal.get()); + + } + + @Test + void shouldRepeatUntilTraversal() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + + Optional animal = graphTemplate.traversalVertex() + .repeat().out("eats") + .until().has("name", "plant").next(); + + assertTrue(animal.isPresent()); + + + assertEquals(plant, animal.get()); + } + + @Test + void shouldRepeatUntilTraversal2() { + Animal lion = graphTemplate.insert(new Animal("lion")); + Animal snake = graphTemplate.insert(new Animal("snake")); + Animal mouse = graphTemplate.insert(new Animal("mouse")); + Animal plant = graphTemplate.insert(new Animal("plant")); + + graphTemplate.edge(lion, "eats", snake); + graphTemplate.edge(snake, "eats", mouse); + graphTemplate.edge(mouse, "eats", plant); + + Optional animal = graphTemplate.traversalVertex() + .repeat().in("eats") + .until().has("name", "lion").next(); + + assertTrue(animal.isPresent()); + + + assertEquals(lion, animal.get()); + } + + + @Test + void shouldReturnErrorWhenTheOrderIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().orderBy(null)); + } + + @Test + void shouldReturnErrorWhenThePropertyDoesNotExist() { + assertThrows(NoSuchElementException.class, () -> + graphTemplate.traversalVertex().orderBy("wrong property").asc().next().get()); + } + + @Test + void shouldOrderAsc() { + String property = "name"; + + List properties = graphTemplate.traversalVertex() + .hasLabel("Book") + .has(property) + .orderBy(property) + .asc().result() + .map(Book::getName) + .collect(toList()); + + assertThat(properties).contains("Effective Java", "Software License", "The Shack"); + } + + @Test + void shouldOrderDesc() { + String property = "name"; + + List properties = graphTemplate.traversalVertex() + .hasLabel("Book") + .has(property) + .orderBy(property) + .desc().result() + .map(Book::getName) + .collect(toList()); + + assertThat(properties).contains("The Shack", "Software License", "Effective Java"); + } + + @Test + void shouldReturnErrorWhenHasLabelStringNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().hasLabel((String) null)); + } + + @Test + void shouldReturnErrorWhenHasLabelSupplierNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().hasLabel((Supplier) null)); + } + + @Test + void shouldReturnErrorWhenHasLabelEntityClassNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().hasLabel((Class) null)); + } + + @Test + void shouldReturnHasLabel() { + assertTrue(graphTemplate.traversalVertex().hasLabel("Person").result().allMatch(Person.class::isInstance)); + assertTrue(graphTemplate.traversalVertex().hasLabel(() -> "Book").result().allMatch(Book.class::isInstance)); + assertTrue(graphTemplate.traversalVertex().hasLabel(Animal.class).result().allMatch(Animal.class::isInstance)); + } + + @Test + void shouldReturnResultAsList() { + List people = graphTemplate.traversalVertex().hasLabel("Person") + .result() + .toList(); + assertEquals(3, people.size()); + } + + @Test + void shouldReturnErrorWhenThereAreMoreThanOneInGetSingleResult() { + assertThrows(NonUniqueResultException.class, () -> graphTemplate.traversalVertex().hasLabel("Person").singleResult()); + } + + @Test + void shouldReturnOptionalEmptyWhenThereIsNotResultInSingleResult() { + Optional entity = graphTemplate.traversalVertex().hasLabel("NoEntity").singleResult(); + assertFalse(entity.isPresent()); + } + + @Test + void shouldReturnSingleResult() { + String name = "Poliana"; + Optional poliana = graphTemplate.traversalVertex().hasLabel("Person"). + has("name", name).singleResult(); + assertEquals(name, poliana.map(Person::getName).orElse("")); + } + + @Test + void shouldReturnErrorWhenPredicateIsNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.traversalVertex().filter(null)); + } + + @Test + void shouldPredicate() { + long count = graphTemplate.traversalVertex() + .hasLabel(Person.class) + .filter(Person::isAdult).count(); + assertEquals(3L, count); + } + + @Test + void shouldDedup() { + + graphTemplate.edge(otavio, "knows", paulo); + graphTemplate.edge(paulo, "knows", otavio); + graphTemplate.edge(otavio, "knows", poliana); + graphTemplate.edge(poliana, "knows", otavio); + graphTemplate.edge(poliana, "knows", paulo); + graphTemplate.edge(paulo, "knows", poliana); + + List people = graphTemplate.traversalVertex() + .hasLabel(Person.class) + .in("knows").result() + .collect(Collectors.toList()); + + assertEquals(6, people.size()); + + people = graphTemplate.traversalVertex() + .hasLabel(Person.class) + .in("knows").dedup().result() + .collect(Collectors.toList()); + + assertEquals(3, people.size()); + } + + +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/EdgeEntityTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/EdgeEntityTest.java new file mode 100644 index 000000000..ee3d14b87 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/EdgeEntityTest.java @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.data.exceptions.EmptyResultException; +import jakarta.inject.Inject; +import org.eclipse.jnosql.communication.Value; +import org.eclipse.jnosql.communication.semistructured.Element; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.entities.Book; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class EdgeEntityTest { + + + @Inject + private GraphTemplate graphTemplate; + + + @Test + void shouldReturnErrorWhenInboundIsNull() { + Assertions.assertThrows(NullPointerException.class, () -> { + Person person = Person.builder().withName("Poliana").withAge().build(); + Book book = null; + graphTemplate.edge(person, "reads", book); + }); + } + + @Test + void shouldReturnErrorWhenOutboundIsNull() { + Assertions.assertThrows(IllegalStateException.class, () -> { + Person person = Person.builder().withName("Poliana").withAge().build(); + Book book = Book.builder().withAge(2007).withName("The Shack").build(); + graphTemplate.edge(person, "reads", book); + }); + } + + @Test + void shouldReturnErrorWhenLabelIsNull() { + Assertions.assertThrows(NullPointerException.class, () -> { + Person person = Person.builder().withName("Poliana").withAge().build(); + Book book = Book.builder().withAge(2007).withName("The Shack").build(); + graphTemplate.edge(person, (String) null, book); + }); + } + + @Test + void shouldReturnNullWhenInboundIdIsNull() { + Assertions.assertThrows(EmptyResultException.class, () -> { + Person person = Person.builder().withId(-5).withName("Poliana").withAge().build(); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + graphTemplate.edge(person, "reads", book); + }); + + } + + @Test + void shouldReturnNullWhenOutboundIdIsNull() { + Assertions.assertThrows(IllegalStateException.class, () -> { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = Book.builder().withAge(2007).withName("The Shack").build(); + graphTemplate.edge(person, "reads", book); + }); + } + + @Test + void shouldReturnEntityNotFoundWhenOutBoundDidNotFound() { + Assertions.assertThrows( EmptyResultException.class, () -> { + Person person = Person.builder().withId(-10L).withName("Poliana").withAge().build(); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + graphTemplate.edge(person, "reads", book); + }); + } + + @Test + void shouldReturnEntityNotFoundWhenInBoundDidNotFound() { + Assertions.assertThrows( EmptyResultException.class, () -> { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = Book.builder().withId(10L).withAge(2007).withName("The Shack").build(); + graphTemplate.edge(person, "reads", book); + }); + } + + @Test + void shouldCreateAnEdge() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + + assertEquals("reads", edge.label()); + assertEquals(person, edge.outgoing()); + assertEquals(book, edge.incoming()); + assertTrue(edge.isEmpty()); + assertNotNull(edge.id()); + } + + @Test + void shouldGetId() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + + assertEquals("reads", edge.label()); + assertEquals(person, edge.outgoing()); + assertEquals(book, edge.incoming()); + assertTrue(edge.isEmpty()); + assertNotNull(edge.id()); + final Long id = edge.id(Long.class); + assertNotNull(id); + + assertEquals(id, edge.id(Integer.class).longValue()); + + } + + @Test + void shouldCreateAnEdgeWithSupplier() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, () -> "reads", book); + + assertEquals("reads", edge.label()); + assertEquals(person, edge.outgoing()); + assertEquals(book, edge.incoming()); + assertTrue(edge.isEmpty()); + assertNotNull(edge.id()); + } + + @Test + void shouldUseAnEdge() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + + EdgeEntity sameEdge = graphTemplate.edge(person, "reads", book); + + assertEquals(edge.id(), sameEdge.id()); + assertEquals(edge, sameEdge); + } + + @Test + void shouldUseAnEdge2() { + Person poliana = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Person nilzete = graphTemplate.insert(Person.builder().withName("Nilzete").withAge().build()); + + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(poliana, "reads", book); + EdgeEntity edge1 = graphTemplate.edge(nilzete, "reads", book); + + EdgeEntity sameEdge = graphTemplate.edge(poliana, "reads", book); + EdgeEntity sameEdge1 = graphTemplate.edge(nilzete, "reads", book); + + assertEquals(edge.id(), sameEdge.id()); + assertEquals(edge, sameEdge); + + assertEquals(edge1.id(), sameEdge1.id()); + assertEquals(edge1, sameEdge1); + + } + + @Test + void shouldUseADifferentEdge() { + Person poliana = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Person nilzete = graphTemplate.insert(Person.builder().withName("Nilzete").withAge().build()); + + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(poliana, "reads", book); + EdgeEntity edge1 = graphTemplate.edge(nilzete, "reads", book); + + EdgeEntity sameEdge = graphTemplate.edge(poliana, "reads", book); + EdgeEntity sameEdge1 = graphTemplate.edge(nilzete, "reads", book); + + assertNotEquals(edge.id(), edge1.id()); + assertNotEquals(edge.id(), sameEdge1.id()); + + assertNotEquals(sameEdge1.id(), sameEdge.id()); + } + + @Test + void shouldReturnErrorWhenAddKeyIsNull() { + assertThrows(NullPointerException.class, () -> { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add(null, "Brazil"); + }); + } + + @Test + void shouldReturnErrorWhenAddValueIsNull() { + + assertThrows(NullPointerException.class, () -> { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add("where", null); + }); + } + + @Test + void shouldAddProperty() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add("where", "Brazil"); + + assertFalse(edge.isEmpty()); + assertEquals(1, edge.size()); + assertThat(edge.properties()).contains(Element.of("where", "Brazil")); + } + + @Test + void shouldAddPropertyWithValue() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add("where", Value.of("Brazil")); + + assertFalse(edge.isEmpty()); + assertEquals(1, edge.size()); + assertThat(edge.properties()).contains(Element.of("where", "Brazil")); + } + + + @Test + void shouldReturnErrorWhenRemoveNullKeyProperty() { + assertThrows(NullPointerException.class, () -> { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add("where", "Brazil"); + + + assertFalse(edge.isEmpty()); + edge.remove(null); + }); + } + + @Test + void shouldRemoveProperty() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add("where", "Brazil"); + assertEquals(1, edge.size()); + assertFalse(edge.isEmpty()); + edge.remove("where"); + assertTrue(edge.isEmpty()); + assertEquals(0, edge.size()); + } + + @Test + void shouldFindProperty() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.add("where", "Brazil"); + + Optional where = edge.get("where"); + assertTrue(where.isPresent()); + assertEquals("Brazil", where.get().get()); + assertFalse(edge.get("not").isPresent()); + + } + + @Test + void shouldDeleteAnEdge() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + edge.delete(); + + EdgeEntity newEdge = graphTemplate.edge(person, "reads", book); + assertNotEquals(edge.id(), newEdge.id()); + + graphTemplate.deleteEdge(newEdge.id()); + } + + @Test + void shouldReturnErrorWhenDeleteAnEdgeWithNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.delete((Iterable) null)); + } + + @Test + void shouldDeleteAnEdge2() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + + graphTemplate.deleteEdge(edge.id()); + + EdgeEntity newEdge = graphTemplate.edge(person, "reads", book); + assertNotEquals(edge.id(), newEdge.id()); + } + + + @Test + void shouldReturnErrorWhenFindEdgeWithNull() { + assertThrows(NullPointerException.class, () -> graphTemplate.edge(null)); + } + + + @Test + void shouldFindAnEdge() { + Person person = graphTemplate.insert(Person.builder().withName("Poliana").withAge().build()); + Book book = graphTemplate.insert(Book.builder().withAge(2007).withName("The Shack").build()); + EdgeEntity edge = graphTemplate.edge(person, "reads", book); + + Optional newEdge = graphTemplate.edge(edge.id()); + + assertTrue(newEdge.isPresent()); + assertEquals(edge.id(), newEdge.get().id()); + + graphTemplate.deleteEdge(edge.id()); + } + + @Test + void shouldNotFindAnEdge() { + Optional edgeEntity = graphTemplate.edge(-12L); + + assertFalse(edgeEntity.isPresent()); + } + +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphProducer.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphProducer.java new file mode 100644 index 000000000..d4840a224 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphProducer.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Alternative; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; +import jakarta.interceptor.Interceptor; +import org.apache.tinkerpop.gremlin.neo4j.structure.Neo4jGraph; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.mapping.Database; +import org.eclipse.jnosql.mapping.DatabaseType; +import org.mockito.Mockito; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Comparator; +import java.util.function.Supplier; +import java.util.logging.Logger; + +import static java.lang.System.currentTimeMillis; +import static java.util.Collections.singleton; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@ApplicationScoped +@Alternative +@Priority(Interceptor.Priority.APPLICATION) +public class GraphProducer implements Supplier { + + private static final Logger LOGGER = Logger.getLogger(GraphProducer.class.getName()); + + private Graph graph; + + private String directory; + + @PostConstruct + public void init() { + this.directory = new File("").getAbsolutePath() + "/target/jnosql-graph/" + currentTimeMillis() + "/"; + LOGGER.info("Starting Graph database at directory: " + directory); + this.graph = Neo4jGraph.open(directory); + LOGGER.info("Graph database created"); + } + + @Produces + @ApplicationScoped + @Override + public Graph get() { + return graph; + } + + + @Produces + @ApplicationScoped + @Database(value = DatabaseType.GRAPH, provider = "graphRepositoryMock") + public Graph getGraphMock() { + + Graph graphMock = mock(Graph.class); + Vertex vertex = mock(Vertex.class); + when(vertex.label()).thenReturn("Person"); + when(vertex.id()).thenReturn(10L); + when(graphMock.vertices(10L)).thenReturn(Collections.emptyIterator()); + when(vertex.keys()).thenReturn(singleton("name")); + when(vertex.value("name")).thenReturn("nameMock"); + when(graphMock.addVertex(Mockito.anyString())).thenReturn(vertex); + when(graphMock.vertices(Mockito.any())).thenReturn(Collections.emptyIterator()); + return graphMock; + } + + public void dispose(@Disposes Graph graph) throws Exception { + LOGGER.info("Graph database closing"); + graph.close(); + final Path path = Paths.get(directory); + if (Files.exists(path)) { + LOGGER.info("Removing directory graph database: " + directory); + Files.walk(path) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + LOGGER.info("Graph directory exists?: " + Files.exists(path)); + } + LOGGER.info("Graph Database closed"); + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphTemplateProducerTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphTemplateProducerTest.java new file mode 100644 index 000000000..743775b61 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphTemplateProducerTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class GraphTemplateProducerTest { + + @Inject + private GraphTemplateProducer producer; + + @Test + void shouldReturnErrorWhenManagerNull() { + assertThrows(NullPointerException.class, () -> producer.apply(null)); + } + + @Test + void shouldReturnGraphTemplateWhenGetGraph() { + Graph graph = Mockito.mock(Graph.class); + GraphTemplate template = producer.apply(graph); + assertNotNull(template); + } + + +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphTemplateTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphTemplateTest.java new file mode 100644 index 000000000..59afb07d8 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/GraphTemplateTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph; + +import jakarta.inject.Inject; +import jakarta.nosql.Template; +import org.eclipse.jnosql.mapping.Database; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.eclipse.jnosql.mapping.DatabaseType.GRAPH; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class GraphTemplateTest { + + @Inject + private Template template; + + @Inject + @Database(GRAPH) + private Template qualifier; + + + @Test + void shouldInjectTemplate() { + Assertions.assertNotNull(template); + } + + @Test + void shouldInjectQualifier() { + Assertions.assertNotNull(qualifier); + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphConfigurationMock.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphConfigurationMock.java new file mode 100644 index 000000000..6af9d061c --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphConfigurationMock.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.configuration; + +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.Settings; +import org.eclipse.jnosql.communication.graph.GraphConfiguration; + +import java.util.Iterator; + +class GraphConfigurationMock implements GraphConfiguration { + + @Override + public Graph apply(Settings settings) { + return new GraphMock(settings); + } + + public record GraphMock(Settings settings) implements Graph { + + @Override + public Vertex addVertex(Object... keyValues) { + return null; + } + + @Override + public C compute(Class graphComputerClass) throws IllegalArgumentException { + return null; + } + + @Override + public GraphComputer compute() throws IllegalArgumentException { + return null; + } + + @Override + public Iterator vertices(Object... vertexIds) { + return null; + } + + @Override + public Iterator edges(Object... edgeIds) { + return null; + } + + @Override + public Transaction tx() { + return null; + } + + @Override + public void close() throws Exception { + + } + + @Override + public Variables variables() { + return null; + } + + @Override + public Configuration configuration() { + return null; + } + + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphConfigurationMock2.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphConfigurationMock2.java new file mode 100644 index 000000000..0a6241803 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphConfigurationMock2.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.configuration; + +import org.apache.commons.configuration2.Configuration; +import org.apache.tinkerpop.gremlin.process.computer.GraphComputer; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.eclipse.jnosql.communication.Settings; +import org.eclipse.jnosql.communication.graph.GraphConfiguration; + +import java.util.Iterator; + +public class GraphConfigurationMock2 implements GraphConfiguration { + + @Override + public Graph apply(Settings settings) { + return new GraphMock(settings); + } + + public record GraphMock(Settings settings) implements Graph { + + @Override + public Vertex addVertex(Object... keyValues) { + return null; + } + + @Override + public C compute(Class graphComputerClass) throws IllegalArgumentException { + return null; + } + + @Override + public GraphComputer compute() throws IllegalArgumentException { + return null; + } + + @Override + public Iterator vertices(Object... vertexIds) { + return null; + } + + @Override + public Iterator edges(Object... edgeIds) { + return null; + } + + @Override + public Transaction tx() { + return null; + } + + @Override + public void close() throws Exception { + + } + + @Override + public Variables variables() { + return null; + } + + @Override + public Configuration configuration() { + return null; + } + + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphSupplierTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphSupplierTest.java new file mode 100644 index 000000000..798cc093b --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/configuration/GraphSupplierTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.configuration; + +import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.BookRepository; +import org.eclipse.jnosql.mapping.graph.GraphProducer; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.graph.Transactional; +import org.eclipse.jnosql.mapping.graph.spi.GraphExtension; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.jnosql.mapping.core.config.MappingConfigurations.GRAPH_PROVIDER; + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class GraphSupplierTest { + + @Inject + private GraphSupplier supplier; + + @BeforeEach + public void beforeEach(){ + System.clearProperty(GRAPH_PROVIDER.get()); + } + + @Test + void shouldGetGraph() { + System.setProperty(GRAPH_PROVIDER.get(), GraphConfigurationMock.class.getName()); + Graph graph = supplier.get(); + Assertions.assertNotNull(graph); + assertThat(graph).isInstanceOf(GraphConfigurationMock.GraphMock.class); + } + + + @Test + void shouldUseDefaultConfigurationWhenProviderIsWrong() { + System.setProperty(GRAPH_PROVIDER.get(), Integer.class.getName()); + Graph graph = supplier.get(); + Assertions.assertNotNull(graph); + assertThat(graph).isInstanceOf(GraphConfigurationMock2.GraphMock.class); + } + + @Test + void shouldUseDefaultConfiguration() { + Graph graph = supplier.get(); + Assertions.assertNotNull(graph); + assertThat(graph).isInstanceOf(GraphConfigurationMock2.GraphMock.class); + } + + @Test + void shouldClose() throws Exception { + Graph graph = Mockito.mock(Graph.class); + supplier.close(graph); + Mockito.verify(graph).close(); + } +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Animal.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Animal.java new file mode 100644 index 000000000..4d9d124a2 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Animal.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + +import jakarta.nosql.Column; +import jakarta.nosql.Entity; +import jakarta.nosql.Id; + +import java.util.Objects; + +@Entity +public class Animal { + + + @Id + private Long id; + + @Column + private String name; + + Animal() { + } + + public Animal(String name) { + this.name = name; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Animal)) { + return false; + } + Animal animal = (Animal) o; + return Objects.equals(id, animal.id) && + Objects.equals(name, animal.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public String toString() { + return "Animal{" + "name='" + name + '\'' + + '}'; + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Book.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Book.java new file mode 100644 index 000000000..11fb6f850 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Book.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + +import jakarta.nosql.Column; +import jakarta.nosql.Entity; +import jakarta.nosql.Id; + +import java.util.Objects; + +@Entity +public class Book { + + @Id + private Long id; + + @Column + private String name; + + @Column + private Integer age; + + + Book() { + } + + Book(Long id, String name, Integer age) { + this.id = id; + this.name = name; + this.age = age; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Integer getAge() { + return age; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Book)) { + return false; + } + Book book = (Book) o; + return Objects.equals(id, book.id); + } + + @Override + public int hashCode() { + return Objects.hashCode(id); + } + + @Override + public String toString() { + return "Book{" + + "id=" + id + + ", name='" + name + '\'' + + ", age=" + age + + '}'; + } + + public static BookBuilder builder() { + return new BookBuilder(); + } + + public static class BookBuilder { + private String name; + private Integer age; + private Long id; + + private BookBuilder() { + } + + public BookBuilder withName(String name) { + this.name = name; + return this; + } + + public BookBuilder withAge(Integer age) { + this.age = age; + return this; + } + + public BookBuilder withId(Long id) { + this.id = id; + return this; + } + + public Book build() { + return new Book(id, name, age); + } + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/BookTemplate.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/BookTemplate.java new file mode 100644 index 000000000..45198effe --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/BookTemplate.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.graph.Transactional; +import org.eclipse.jnosql.mapping.graph.entities.Book; + +@ApplicationScoped +public class BookTemplate { + + @Inject + private GraphTemplate graphTemplate; + + @Transactional + public void insert(Book actor) { + graphTemplate.insert(actor); + } + + @Transactional + public void insertException(Book actor) { + graphTemplate.insert(actor); + throw new NullPointerException("should get a rollback"); + } + + public void normalInsertion(Book actor) { + graphTemplate.insert(actor); + } + +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/People.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/People.java new file mode 100644 index 000000000..56e35d37d --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/People.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + +import jakarta.data.repository.Insert; +import jakarta.data.repository.Repository; + + +@Repository +public interface People { + + @Insert + Person insert(Person person); +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Person.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Person.java new file mode 100644 index 000000000..c14a87ec8 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/Person.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + + +import jakarta.nosql.Column; +import jakarta.nosql.Entity; +import jakarta.nosql.Id; +import jakarta.nosql.MappedSuperclass; + +import java.util.List; +import java.util.Objects; + +@Entity +@MappedSuperclass +public class Person { + + @Id + private long id; + + @Column + private String name; + + @Column + private int age; + + @Column + private List phones; + + private String ignore; + + + public long getId() { + return id; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public List getPhones() { + return phones; + } + + public String getIgnore() { + return ignore; + } + + public boolean isAdult() { + return age > 21; + } + + Person() { + } + + Person(long id, String name, int age, List phones, String ignore) { + this.id = id; + this.name = name; + this.age = age; + this.phones = phones; + this.ignore = ignore; + } + + @Override + public String toString() { + return "Person{" + "id=" + id + + ", name='" + name + '\'' + + ", age=" + age + + ", phones=" + phones + + ", ignore='" + ignore + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Person person = (Person) o; + return id == person.id && + age == person.age && + Objects.equals(name, person.name) && + Objects.equals(phones, person.phones); + } + + @Override + public int hashCode() { + return Objects.hash(id, name, age, phones, ignore); + } + + public static PersonBuilder builder() { + return new PersonBuilder(); + } + + public void setName(String name){ + this.name = name; + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/PersonBuilder.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/PersonBuilder.java new file mode 100644 index 000000000..207666f46 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/PersonBuilder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + +import java.util.List; + +public class PersonBuilder { + private long id; + private String name; + private int age; + private List phones; + private String ignore; + + public PersonBuilder withId(long id) { + this.id = id; + return this; + } + + public PersonBuilder withName(String name) { + this.name = name; + return this; + } + + public PersonBuilder withAge() { + this.age = 10; + return this; + } + + public PersonBuilder withAge(int age) { + this.age = age; + return this; + } + + + public PersonBuilder withPhones(List phones) { + this.phones = phones; + return this; + } + + public PersonBuilder withIgnore() { + this.ignore = "Just Ignore"; + return this; + } + + public Person build() { + return new Person(id, name, age, phones, ignore); + } +} \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/PersonRepository.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/PersonRepository.java new file mode 100644 index 000000000..9f31428d1 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/PersonRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + + +import jakarta.data.repository.BasicRepository; +import jakarta.data.repository.Repository; + +@Repository +public interface PersonRepository extends BasicRepository { +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/WrongEntity.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/WrongEntity.java new file mode 100644 index 000000000..6475eba8f --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/entities/WrongEntity.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.entities; + +import java.util.Objects; + +public class WrongEntity { + + private String name; + + WrongEntity() { + } + + public WrongEntity(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + WrongEntity animal = (WrongEntity) o; + return Objects.equals(name, animal.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "Animal{" + "name='" + name + '\'' + + '}'; + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/spi/GraphCustomExtensionTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/spi/GraphCustomExtensionTest.java new file mode 100644 index 000000000..abdd3c1cb --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/spi/GraphCustomExtensionTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.spi; + +import jakarta.inject.Inject; +import org.assertj.core.api.SoftAssertions; +import org.eclipse.jnosql.mapping.Database; +import org.eclipse.jnosql.mapping.DatabaseType; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.GraphProducer; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.graph.entities.People; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class GraphCustomExtensionTest { + + @Inject + @Database(value = DatabaseType.GRAPH) + private People people; + + @Inject + @Database(value = DatabaseType.GRAPH, provider = "graphRepositoryMock") + private People pepoleMock; + + @Inject + private People repository; + + @Test + void shouldInitiate() { + assertNotNull(people); + Person person = people.insert(Person.builder().build()); + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(person).isNotNull(); + }); + } + + @Test + void shouldUseMock(){ + assertNotNull(pepoleMock); + + Person person = pepoleMock.insert(Person.builder().build()); + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(person).isNotNull(); + }); + } + + @Test + void shouldUseDefault(){ + assertNotNull(repository); + + Person person = repository.insert(Person.builder().build()); + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(person).isNotNull(); + }); + } +} diff --git a/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/spi/GraphExtensionTest.java b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/spi/GraphExtensionTest.java new file mode 100644 index 000000000..c558a65c7 --- /dev/null +++ b/jnosql-tinkerpop/src/test/java/org/eclipse/jnosql/mapping/graph/spi/GraphExtensionTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Apache License v2.0 which accompanies this distribution. + * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html + * and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php. + * + * You may elect to redistribute this code under either of these licenses. + * + * Contributors: + * + * Otavio Santana + */ +package org.eclipse.jnosql.mapping.graph.spi; + +import jakarta.inject.Inject; +import org.eclipse.jnosql.mapping.Database; +import org.eclipse.jnosql.mapping.DatabaseType; +import org.eclipse.jnosql.mapping.core.Converters; +import org.eclipse.jnosql.mapping.core.spi.EntityMetadataExtension; +import org.eclipse.jnosql.mapping.graph.GraphProducer; +import org.eclipse.jnosql.mapping.graph.GraphTemplate; +import org.eclipse.jnosql.mapping.graph.entities.Person; +import org.eclipse.jnosql.mapping.graph.entities.PersonRepository; +import org.eclipse.jnosql.mapping.reflection.Reflections; +import org.eclipse.jnosql.mapping.semistructured.EntityConverter; +import org.jboss.weld.junit5.auto.AddExtensions; +import org.jboss.weld.junit5.auto.AddPackages; +import org.jboss.weld.junit5.auto.EnableAutoWeld; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + + +@EnableAutoWeld +@AddPackages(value = {Converters.class, EntityConverter.class, GraphTemplate.class}) +@AddPackages(GraphProducer.class) +@AddPackages(Reflections.class) +@AddExtensions({EntityMetadataExtension.class, GraphExtension.class}) +class GraphExtensionTest { + + + @Inject + @Database(value = DatabaseType.GRAPH) + private PersonRepository repository; + + @Inject + @Database(value = DatabaseType.GRAPH, provider = "graphRepositoryMock") + private PersonRepository repositoryMock; + + @Inject + @Database(value = DatabaseType.GRAPH, provider = "graphRepositoryMock") + private GraphTemplate templateMock; + + @Inject + private GraphTemplate template; + + + @Test + void shouldInitiate() { + assertNotNull(repository); + Person person = repository.save(Person.builder().build()); + assertNull(person.getName()); + } + + @Test + void shouldUseMock(){ + assertNotNull(repositoryMock); + } + + @Test + void shouldInjectTemplate() { + assertNotNull(templateMock); + assertNotNull(template); + } + + @Test + void shouldInjectRepository() { + assertNotNull(repository); + assertNotNull(repositoryMock); + } +} diff --git a/jnosql-tinkerpop/src/test/resources/META-INF/beans.xml b/jnosql-tinkerpop/src/test/resources/META-INF/beans.xml new file mode 100644 index 000000000..2a29afc00 --- /dev/null +++ b/jnosql-tinkerpop/src/test/resources/META-INF/beans.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/resources/META-INF/microprofile-config.properties b/jnosql-tinkerpop/src/test/resources/META-INF/microprofile-config.properties new file mode 100644 index 000000000..1b47fe614 --- /dev/null +++ b/jnosql-tinkerpop/src/test/resources/META-INF/microprofile-config.properties @@ -0,0 +1,4 @@ +graph=graph +graph.settings.key=value +graph.settings.key2=value2 +graph.provider=org.eclipse.jnosql.mapping.graph.configuration.GraphConfigurationMock \ No newline at end of file diff --git a/jnosql-tinkerpop/src/test/resources/META-INF/services/org.eclipse.jnosql.communication.graph.GraphConfiguration b/jnosql-tinkerpop/src/test/resources/META-INF/services/org.eclipse.jnosql.communication.graph.GraphConfiguration new file mode 100644 index 000000000..36f74a08a --- /dev/null +++ b/jnosql-tinkerpop/src/test/resources/META-INF/services/org.eclipse.jnosql.communication.graph.GraphConfiguration @@ -0,0 +1 @@ +org.eclipse.jnosql.mapping.graph.configuration.GraphConfigurationMock2 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 088df2544..cac625f8e 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,7 @@ jnosql-riak jnosql-solr jnosql-oracle-nosql + jnosql-tinkerpop