apoc.trigger.TriggerMetadata Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apoc-core Show documentation
Show all versions of apoc-core Show documentation
Core package for Neo4j Procedures
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package apoc.trigger;
import static apoc.util.Util.map;
import apoc.convert.ConvertUtils;
import apoc.result.VirtualNode;
import apoc.result.VirtualRelationship;
import apoc.util.Util;
import apoc.util.collection.Iterables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.neo4j.graphdb.Entity;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.event.LabelEntry;
import org.neo4j.graphdb.event.PropertyEntry;
import org.neo4j.graphdb.event.TransactionData;
public class TriggerMetadata {
private final long transactionId;
private final long commitTime;
private final List createdNodes;
private final List createdRelationships;
private final List deletedNodes;
private final List deletedRelationships;
private final Map> removedLabels;
private final Map>> removedNodeProperties;
private final Map>> removedRelationshipProperties;
private final Map> assignedLabels;
private final Map>> assignedNodeProperties;
private final Map>> assignedRelationshipProperties;
private final Map metaData;
private TriggerMetadata(
long transactionId,
long commitTime,
List createdNodes,
List createdRelationships,
List deletedNodes,
List deletedRelationships,
Map> removedLabels,
Map>> removedNodeProperties,
Map>> removedRelationshipProperties,
Map> assignedLabels,
Map>> assignedNodeProperties,
Map>> assignedRelationshipProperties,
Map metaData) {
this.transactionId = transactionId;
this.commitTime = commitTime;
this.createdNodes = createdNodes;
this.createdRelationships = createdRelationships;
this.deletedNodes = deletedNodes;
this.deletedRelationships = deletedRelationships;
this.removedLabels = removedLabels;
this.removedNodeProperties = removedNodeProperties;
this.removedRelationshipProperties = removedRelationshipProperties;
this.assignedLabels = assignedLabels;
this.assignedNodeProperties = assignedNodeProperties;
this.assignedRelationshipProperties = assignedRelationshipProperties;
this.metaData = metaData;
}
public static TriggerMetadata from(TransactionData txData, boolean rebindDeleted) {
long txId, commitTime;
try {
txId = txData.getTransactionId();
} catch (Exception ignored) {
txId = -1L;
}
try {
commitTime = txData.getCommitTime();
} catch (Exception ignored) {
commitTime = -1L;
}
List createdNodes = ConvertUtils.convertToList(txData.createdNodes());
List createdRelationships = ConvertUtils.convertToList(txData.createdRelationships());
List deletedNodes = rebindDeleted
? rebindDeleted(ConvertUtils.convertToList(txData.deletedNodes()), txData)
: ConvertUtils.convertToList(txData.deletedNodes());
List deletedRelationships = rebindDeleted
? rebindDeleted(ConvertUtils.convertToList(txData.deletedRelationships()), txData)
: ConvertUtils.convertToList(txData.deletedRelationships());
Map> removedLabels = aggregateLabels(txData.removedLabels());
Map> assignedLabels = aggregateLabels(txData.assignedLabels());
Map>> removedNodeProperties =
aggregatePropertyKeys(txData.removedNodeProperties(), true);
Map>> removedRelationshipProperties =
aggregatePropertyKeys(txData.removedRelationshipProperties(), true);
final Map>> assignedNodeProperties =
aggregatePropertyKeys(txData.assignedNodeProperties(), false);
final Map>> assignedRelationshipProperties =
aggregatePropertyKeys(txData.assignedRelationshipProperties(), false);
if (rebindDeleted) {
removedLabels = removedLabels.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> rebindDeleted(e.getValue(), txData)));
removedNodeProperties = rebindPropsEntries(txData, removedNodeProperties);
removedRelationshipProperties = rebindPropsEntries(txData, removedRelationshipProperties);
}
return new TriggerMetadata(
txId,
commitTime,
createdNodes,
createdRelationships,
deletedNodes,
deletedRelationships,
removedLabels,
removedNodeProperties,
removedRelationshipProperties,
assignedLabels,
assignedNodeProperties,
assignedRelationshipProperties,
txData.metaData());
}
private static Map>> rebindPropsEntries(
TransactionData txData, Map>> removedNodeProperties) {
return removedNodeProperties.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream()
.map(entry -> entry.copy((T) toVirtualEntity(txData, entry.entity)))
.collect(Collectors.toList())));
}
private static List rebindDeleted(List entities, TransactionData txData) {
return entities.stream().map(e -> (T) toVirtualEntity(txData, e)).collect(Collectors.toList());
}
private static Entity toVirtualEntity(TransactionData txData, T e) {
if (e instanceof Node) {
Node node = (Node) e;
final Label[] labels = Iterables.stream(txData.removedLabels())
.filter(label -> label.node().equals(node))
.map(LabelEntry::label)
.toArray(Label[]::new);
final Map props = getProps(txData.removedNodeProperties(), node);
return new VirtualNode(labels, props);
} else {
Relationship rel = (Relationship) e;
final Map props = getProps(txData.removedRelationshipProperties(), rel);
return new VirtualRelationship(rel.getStartNode(), rel.getEndNode(), rel.getType(), props);
}
}
private static Map getProps(
Iterable> propertyEntries, T entity) {
return Iterables.stream(propertyEntries)
.filter(label -> label.entity().equals(entity))
.collect(Collectors.toMap(PropertyEntry::key, PropertyEntry::previouslyCommittedValue));
}
public TriggerMetadata rebind(Transaction tx) {
final List createdNodes = Util.rebind(this.createdNodes, tx);
final List createdRelationships = Util.rebind(this.createdRelationships, tx);
// final List deletedNodes = Util.rebind(this.deletedNodes, tx);
// final List deletedRelationships = Util.rebind(this.deletedRelationships, tx);
final Map> removedLabels = rebindMap(this.removedLabels, tx);
final Map> assignedLabels = rebindMap(this.assignedLabels, tx);
final Map>> removedNodeProperties =
rebindPropertyEntryContainer(this.removedNodeProperties, tx);
final Map>> removedRelationshipProperties =
rebindPropertyEntryContainer(this.removedRelationshipProperties, tx);
final Map>> assignedNodeProperties =
rebindPropertyEntryContainer(this.assignedNodeProperties, tx);
final Map>> assignedRelationshipProperties =
rebindPropertyEntryContainer(this.assignedRelationshipProperties, tx);
return new TriggerMetadata(
transactionId,
commitTime,
createdNodes,
createdRelationships,
deletedNodes,
deletedRelationships,
removedLabels,
removedNodeProperties,
removedRelationshipProperties,
assignedLabels,
assignedNodeProperties,
assignedRelationshipProperties,
metaData);
}
private Map>> rebindPropertyEntryContainer(
Map>> map, Transaction tx) {
return map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().stream()
.map(p -> p.rebind(tx))
.collect(Collectors.toList())));
}
private Map> rebindMap(Map> map, Transaction tx) {
return map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Util.rebind(e.getValue(), tx)));
}
private Map>> convertMapOfPropertyEntryContainers(
Map>> map) {
return map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().stream()
.map(PropertyEntryContainer::toMap)
.collect(Collectors.toList())));
}
public Map toMap() {
return map(
"transactionId",
transactionId,
"commitTime",
commitTime,
"createdNodes",
createdNodes,
"createdRelationships",
createdRelationships,
"deletedNodes",
deletedNodes,
"deletedRelationships",
deletedRelationships,
"removedLabels",
removedLabels,
"removedNodeProperties",
convertMapOfPropertyEntryContainers(removedNodeProperties),
"removedRelationshipProperties",
convertMapOfPropertyEntryContainers(removedRelationshipProperties),
"assignedLabels",
assignedLabels,
"assignedNodeProperties",
convertMapOfPropertyEntryContainers(assignedNodeProperties),
"assignedRelationshipProperties",
convertMapOfPropertyEntryContainers(assignedRelationshipProperties),
"metaData",
metaData);
}
private static Map> aggregateLabels(Iterable labelEntries) {
if (!labelEntries.iterator().hasNext()) return Collections.emptyMap();
Map> result = new HashMap<>();
for (LabelEntry entry : labelEntries) {
result.compute(entry.label().name(), (k, v) -> {
if (v == null) v = new ArrayList<>(100);
v.add(entry.node());
return v;
});
}
return result;
}
private static class PropertyEntryContainer {
private final String key;
private final T entity;
private final Object oldVal;
private final Object newVal;
PropertyEntryContainer(String key, T entity, Object oldVal, Object newVal) {
this.key = key;
this.entity = entity;
this.oldVal = oldVal;
this.newVal = newVal;
}
PropertyEntryContainer rebind(Transaction tx) {
return new PropertyEntryContainer(key, Util.rebind(tx, entity), oldVal, newVal);
}
PropertyEntryContainer copy(T entity) {
return new PropertyEntryContainer(key, entity, oldVal, newVal);
}
Map toMap() {
final Map map =
map("key", key, entity instanceof Node ? "node" : "relationship", entity, "old", oldVal);
if (newVal != null) {
map.put("new", newVal);
}
return map;
}
}
private static Map>> aggregatePropertyKeys(
Iterable> entries, boolean removed) {
if (!entries.iterator().hasNext()) return Collections.emptyMap();
Map>> result = new HashMap<>();
for (PropertyEntry entry : entries) {
result.compute(entry.key(), (k, v) -> {
if (v == null) v = new ArrayList<>(100);
v.add(new PropertyEntryContainer<>(
k, entry.entity(), entry.previouslyCommittedValue(), removed ? null : entry.value()));
return v;
});
}
return result;
}
}