From e1f394249b35aae41b50ea0184be288435083af9 Mon Sep 17 00:00:00 2001 From: Otavio Santana Date: Sat, 9 Mar 2024 09:23:38 +0000 Subject: [PATCH] feat: add converter Signed-off-by: Otavio Santana --- ...java => CommunicationEntityConverter.java} | 2 +- .../graph/DefaultGraphDatabaseManager.java | 2 +- .../mapping/graph/AbstractGraphTemplate.java | 179 ++++++++++++++++++ .../mapping/graph/DefaultEdgeEntity.java | 163 ++++++++++++++++ .../mapping/graph/DefaultGraphTemplate.java | 7 + .../jnosql/mapping/graph/EdgeEntity.java | 98 ++++++---- .../mapping/graph/GraphTransactionUtil.java | 88 +++++++++ 7 files changed, 496 insertions(+), 43 deletions(-) rename jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/{EntityConverter.java => CommunicationEntityConverter.java} (85%) create mode 100644 jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java create mode 100644 jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTransactionUtil.java diff --git a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/EntityConverter.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/CommunicationEntityConverter.java similarity index 85% rename from jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/EntityConverter.java rename to jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/CommunicationEntityConverter.java index f2a6fe0db..5eb56ae18 100644 --- a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/EntityConverter.java +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/CommunicationEntityConverter.java @@ -5,7 +5,7 @@ import java.util.function.Function; -enum EntityConverter implements Function{ +public enum CommunicationEntityConverter implements Function{ INSTANCE; diff --git a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java index daa0d3479..a698321f4 100644 --- a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/communication/graph/DefaultGraphDatabaseManager.java @@ -145,7 +145,7 @@ public Stream select(SelectQuery query) { traversal.order().by(s.property(), desc); } }); - return traversal.toStream().map(EntityConverter.INSTANCE); + return traversal.toStream().map(CommunicationEntityConverter.INSTANCE); } @Override diff --git a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java index 9bc26c700..ab010db20 100644 --- a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/AbstractGraphTemplate.java @@ -1,9 +1,188 @@ package org.eclipse.jnosql.mapping.graph; +import jakarta.data.exceptions.EmptyResultException; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +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.GraphDatabaseManager; +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.Optional; +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 { protected abstract GraphDatabaseManager manager(); + protected abstract GraphTraversalSource traversal(); + protected abstract Graph graph(); + + + @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(converter()::toEdgeEntity).toList(); + } + return Collections.emptyList(); + } + + @Override + public Collection edgesById(K id, Direction direction, Supplier... labels) { + return null; + } + + @Override + public Collection edgesById(K id, Direction direction) { + return null; + } + + @Override + public Collection edges(T entity, Direction direction, String... labels) { + return null; + } + + @Override + public Collection edges(T entity, Direction direction, Supplier... labels) { + return null; + } + + @Override + public Collection edges(T entity, Direction direction) { + return null; + } + + @Override + public Optional edge(E edgeId) { + return Optional.empty(); + } + + @Override + public VertexTraversal traversalVertex(Object... vertexIds) { + return null; + } + + @Override + public EdgeTraversal traversalEdge(Object... edgeIds) { + return null; + } + + @Override + public Transaction transaction() { + return null; + } + + @Override + public Stream gremlin(String gremlin) { + return null; + } + + 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; + } + } diff --git a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java new file mode 100644 index 000000000..b36107d09 --- /dev/null +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultEdgeEntity.java @@ -0,0 +1,163 @@ +/* + * 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 instanceof DefaultEdgeEntity)) { + return false; + } + DefaultEdgeEntity that = (DefaultEdgeEntity) o; + return Objects.equals(edge, that.edge) && + Objects.equals(incoming, that.incoming) && + Objects.equals(outgoing, that.outgoing); + } + + @Override + public int hashCode() { + return Objects.hash(edge, incoming, outgoing); + } + + @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-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java index cc335ce10..1e51d46e5 100644 --- a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/DefaultGraphTemplate.java @@ -3,7 +3,9 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Default; import jakarta.inject.Inject; +import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.Transaction; import org.eclipse.jnosql.communication.graph.GraphDatabaseManager; import org.eclipse.jnosql.mapping.Database; import org.eclipse.jnosql.mapping.core.Converters; @@ -11,6 +13,11 @@ import org.eclipse.jnosql.mapping.semistructured.EntityConverter; import org.eclipse.jnosql.mapping.semistructured.EventPersistManager; +import java.util.Collection; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + import static org.eclipse.jnosql.mapping.DatabaseType.GRAPH; @Default diff --git a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java index 6f0401579..6fa97a85a 100644 --- a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/EdgeEntity.java @@ -14,10 +14,14 @@ */ package org.eclipse.jnosql.mapping.graph; +import org.apache.tinkerpop.gremlin.structure.Edge; import org.eclipse.jnosql.communication.Value; -import org.apache.tinkerpop.gremlin.structure.Property; +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; /** @@ -29,108 +33,120 @@ *
outVertex ---label---> inVertex.
*/ public interface EdgeEntity { - /** - * Returns the id + * Returns the identifier of this edge. * - * @return the id + * @return the identifier of this edge */ Object id(); /** - * Returns an id from {@link org.eclipse.jnosql.communication.Value#get(Class)} + * Returns the identifier of this edge converted to the specified type. * - * @param type the class type - * @param the type - * @return an id converted + * @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 the vertex + * Returns the label of this edge. * - * @return the label + * @return the label of this edge */ String label(); /** - * Gets the incoming entity + * Retrieves the incoming entity connected to this edge. * - * @param the type - * @return the incoming entity + * @param the type of the incoming entity + * @return the incoming entity connected to this edge */ T incoming(); /** - * Gets the outgoing entity + * Retrieves the outgoing entity connected to this edge. * - * @param the type - * @return the outgoing entity + * @param the type of the outgoing entity + * @return the outgoing entity connected to this edge */ T outgoing(); /** - * Returns the properties of this vertex + * Returns the properties of this edge. * - * @return the properties + * @return the properties of this edge */ - List properties(); - + List properties(); /** - * Add a new element in the Vertex + * Adds a new property to this edge with the specified key and value. * - * @param key the key - * @param value the information - * @throws NullPointerException when either key or value are null + * @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); /** - * Add a new element in the Vertex + * Adds a new property to this edge with the specified key and value. * - * @param key the key - * @param value the information - * @throws NullPointerException when either key or value are null + * @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 an property + * Removes the property with the specified key from this edge. * - * @param key the key - * @throws NullPointerException whe key is null + * @param key the key of the property to be removed + * @throws NullPointerException if the key is null */ void remove(String key); /** - * Returns the property from the key + * Returns the property value associated with the specified key, if present. * - * @param key the key to find the property - * @return the property to the respective key otherwise {@link Optional#empty()} - * @throws NullPointerException when key is null + * @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 elements. + * Returns true if this edge contains no properties. * - * @return true if this collection contains no elements + * @return true if this edge contains no properties */ boolean isEmpty(); /** - * Returns the number of property in Edge + * Returns the number of properties in this edge. * - * @return the number of elements in this Edge + * @return the number of properties in this edge */ int size(); /** - * Deletes the Edge from the database, after this operation, any write operation - * such as add a property will an illegal state. + * 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.outVertex())), + converter.toEntity(entityConverter.apply(edge.inVertex()))); + } } diff --git a/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTransactionUtil.java b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTransactionUtil.java new file mode 100644 index 000000000..7a84acbe9 --- /dev/null +++ b/jnosql-mapping/jnosql-mapping-graph/src/main/java/org/eclipse/jnosql/mapping/graph/GraphTransactionUtil.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.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; + +/** + * Utilitarian to {@link Transaction} + */ +final class GraphTransactionUtil { + + private static final Logger LOGGER = Logger.getLogger(GraphTransactionUtil.class.getName()); + private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>(); + + private GraphTransactionUtil() { + } + + /** + * Holds the current transaction to don't allow a {@link Transaction#commit()} + * + * @param transaction the {@link Transaction} + */ + static void lock(Transaction transaction) { + THREAD_LOCAL.set(transaction); + } + + /** + * Unlocks the {@link Transaction} of the current thread + */ + static void unlock() { + THREAD_LOCAL.remove(); + } + + /** + * Checks if possible to {@link Transaction#commit()}, + * if checks it the {@link Transaction} holds and if it is defined as an automatic transaction. + * + * @param graph the graph + */ + 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()); + } + + } + } + + /** + * Check if the transaction is enabled + * + * @return Check if the transaction is enabled + */ + static boolean isAutomatic() { + return MicroProfileSettings.INSTANCE.get(GRAPH_TRANSACTION_AUTOMATIC, String.class) + .map(Boolean::valueOf) + .orElse(true); + } + + private static boolean isNotLock() { + return THREAD_LOCAL.get() == null; + } +}