Skip to content

Commit

Permalink
Merge pull request #13 from tschn/master
Browse files Browse the repository at this point in the history
🐛 fix for #8 + new relationship procedures
  • Loading branch information
mfalcier committed Mar 10, 2021
2 parents 7fc0c71 + d879ee9 commit c268139
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 82 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ We would appreciate your feedback about our Versioner Core, how to improve and f

## Buy us a coffee :coffee:

This project is developed during our free time, and our free time is mostly during evening/night! So coffee is really helpful during our sessions :sweat_smile:. If you want to help us with that, you can buy us some :coffee: thanks to [PayPal](https://www.paypal.me/mfalcier/2)!
This project is developed during our free time, and our free time is mostly during evening/night! So coffee is really helpful during our sessions :sweat_smile:. If you want to help us with that, you can buy us some :coffee: thanks to [PayPal](https://www.paypal.me/mfalcier/2)!
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
apply plugin: 'java'

group = 'org.homer'
version = '2.0.0'
version = '2.0.2'

description = """Neo4j Procedures for Graph Versioning"""

Expand Down
3 changes: 3 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ name | parameters | return values | description
[graph.versioner.diff](#diff) | **stateFrom**, **stateTo** | operation, label, oldValue, newValue | Get a list of differences that must be applied to stateFrom in order to convert it into stateTo.
[graph.versioner.diff.from.previous](#diff-from-previous) | **state** | operation, label, oldValue, newValue | Get a list of differences that must be applied to the previous status of the given one in order to become the given state.
[graph.versioner.diff.from.current](#diff-from-current) | **state** | operation, label, oldValue, newValue | Get a list of differences that must be applied to the given state in order to become the current entity state.
[graph.versioner.relationships.createTo](#relationships-createTo) | **entitySource**, **List<entityDestination>**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to each of the R nodes of the destinations with a relationship of the given type.
[graph.versioner.relationships.createFrom](#relationships-createFrom) | **List<entitySource>**, **entityDestination**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for each of the source entities connected to the R nodes of the destination with a relationship of the given type.
[graph.versioner.relationship.create](#relationship-create) | **entitySource**, **entityDestination**, relationshipType, *{key:value,...}*, *date* | **relationship** | Creates a new state for the source entity connected to the R node of the destination with a relationship of the given type.
[graph.versioner.relationship.delete](#relationship-delete) | **entitySource**, **entityDestination**, relationshipType, *date* | **result** | Creates a new state for the source entity without a custom relationship of the given type.
[graph.versioner.relationships.delete](#relationships-delete) | **entitySource**, **List<entityDestination>**, relationshipType, *date* | **result** | Creates a new state for the source entity without a custom relationships for each of the destination nodes of the given type.

## init

Expand Down
25 changes: 18 additions & 7 deletions src/main/java/org/homer/versioner/core/Utility.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package org.homer.versioner.core;

import org.apache.commons.lang3.tuple.Pair;
import org.homer.versioner.core.exception.VersionerCoreException;
import org.homer.versioner.core.output.NodeOutput;
import org.homer.versioner.core.output.RelationshipOutput;
import org.homer.versioner.core.procedure.RelationshipProcedure;
import org.neo4j.graphdb.*;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

Expand Down Expand Up @@ -143,10 +144,10 @@ public static void addCurrentState(Node state, Node entity, LocalDateTime instan
* @param state a {@link Node} representing the State
* @return {@link Boolean} result
*/
public static Boolean checkRelationship(Node entity, Node state) {
public static void checkRelationship(Node entity, Node state) throws VersionerCoreException {
Spliterator<Relationship> stateRelIterator = state.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.INCOMING).spliterator();

Boolean check = StreamSupport.stream(stateRelIterator, false).map(hasStateRel -> {
StreamSupport.stream(stateRelIterator, false).map(hasStateRel -> {
Node maybeEntity = hasStateRel.getStartNode();
if (maybeEntity.getId() != entity.getId()) {
throw new VersionerCoreException("Can't patch the given entity, because the given State is owned by another entity.");
Expand All @@ -156,7 +157,6 @@ public static Boolean checkRelationship(Node entity, Node state) {
throw new VersionerCoreException("Can't find any entity node relate to the given State.");
});

return check;
}

/**
Expand Down Expand Up @@ -195,9 +195,9 @@ public static LocalDateTime defaultToNow(LocalDateTime date) {
* @param node the {@link Node} to check
* @throws VersionerCoreException
*/
public static void isEntityOrThrowException(Node node) throws VersionerCoreException {
public static void isEntityOrThrowException(Node node) {

streamOfIterable(node.getRelationships(RelationshipType.withName("CURRENT"), Direction.OUTGOING)).findAny()
streamOfIterable(node.getRelationships(RelationshipType.withName(CURRENT_TYPE), Direction.OUTGOING)).findAny()
.map(ignored -> streamOfIterable(node.getRelationships(RelationshipType.withName("R"), Direction.INCOMING)).findAny())
.orElseThrow(() -> new VersionerCoreException("The given node is not a Versioner Core Entity"));
}
Expand All @@ -218,11 +218,22 @@ public static Optional<Relationship> getCurrentRelationship(Node entity) {
.findFirst();
}

public static Optional<Node> getCurrentState(Node entity) {
return streamOfIterable(entity.getRelationships(RelationshipType.withName(CURRENT_TYPE), Direction.OUTGOING)).map(relationship -> relationship.getEndNode()).findFirst();
}

public static LocalDateTime convertEpochToLocalDateTime(Long epochDateTime) {
return Instant.ofEpochMilli(epochDateTime).atZone(ZoneId.systemDefault()).toLocalDateTime();
}

public static Boolean isSystemType(String type) {
public static <A, B> List<org.apache.commons.lang3.tuple.Pair<A, B>> zip(List<A> listA, List<B> listB) {
return IntStream.range(0, listA.size())
.mapToObj(i -> Pair.of(listA.get(i), listB.get(i)))
.collect(Collectors.toList());

}

public static boolean isSystemType(String type) {
return SYSTEM_RELS.contains(type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.logging.Log;

import java.lang.reflect.InvocationTargetException;
import java.util.Optional;

/**
Expand All @@ -13,7 +14,7 @@
*/
public abstract class CoreProcedureBuilder<T extends CoreProcedure> {

private Class<T> clazz;
private final Class<T> clazz;

private GraphDatabaseService db;
private Log log;
Expand Down Expand Up @@ -62,13 +63,12 @@ public CoreProcedureBuilder<T> withLog(Log log) {
* @return instance
*/
Optional<T> instantiate() {
T instance;
T instance = null;
try {
instance = clazz.newInstance();
instance = clazz.getDeclaredConstructor().newInstance();
instance.db = db;
instance.log = log;
} catch (InstantiationException | IllegalAccessException e) {
instance = null;
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e ) {
log.error(e.getMessage());
}
return Optional.ofNullable(instance);
Expand Down
78 changes: 40 additions & 38 deletions src/main/java/org/homer/versioner/core/procedure/Get.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.homer.versioner.core.Utility;
import org.homer.versioner.core.output.NodeOutput;
import org.homer.versioner.core.output.PathOutput;
import org.neo4j.cypher.internal.compiler.v2_3.commands.expressions.PathValueBuilder;
import org.neo4j.graphalgo.impl.util.PathImpl;
import org.neo4j.graphdb.*;
import org.neo4j.procedure.Description;
Expand Down Expand Up @@ -44,22 +45,23 @@ public Stream<NodeOutput> getCurrentState(
.map(Relationship::getEndNode).map(NodeOutput::new).orElse(null));
}


//fix for error Node[xyz] not connected to this relationship[xyz]
@Procedure(value = "graph.versioner.get.all", mode = DEFAULT)
@Description("graph.versioner.get.all(entity) - Get all the State nodes for the given Entity.")
public Stream<PathOutput> getAllState(
@Name("entity") Node entity) {

PathImpl.Builder builder = new PathImpl.Builder(entity)
.push(entity.getSingleRelationship(RelationshipType.withName(Utility.CURRENT_TYPE), Direction.OUTGOING));
builder = StreamSupport.stream(entity.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.OUTGOING).spliterator(), false)
//.sorted((a, b) -> -1 * Long.compare((long)a.getProperty(START_DATE_PROP), (long)b.getProperty(START_DATE_PROP)))
.reduce(
builder,
(build, rel) -> Optional.ofNullable(rel.getEndNode().getSingleRelationship(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING))
.map(build::push)
.orElse(build),
(a, b) -> a);
return Stream.of(new PathOutput(builder.build()));
PathValueBuilder builder = new PathValueBuilder();
builder.addNode(entity);
builder.addOutgoingRelationship(entity.getSingleRelationship(RelationshipType.withName(Utility.CURRENT_TYPE), Direction.OUTGOING));
StreamSupport.stream(entity.getRelationships(RelationshipType.withName(Utility.HAS_STATE_TYPE), Direction.OUTGOING).spliterator(), false)
.forEach(rel ->
Optional.ofNullable(rel.getEndNode().getSingleRelationship(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING))
.map(builder::addOutgoingRelationship)
);

return Stream.of(new PathOutput(builder.result()));
}

@Procedure(value = "graph.versioner.get.by.label", mode = DEFAULT)
Expand All @@ -86,31 +88,31 @@ public Stream<NodeOutput> getStateByDate(
.map(NodeOutput::new);
}

@Procedure(value = "graph.versioner.get.nth.state", mode = DEFAULT)
@Description("graph.versioner.get.nth.state(entity, nth) - Get the nth State node for the given Entity.")
public Stream<NodeOutput> getNthState(
@Name("entity") Node entity,
@Name("nth") long nth) {

return getCurrentState(entity)
.findFirst()
.flatMap(currentState -> getNthStateFrom(currentState.node, nth))
.map(Utility::streamOfNodes)
.orElse(Stream.empty());
}

private Optional<Node> getNthStateFrom(Node state, long nth) {

return Stream.iterate(Optional.of(state), s -> s.flatMap(this::jumpToPreviousState))
.limit(nth + 1)
.reduce((a, b) -> b) //get only the last value (apply jumpToPreviousState n times
.orElse(Optional.empty());
}

private Optional<Node> jumpToPreviousState(Node state) {

return StreamSupport.stream(state.getRelationships(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING).spliterator(), false)
.findFirst()
.map(Relationship::getEndNode);
}
@Procedure(value = "graph.versioner.get.nth.state", mode = DEFAULT)
@Description("graph.versioner.get.nth.state(entity, nth) - Get the nth State node for the given Entity.")
public Stream<NodeOutput> getNthState(
@Name("entity") Node entity,
@Name("nth") long nth) {

return getCurrentState(entity)
.findFirst()
.flatMap(currentState -> getNthStateFrom(currentState.node, nth))
.map(Utility::streamOfNodes)
.orElse(Stream.empty());
}

private Optional<Node> getNthStateFrom(Node state, long nth) {

return Stream.iterate(Optional.of(state), s -> s.flatMap(this::jumpToPreviousState))
.limit(nth + 1)
.reduce((a, b) -> b) //get only the last value (apply jumpToPreviousState n times
.orElse(Optional.empty());
}

private Optional<Node> jumpToPreviousState(Node state) {

return StreamSupport.stream(state.getRelationships(RelationshipType.withName(Utility.PREVIOUS_TYPE), Direction.OUTGOING).spliterator(), false)
.findFirst()
.map(Relationship::getEndNode);
}
}
Loading

0 comments on commit c268139

Please sign in to comment.