/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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 org.hawkular.inventory.impl.tinkerpop;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.__;
import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.has;
import static org.hawkular.inventory.api.Relationships.Direction.incoming;
import static org.hawkular.inventory.api.Relationships.Direction.outgoing;
import static org.hawkular.inventory.api.Relationships.WellKnown.contains;
import static org.hawkular.inventory.api.Relationships.WellKnown.defines;
import static org.hawkular.inventory.api.Relationships.WellKnown.hasData;
import static org.hawkular.inventory.impl.tinkerpop.HawkularTraversal.hwk;
import static org.hawkular.inventory.impl.tinkerpop.HawkularTraversal.hwk__;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.InternalEdge.__inState;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__changeKind;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__cp;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__eid;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__from;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__sourceCp;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__sourceEid;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__sourceType;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__targetCp;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__targetEid;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__targetType;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__to;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Property.__type;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Type.relationship;
import static org.hawkular.inventory.impl.tinkerpop.spi.Constants.Type.structuredData;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
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.process.traversal.lambda.TrueTraversal;
import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Element;
import org.apache.tinkerpop.gremlin.structure.Property;
import org.apache.tinkerpop.gremlin.structure.Vertex;
import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONWriter;
import org.apache.tinkerpop.gremlin.structure.util.ElementHelper;
import org.hawkular.inventory.api.Action;
import org.hawkular.inventory.api.EntityNotFoundException;
import org.hawkular.inventory.api.Query;
import org.hawkular.inventory.api.Relationships;
import org.hawkular.inventory.api.filters.Filter;
import org.hawkular.inventory.api.filters.Related;
import org.hawkular.inventory.api.filters.RelationFilter;
import org.hawkular.inventory.api.filters.With;
import org.hawkular.inventory.api.model.AbstractElement;
import org.hawkular.inventory.api.model.Blueprint;
import org.hawkular.inventory.api.model.DataEntity;
import org.hawkular.inventory.api.model.ElementBlueprintVisitor;
import org.hawkular.inventory.api.model.ElementUpdateVisitor;
import org.hawkular.inventory.api.model.ElementVisitor;
import org.hawkular.inventory.api.model.Entity;
import org.hawkular.inventory.api.model.Environment;
import org.hawkular.inventory.api.model.Feed;
import org.hawkular.inventory.api.model.Hashes;
import org.hawkular.inventory.api.model.MetadataPack;
import org.hawkular.inventory.api.model.Metric;
import org.hawkular.inventory.api.model.MetricDataType;
import org.hawkular.inventory.api.model.MetricType;
import org.hawkular.inventory.api.model.MetricUnit;
import org.hawkular.inventory.api.model.OperationType;
import org.hawkular.inventory.api.model.Relationship;
import org.hawkular.inventory.api.model.Resource;
import org.hawkular.inventory.api.model.ResourceType;
import org.hawkular.inventory.api.model.StructuredData;
import org.hawkular.inventory.api.model.Tenant;
import org.hawkular.inventory.api.paging.Order;
import org.hawkular.inventory.api.paging.Page;
import org.hawkular.inventory.api.paging.Pager;
import org.hawkular.inventory.base.spi.CommitFailureException;
import org.hawkular.inventory.base.spi.Discriminator;
import org.hawkular.inventory.base.spi.ElementNotFoundException;
import org.hawkular.inventory.base.spi.EntityHistory;
import org.hawkular.inventory.base.spi.EntityStateChange;
import org.hawkular.inventory.base.spi.InconsistenStateException;
import org.hawkular.inventory.base.spi.InventoryBackend;
import org.hawkular.inventory.base.spi.ShallowStructuredData;
import org.hawkular.inventory.impl.tinkerpop.spi.Constants;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.DataRole;
import org.hawkular.inventory.paths.RelativePath;
import org.hawkular.inventory.paths.SegmentType;
/**
* @author Lukas Krejci
* @since 0.1.0
*/
final class TinkerpopBackend implements InventoryBackend<Element> {
private final InventoryContext context;
public TinkerpopBackend(InventoryContext context) {
this.context = context;
}
/**
* If the properties map contains a key from the disallowed properties, throw an exception.
*
* @param properties the properties to check
* @param disallowedProperties the list of property names that cannot appear in the provided map
* @throws IllegalArgumentException if the map contains one or more disallowed keys
*/
static void checkProperties(Map<String, Object> properties, String[] disallowedProperties) {
if (properties == null || properties.isEmpty()) {
return;
}
HashSet<String> disallowed = new HashSet<>(properties.keySet());
disallowed.retainAll(Arrays.asList(disallowedProperties));
if (!disallowed.isEmpty()) {
throw new IllegalArgumentException("The following properties are reserved for this type of entity: "
+ Arrays.asList(disallowedProperties));
}
}
/**
* Updates the properties of the element, disregarding any changes of the disallowed properties
* <p>
* <p> The list of the disallowed properties will usually come from
* {@link Constants.Type#getMappedProperties()}.
*
* @param e the element to update properties of
* @param properties the properties to update
* @param disallowedProperties the list of properties that are not allowed to change.
*/
static void updateProperties(Element e, Map<String, Object> properties, String[] disallowedProperties) {
if (properties == null) {
return;
}
Set<String> disallowed = new HashSet<>(Arrays.asList(disallowedProperties));
//remove all non-mapped properties, that are not in the update
Spliterator<Property<?>> sp = Spliterators.spliteratorUnknownSize(e.properties(),
Spliterator.NONNULL & Spliterator.IMMUTABLE);
Property<?>[] toRemove = StreamSupport.stream(sp, false)
.filter((p) -> !disallowed.contains(p.key()) && !properties.containsKey(p.key()))
.toArray(Property[]::new);
for (Property<?> p : toRemove) {
p.remove();
}
//update and add new the properties
properties.forEach((p, v) -> {
if (!disallowed.contains(p)) {
e.property(p, v);
}
});
}
/**
* Gets the type of the entity that the provided vertex represents.
*/
static Constants.Type getType(Vertex v) {
return Constants.Type.valueOf((String) v.property(__type.name()).value());
}
static Direction toNative(Relationships.Direction direction) {
return direction == incoming ? Direction.IN : (direction == outgoing ? Direction.OUT : Direction.BOTH);
}
static Direction asDirection(Related.EntityRole role) {
return role == Related.EntityRole.SOURCE ? Direction.OUT : (role == Related.EntityRole.TARGET ? Direction.IN
: Direction.BOTH);
}
private static org.apache.tinkerpop.gremlin.process.traversal.Order toTinkerpopOrder(Order.Direction direction) {
switch (direction) {
case ASCENDING:
return org.apache.tinkerpop.gremlin.process.traversal.Order.incr;
case DESCENDING:
return org.apache.tinkerpop.gremlin.process.traversal.Order.decr;
default:
throw new IllegalStateException("Unsupported order direction: " + direction);
}
}
@Override public boolean isUniqueIndexSupported() {
return context.isUniqueIndexSupported();
}
@Override public boolean isPreferringBigTransactions() {
return context.isPreferringBigTransactions();
}
@Override
public InventoryBackend<Element> startTransaction() {
return new TinkerpopBackend(context.cloneWith(context.startTransaction()));
}
@Override
public Element find(Discriminator discriminator, CanonicalPath path) throws ElementNotFoundException {
Iterator<? extends Element> it;
if (SegmentType.rl.equals(path.getSegment().getElementType())) {
//__eid is globally unique for relationships
it = hwk(context.getGraph().traversal().E()).has(__eid.name(), path.getSegment().getElementId())
.restrictTo(discriminator);
} else {
it = hwk(context.getGraph().traversal().V())
.hasLabel(Constants.Type.of(path.getSegment().getElementType()).name())
.has(__cp.name(), path.toString())
.existsAt(discriminator);
}
if (!it.hasNext()) {
throw new ElementNotFoundException();
}
return it.next();
}
@SuppressWarnings("unchecked")
@Override
public Page<Element> traverse(Discriminator discriminator, Element startingPoint, Query query, Pager pager) {
HawkularTraversal<?, ? extends Element> q = translate(discriminator, startingPoint, query);
Log.LOG.debugf("Query execution (starting at %s):\nquery:\n%s\n\npipeline:\n%s", startingPoint, query, q);
return page(q, pager, Function.identity());
}
@Override public Element traverseToSingle(Discriminator discriminator, Element startingPoint, Query query) {
HawkularTraversal<?, ? extends Element> q = translate(discriminator, startingPoint, query);
Log.LOG.debugf("Query execution (starting at %s):\nquery:\n%s\n\npipeline:\n%s", startingPoint, query, q);
return drainAfter(q, () -> {
if (q.hasNext()) {
return q.next();
}
return null;
});
}
@Override
public Page<Element> query(Discriminator discriminator, Query query, Pager pager) {
return traverse(discriminator, null, query, pager);
}
@Override public Element querySingle(Discriminator discriminator, Query query) {
return traverseToSingle(discriminator, null, query);
}
private HawkularTraversal<?, ? extends Element> translate(Discriminator discriminator, Element startingPoint,
Query query) {
GraphTraversal<?, ? extends Element> q;
boolean inEdges = false;
if (startingPoint == null) {
Filter first = query.getFragments()[0].getFilter();
if (first instanceof RelationFilter) {
q = context.getGraph().traversal().E();
inEdges = true;
} else if (first instanceof With.CanonicalPaths) {
//XXX this does NOT handle the situation where we mix relationships and entities in one filter
SegmentType elementType = ((With.CanonicalPaths) first).getPaths()[0].getSegment().getElementType();
if (SegmentType.rl == elementType) {
q = context.getGraph().traversal().E();
inEdges = true;
} else {
q = context.getGraph().traversal().V();
}
} else {
q = context.getGraph().traversal().V();
}
} else {
q = __(startingPoint);
inEdges = startingPoint instanceof Edge;
}
HawkularTraversal<?, ? extends Element> ret = hwk(q);
FilterApplicator.applyAll(discriminator, query, ret, inEdges);
return ret;
}
@Override
public <T> Page<T> query(Discriminator discriminator, Query query, Pager pager,
Function<Element, T> conversion, Function<T, Boolean> filter) {
HawkularTraversal<?, ? extends Element> q = translate(discriminator, null, query);
//XXX this probably would be more efficient as a proper pipe
q.filter(e -> !isBackendInternal(e.get()));
if (filter == null) {
return page(q, pager, conversion);
} else {
//the ResultFilter interface requires an entity to check its applicability and can rule out some of the
//entities from the result set, which affects the total count. We therefore need to convert to entity first
//and only then filter, count and page.
//Note that it would not be enough to pass the current path and the ID to the filter, because for the filter
//to have stable ids, it needs to have the "canonical" path to the entity, which the inventory traversal
//path might not be. The transformation of a non-canonical to canonical path is essentially identical
//operation to converting the vertex to the entity.
return page(
q.map(t -> conversion.apply(t.get())).filter(t -> filter.apply(t.get())),
pager, Function.identity());
}
}
@Override
public Iterator<Element> getTransitiveClosureOver(Discriminator discriminator, Element startingPoint,
Relationships.Direction direction,
String... relationshipNames) {
return getTransitiveClosureOverImpl(discriminator, startingPoint, direction, relationshipNames).iterator();
}
@Override
public <T extends Entity<?, ?>> Iterator<T> getTransitiveClosureOver(Discriminator discriminator,
CanonicalPath startingPoint,
Relationships.Direction direction,
Class<T> clazz,
String... relationshipNames) {
try {
Element startingElement = find(discriminator, startingPoint);
if (!(startingElement instanceof Vertex)) {
return Collections.emptyIterator();
}
return getTransitiveClosureOverImpl(discriminator, startingElement, direction, relationshipNames).stream()
.map(vertex -> convert(discriminator, vertex, clazz)).iterator();
} catch (ElementNotFoundException e) {
throw new EntityNotFoundException(clazz, null);
}
}
@SuppressWarnings("unchecked")
private List<Element> getTransitiveClosureOverImpl(Discriminator discriminator, Element startingPoint,
Relationships.Direction direction, String... relationshipNames) {
if (!(startingPoint instanceof Vertex)) {
return emptyList();
} else {
GraphTraversal<?, ? extends Element> loop;
switch (direction) {
case incoming:
loop = __.in(relationshipNames);
break;
case outgoing:
loop = __.out(relationshipNames);
break;
case both:
loop = __.both(relationshipNames);
break;
default:
throw new IllegalStateException("Unhandled traversal direction: " + direction);
}
Traversal<?, ?> emitCondition = discriminator == null
? TrueTraversal.instance()
: hwk__().existsAt(discriminator);
//toList() is important as it ensures eager evaluation of the closure - the callers might modify the
//conditions for the evaluation during the iteration which would skew the results.
return (List<Element>) (List) __((Vertex) startingPoint).repeat((Traversal<?, Vertex>) loop)
.emit(emitCondition)
.toList();
}
}
@Override
public boolean hasRelationship(Discriminator discriminator, Element entity, Relationships.Direction direction,
String relationshipName) {
if (!(entity instanceof Vertex)) {
return false;
}
Iterator<Edge> it = hwk__(entity).toE(toNative(direction), relationshipName).restrictTo(discriminator);
return closeAfter(it, it::hasNext);
}
@Override
public boolean hasRelationship(Discriminator discriminator, Element source, Element target,
String relationshipName) {
if (!(source instanceof Vertex) || !(target instanceof Vertex)) {
return false;
}
Iterator<Vertex> targets = hwk(__(source)).outE(relationshipName).restrictTo(discriminator).inV();
return closeAfter(targets, () -> {
while (targets.hasNext()) {
if (target.equals(targets.next())) {
return true;
}
}
return false;
});
}
@Override
public Element getRelationship(Discriminator discriminator, Element source, Element target, String relationshipName)
throws ElementNotFoundException {
if (!(source instanceof Vertex) || !(target instanceof Vertex)) {
throw new IllegalArgumentException("Source or target entity not a vertex.");
}
if (relationshipName == null) {
throw new IllegalArgumentException("relationshipName == null");
}
Vertex t = (Vertex) target;
Iterator<Edge> it = hwk(__(source)).outE(relationshipName)
.restrictTo(discriminator).has(__targetCp.name(), t.property(__cp.name()).value());
try {
if (!it.hasNext()) {
throw new ElementNotFoundException();
} else {
return it.next();
}
} finally {
closeIfNeeded(it);
}
}
@Override
public Set<Element> getRelationships(Discriminator discriminator, Element entity, Relationships.Direction direction,
String... names) {
if (!(entity instanceof Vertex)) {
return Collections.emptySet();
}
Vertex v = (Vertex) entity;
HawkularTraversal<?, Element> q = hwk(__(v));
switch (direction) {
case incoming:
q.inE(names).restrictTo(discriminator);
break;
case outgoing:
q.outE(names).restrictTo(discriminator);
break;
case both:
q.bothE(names).restrictTo(discriminator);
break;
default:
throw new AssertionError("Invalid relationship direction specified: " + direction);
}
Spliterator<Element> sp = Spliterators.spliteratorUnknownSize(q, Spliterator.NONNULL & Spliterator.IMMUTABLE);
return drainAfter(q,
() -> StreamSupport.stream(sp, false).filter(e -> !isBackendInternal(e)).collect(toSet()));
}
@Override
public String extractRelationshipName(Element relationship) {
return relationship.label();
}
@Override
public Element getRelationshipSource(Discriminator discriminator, Element relationship) {
return ((Edge) relationship).outVertex();
}
@Override
public Element getRelationshipTarget(Discriminator discriminator, Element relationship) {
return ((Edge) relationship).inVertex();
}
@Override
public String extractId(Element entityRepresentation) {
return entityRepresentation.<String>property(__eid.name()).orElse(null);
}
@Override
public Class<?> extractType(Element entityRepresentation) {
if (entityRepresentation instanceof Edge) {
return Relationship.class;
} else {
return getType((Vertex) entityRepresentation).getEntityType();
}
}
@Override
public CanonicalPath extractCanonicalPath(Element entityRepresentation) {
String cp = entityRepresentation.<String>property(__cp.name()).orElse(null);
if (cp == null) {
throw new IllegalArgumentException("Element is not representable using a canonical path. Element type is "
+ extractType(entityRepresentation).getSimpleName() + ", element id is '"
+ extractId(entityRepresentation) + "'.");
}
return CanonicalPath.fromString(cp);
}
@Override
public String extractIdentityHash(Discriminator discriminator, Element entityRepresentation) {
return _extractIdentityHash(getStateOf(entityRepresentation, discriminator));
}
private String _extractIdentityHash(Vertex stateVertex) {
return stateVertex.<String>property(Constants.Property.__identityHash.name()).orElse(null);
}
public String extractContentHash(Discriminator discriminator, Element entityRepresentation) {
return _extractContentHash(getStateOf(entityRepresentation, discriminator));
}
private String _extractContentHash(Vertex stateVertex) {
return stateVertex.<String>property(Constants.Property.__contentHash.name()).orElse(null);
}
public String extractSyncHash(Discriminator discriminator, Element entityRepresentation) {
return _extractSyncHash(getStateOf(entityRepresentation, discriminator));
}
private String _extractSyncHash(Vertex stateVertex) {
return stateVertex.<String>property(Constants.Property.__syncHash.name()).orElse(null);
}
@Override public void updateHashes(Discriminator discriminator, Element entity, Hashes hashes) {
Vertex stateVertex = getStateOf(entity, discriminator);
setNonNullProperty(stateVertex, Constants.Property.__contentHash.name(), hashes.getContentHash());
setNonNullProperty(stateVertex, Constants.Property.__syncHash.name(), hashes.getSyncHash());
updateIdentityHash(discriminator, entity, stateVertex, hashes.getIdentityHash());
}
private void updateIdentityHash(Discriminator discriminator, Element entity, Vertex stateVertex,
String identityHash) {
Objects.requireNonNull(entity, "entity == null");
if (!(entity instanceof Vertex)) {
return;
}
String oldIdentityHash = _extractIdentityHash(stateVertex);
if (Objects.equals(oldIdentityHash, identityHash)) {
return;
}
stateVertex.property(Constants.Property.__identityHash.name(), identityHash);
Vertex vertex = (Vertex) entity;
removeHashNodeOf(vertex);
//k, now we need to associate with a new hash node corresponding to our new identity hash
CanonicalPath cp = extractCanonicalPath(entity);
String tenantId = cp.ids().getTenantId();
Vertex tenantVertex = context.getGraph().traversal().V()
.has(__cp.name(), CanonicalPath.of().tenant(tenantId).get().toString()).next();
Iterator<Vertex> hashNodesIt = __(tenantVertex).outE(Constants.InternalEdge.__containsIdentityHash.name())
.has(Constants.Property.__targetIdentityHash.name(), identityHash).inV();
closeAfter(hashNodesIt, () -> {
if (hashNodesIt.hasNext()) {
Vertex hashNode = hashNodesIt.next();
vertex.addEdge(Constants.InternalEdge.__withIdentityHash.name(), hashNode);
} else {
Vertex hashNode = context.getGraph().addVertex(Constants.InternalType.__identityHash.name());
hashNode.property(Constants.Property.__identityHash.name(), identityHash);
hashNode.property(Constants.Property.__type.name(), Constants.InternalType.__identityHash.name());
Edge e = tenantVertex.addEdge(Constants.InternalEdge.__containsIdentityHash.name(), hashNode);
e.property(Constants.Property.__targetIdentityHash.name(), identityHash);
vertex.addEdge(Constants.InternalEdge.__withIdentityHash.name(), hashNode);
}
return null;
});
}
private void removeHashNodeOf(Vertex vertex) {
Iterator<Edge> hashNodeEdgeIt = vertex.edges(Direction.OUT,
Constants.InternalEdge.__withIdentityHash.name());
closeAfter(hashNodeEdgeIt, () -> {
if (hashNodeEdgeIt.hasNext()) {
Edge hashNodeEdge = hashNodeEdgeIt.next();
if (hashNodeEdgeIt.hasNext()) {
throw new InconsistenStateException(
"Entity with path: " + extractCanonicalPath(vertex) + " was associated " +
"with more than 1 hash node.");
}
//XXX do we need to do this check? It might be quite expensive to determine the count
//An alternative might be a periodical clean job.
//check if were are the last user of the hash node
Vertex hashNode = hashNodeEdge.inVertex();
Iterator<Edge> entitiesWithSameHash =
hashNode.edges(Direction.IN, Constants.InternalEdge.__withIdentityHash.name());
Spliterator<Edge> sp = Spliterators.spliteratorUnknownSize(entitiesWithSameHash,
Spliterator.IMMUTABLE & Spliterator.NONNULL);
if (StreamSupport.stream(sp, false).count() == 1) {
hashNode.remove();
} else {
hashNodeEdge.remove();
}
}
return null;
});
}
private Vertex getStateOf(Element identityVertex, Discriminator discriminator) {
HawkularTraversal<?, Vertex> q = hwk__(identityVertex).outE(__inState.name()).restrictTo(discriminator)
.inV();
return closeAfter(q, () -> {
if (q.hasNext()) {
return q.next();
} else {
return null;
}
});
}
@Override
public <T> T convert(Discriminator discriminator, Element entityRepresentation, Class<T> entityType) {
Constants.Type type = Constants.Type.of(extractType(entityRepresentation));
Object e;
String name = null;
Element stateElement = entityRepresentation;
if (type == relationship) {
Edge edge = (Edge) entityRepresentation;
CanonicalPath source = extractCanonicalPath(edge.outVertex());
CanonicalPath target = extractCanonicalPath(edge.inVertex());
e = new Relationship(extractId(edge), edge.label(), source, target);
} else {
Vertex v = (Vertex) entityRepresentation;
stateElement = type == structuredData ? v : getStateOf(v, discriminator);
Vertex state = (Vertex) stateElement;
name = state.<String>property(Constants.Property.name.name()).orElse(null);
Iterator<Vertex> it;
switch (type) {
case environment:
e = new Environment(extractCanonicalPath(v), _extractContentHash(state));
break;
case feed:
e = new Feed(extractCanonicalPath(v), _extractIdentityHash(state), _extractContentHash(state),
_extractSyncHash(state));
break;
case metric:
it = hwk__(v).inE(defines.name()).restrictTo(discriminator).outV();
Vertex mdv = closeAfter(it, it::next);
MetricType md = convert(discriminator, mdv, MetricType.class);
e = new Metric(extractCanonicalPath(v), _extractIdentityHash(state), _extractContentHash(state),
_extractSyncHash(state), md,
state.<Long>property(Constants.Property.__metric_interval.name()).orElse(null));
break;
case metricType:
e = new MetricType(extractCanonicalPath(v), _extractIdentityHash(state),
_extractContentHash(state), _extractSyncHash(state),
MetricUnit.fromDisplayName(
state.<String>property(Constants.Property.__unit.name()).value()),
MetricDataType.fromDisplayName(
state.<String>property(Constants.Property.__metric_data_type.name()).value()),
state.<Long>property(Constants.Property.__metric_interval.name()).value());
break;
case resource:
it = hwk__(v).inE(defines.name()).restrictTo(discriminator).outV();
Vertex rtv = closeAfter(it, it::next);
ResourceType rt = convert(discriminator, rtv, ResourceType.class);
e = new Resource(extractCanonicalPath(v), _extractIdentityHash(state), _extractContentHash(state),
_extractSyncHash(state), rt);
break;
case resourceType:
e = new ResourceType(extractCanonicalPath(v), _extractIdentityHash(state),
_extractContentHash(state), _extractSyncHash(state));
break;
case tenant:
e = new Tenant(extractCanonicalPath(v), _extractContentHash(state));
break;
case structuredData:
e = loadStructuredData(v, StructuredData.class.equals(entityType));
break;
case dataEntity:
CanonicalPath cp = extractCanonicalPath(v);
String identityHash = _extractIdentityHash(state);
e = new DataEntity(cp.up(), DataRole.valueOf(cp.getSegment().getElementId()),
loadStructuredData(discriminator, v, hasData), identityHash, _extractContentHash(state),
_extractSyncHash(state));
break;
case operationType:
e = new OperationType(extractCanonicalPath(v), _extractIdentityHash(state),
_extractContentHash(state), _extractSyncHash(state));
break;
case metadatapack:
e = new MetadataPack(extractCanonicalPath(v));
break;
default:
throw new IllegalArgumentException("Unknown type of vertex");
}
}
List<String> mappedProps = Arrays.asList(type.getMappedProperties());
Map<String, Object> filteredProperties = new HashMap<>();
stateElement.properties().forEachRemaining(p -> {
if (!mappedProps.contains(p.key())) {
filteredProperties.put(p.key(), p.value());
}
});
if (StructuredData.class.equals(entityType)) {
return entityType.cast(e);
} else if (ShallowStructuredData.class.equals(entityType)) {
return entityType.cast(new ShallowStructuredData((StructuredData) e));
} else {
@SuppressWarnings("ConstantConditions")
AbstractElement<?, ?> el = (AbstractElement<?, ?>) e;
String entityName = name;
return el.accept(new ElementVisitor<T, Void>() {
@Override
public T visitTenant(Tenant tenant, Void ignored) {
return common(tenant, Tenant.Update.builder());
}
@Override
public T visitEnvironment(Environment environment, Void ignored) {
return common(environment, Environment.Update.builder());
}
@Override
public T visitFeed(Feed feed, Void ignored) {
return common(feed, Feed.Update.builder());
}
@Override
public T visitMetric(Metric metric, Void ignored) {
return common(metric, Metric.Update.builder());
}
@Override
public T visitMetricType(MetricType metricType, Void ignored) {
return common(metricType, MetricType.Update.builder());
}
@Override
public T visitResource(Resource resource, Void ignored) {
return common(resource, Resource.Update.builder());
}
@Override
public T visitResourceType(ResourceType type, Void ignored) {
return common(type, ResourceType.Update.builder());
}
@Override
public T visitData(DataEntity data, Void ignored) {
return common(data, DataEntity.Update.builder());
}
@Override public T visitOperationType(OperationType operationType, Void parameter) {
return common(operationType, OperationType.Update.builder());
}
@Override public T visitMetadataPack(MetadataPack metadataPack, Void parameter) {
return common(metadataPack, MetadataPack.Update.builder());
}
@Override public T visitUnknown(Object entity, Void parameter) {
return null;
}
@Override
public T visitRelationship(Relationship relationship, Void parameter) {
return entityType.cast(relationship.update().with(Relationship.Update.builder()
.withProperties(filteredProperties).build()));
}
@SuppressWarnings("unchecked")
private <U extends Entity.Update> T common(Entity<?, U> entity, Entity.Update.Builder<?, U, ?> bld) {
return entityType.cast(entity.update().with(bld.withName(entityName)
.withProperties(filteredProperties).build()));
}
}, null);
}
}
@Override
public Element descendToData(Discriminator discriminator, Element dataEntityRepresentation, RelativePath dataPath) {
Query q = Query.path().with(With.dataAt(dataPath)).get();
HawkularTraversal<Element, Element> pipeline = hwk__(dataEntityRepresentation);
FilterApplicator.applyAll(discriminator, q, pipeline, false);
return drainAfter(pipeline, () -> {
if (pipeline.hasNext()) {
return pipeline.next();
} else {
return null;
}
});
}
@Override
public Element relate(Discriminator discriminator, Element sourceEntity, Element targetEntity, String name,
Map<String, Object> properties) {
if (name == null) {
throw new IllegalArgumentException("name == null");
}
if (!(sourceEntity instanceof Vertex)) {
throw new IllegalArgumentException("Source not a vertex.");
}
if (!(targetEntity instanceof Vertex)) {
throw new IllegalArgumentException("Target not a vertex.");
}
Edge e = ((Vertex) sourceEntity).addEdge(name, (Vertex) targetEntity);
if (properties != null) {
properties.forEach(e::property);
}
e.property(__eid.name(), e.id().toString());
e.property(__cp.name(), CanonicalPath.of().relationship(e.id().toString()).get().toString());
e.property(__sourceType.name(), sourceEntity.property(__type.name()).value());
setNonNullProperty(e, __targetType.name(), targetEntity.property(__type.name()).orElse(null));
setNonNullProperty(e, __sourceCp.name(), sourceEntity.property(__cp.name()).orElse(null));
setNonNullProperty(e, __targetCp.name(), targetEntity.property(__cp.name()).orElse(null));
setNonNullProperty(e, __sourceEid.name(), sourceEntity.property(__eid.name()).orElse(null));
setNonNullProperty(e, __targetEid.name(), targetEntity.property(__eid.name()).orElse(null));
e.property(__from.name(), discriminator.getTime().toEpochMilli());
e.property(__to.name(), Long.MAX_VALUE);
return e;
}
private void setNonNullProperty(Element el, String propertyName, Object propertyValue) {
if (propertyValue != null) {
el.property(propertyName, propertyValue);
}
}
@Override
public Element persist(Discriminator discriminator, CanonicalPath path, Blueprint blueprint) {
return blueprint.accept(new ElementBlueprintVisitor<Element, Void>() {
@Override
public Element visitTenant(Tenant.Blueprint tenant, Void parameter) {
return common(path, tenant.getName(), tenant.getProperties(), Tenant.class).first;
}
@Override
public Element visitEnvironment(Environment.Blueprint env, Void parameter) {
return common(path, env.getName(), env.getProperties(), Environment.class).first;
}
@Override
public Element visitFeed(Feed.Blueprint feed, Void parameter) {
return common(path, feed.getName(), feed.getProperties(), Feed.class).first;
}
@Override
public Element visitMetric(Metric.Blueprint metric, Void parameter) {
Pair<Vertex, Vertex> created = common(path, metric.getName(), metric.getProperties(), Metric.class);
if (metric.getCollectionInterval() != null) {
created.second.property(Constants.Property.__metric_interval.name(),
metric.getCollectionInterval());
}
return created.first;
}
@Override
public Element visitMetricType(MetricType.Blueprint type, Void parameter) {
Pair<Vertex, Vertex> created = common(path, type.getName(), type.getProperties(), MetricType.class);
created.second.property(Constants.Property.__metric_data_type.name(), type.getMetricDataType()
.getDisplayName());
created.second.property(Constants.Property.__metric_interval.name(), type.getCollectionInterval());
created.second.property(Constants.Property.__unit.name(), type.getUnit().getDisplayName());
return created.first;
}
@Override
public Element visitResource(Resource.Blueprint resource, Void parameter) {
return common(path, resource.getName(), resource.getProperties(), Resource.class).first;
}
@Override
public Element visitResourceType(ResourceType.Blueprint type, Void parameter) {
return common(path, type.getName(), type.getProperties(), ResourceType.class).first;
}
@Override
public Element visitRelationship(Relationship.Blueprint relationship, Void parameter) {
throw new IllegalArgumentException("Relationships cannot be persisted using the persist() method.");
}
@Override
public Element visitData(DataEntity.Blueprint data, Void parameter) {
return common(path, data.getName(), data.getProperties(), DataEntity.class).first;
}
@Override
public Element visitOperationType(OperationType.Blueprint operationType, Void parameter) {
return common(path, operationType.getName(), operationType.getProperties(), OperationType.class).first;
}
@Override
public Element visitMetadataPack(MetadataPack.Blueprint metadataPack, Void parameter) {
return common(path, metadataPack.getName(), metadataPack.getProperties(), MetadataPack.class).first;
}
@Override
public Element visitUnknown(Object blueprint, Void parameter) {
throw new IllegalArgumentException("Unknown type of entity blueprint: " + blueprint.getClass());
}
private Pair<Vertex, Vertex> common(org.hawkular.inventory.paths.CanonicalPath path, String name,
Map<String, Object> properties, Class<? extends Entity<?, ?>> cls) {
try {
Constants.Type type = Constants.Type.of(cls);
checkProperties(properties, type.getMappedProperties());
String cp = path.toString();
//check that there is no deleted vertex on our path...
Iterator<Vertex> check = hwk(context.getGraph().traversal().V())
.hasLabel(type.name())
.has(__cp.name(), cp)
.doesntExistAt(discriminator);
Vertex identity = closeAfter(check, () -> {
if (check.hasNext()) {
return check.next();
} else {
Vertex ret = context.getGraph().addVertex(type.name());
ret.property(__cp.name(), cp);
ret.property(__type.name(), Constants.Type.of(cls).name());
ret.property(__eid.name(), path.getSegment().getElementId());
return ret;
}
});
Vertex state = context.getGraph().addVertex(type.stateVertexLabel());
setNonNullProperty(state, Constants.Property.name.name(), name);
if (properties != null) {
properties.forEach(state::property);
}
Edge stateEdge = identity.addEdge(__inState.name(), state);
stateEdge.property(__from.name(), discriminator.getTime().toEpochMilli());
stateEdge.property(__to.name(), Long.MAX_VALUE);
stateEdge.property(__changeKind.name(), ChangeKind.create.ordinal());
return new Pair<>(identity, state);
} catch (RuntimeException e) {
throw context.translateException(e, path);
}
}
}, null);
}
@Override
public Vertex persist(StructuredData data) {
Vertex thisVertex = context.getGraph().addVertex(structuredData.name());
Pair<Vertex, Vertex> parentAndCurrent = new Pair<>(null, thisVertex);
data.accept(new StructuredData.Visitor.Simple<Void, StructuredData>() {
@Override
protected Void defaultAction(Serializable value, StructuredData data) {
relateToParent();
parentAndCurrent.second.property(__type.name(),
structuredData.name());
parentAndCurrent.second.property(Constants.Property.__structuredDataType.name(),
data.getType().name());
if (value != null) {
String propName;
Object val = value;
Class<?> valType = value.getClass();
if (Boolean.class == valType) {
propName = Constants.Property.__structuredDataValue_b.name();
} else if (Long.class == valType) {
propName = Constants.Property.__structuredDataValue_i.name();
} else if (Double.class == valType) {
propName = Constants.Property.__structuredDataValue_f.name();
} else {
propName = Constants.Property.__structuredDataValue_s.name();
val = value.toString(); //just to be sure
}
parentAndCurrent.second.property(propName, val);
}
return null;
}
@Override
public Void visitList(List<StructuredData> value, StructuredData data) {
relateToParent();
parentAndCurrent.second.property(Constants.Property.__structuredDataType.name(),
StructuredData.Type.list.name());
parentAndCurrent.second.property(__type.name(),
structuredData.name());
Vertex currentParent = parentAndCurrent.first;
Vertex currentCurrent = parentAndCurrent.second;
parentAndCurrent.first = parentAndCurrent.second;
int idx = 0;
for (StructuredData c : value) {
parentAndCurrent.second = context.getGraph().addVertex(structuredData.name());
parentAndCurrent.second.property(Constants.Property.__structuredDataIndex.name(), idx++);
c.accept(this, c);
}
parentAndCurrent.first = currentParent;
parentAndCurrent.second = currentCurrent;
return null;
}
@Override
public Void visitMap(Map<String, StructuredData> value, StructuredData data) {
relateToParent();
parentAndCurrent.second.property(__type.name(),
structuredData.name());
parentAndCurrent.second.property(Constants.Property.__structuredDataType.name(),
StructuredData.Type.map.name());
Vertex currentParent = parentAndCurrent.first;
Vertex currentCurrent = parentAndCurrent.second;
parentAndCurrent.first = parentAndCurrent.second;
int idx = 0;
for (Map.Entry<String, StructuredData> e : value.entrySet()) {
parentAndCurrent.second = context.getGraph().addVertex(structuredData.name());
//we need to make sure the maps are stored in the same order as seen - the maps are linked
//and therefore preserve insertion order
parentAndCurrent.second.property(Constants.Property.__structuredDataIndex.name(), idx++);
parentAndCurrent.second.property(Constants.Property.__structuredDataKey.name(), e.getKey());
e.getValue().accept(this, e.getValue());
}
parentAndCurrent.first = currentParent;
parentAndCurrent.second = currentCurrent;
return null;
}
private void relateToParent() {
if (parentAndCurrent.first != null) {
relate(Discriminator.time(Instant.now()), parentAndCurrent.first, parentAndCurrent.second,
Relationships.WellKnown.contains.name(), null);
}
}
}, data);
return thisVertex;
}
@Override
public void update(Discriminator discriminator, Element entity, AbstractElement.Update update) {
update.accept(new ElementUpdateVisitor.Simple<Void, Void>() {
@Override
public Void visitTenant(Tenant.Update tenant, Void parameter) {
common(tenant.getName(), tenant.getProperties(), Tenant.class);
return null;
}
@Override
public Void visitEnvironment(Environment.Update environment, Void parameter) {
common(environment.getName(), environment.getProperties(), Environment.class);
return null;
}
@Override
public Void visitFeed(Feed.Update feed, Void parameter) {
common(feed.getName(), feed.getProperties(), Feed.class);
return null;
}
@Override
public Void visitMetric(Metric.Update metric, Void parameter) {
Vertex state = common(metric.getName(), metric.getProperties(), Metric.class);
if (metric.getCollectionInterval() != null) {
state.property(Constants.Property.__metric_interval.name(), metric.getCollectionInterval());
} else {
state.property(Constants.Property.__metric_interval.name()).remove();
}
return null;
}
@Override
public Void visitMetricType(MetricType.Update type, Void parameter) {
Vertex state = common(type.getName(), type.getProperties(), MetricType.class);
if (type.getUnit() != null) {
state.property(Constants.Property.__unit.name(), type.getUnit().getDisplayName());
}
if (type.getCollectionInterval() != null) {
state.property(Constants.Property.__metric_interval.name(), type.getCollectionInterval());
}
return null;
}
@Override
public Void visitResource(Resource.Update resource, Void parameter) {
common(resource.getName(), resource.getProperties(), Resource.class);
return null;
}
@Override
public Void visitResourceType(ResourceType.Update type, Void parameter) {
common(type.getName(), type.getProperties(), ResourceType.class);
return null;
}
@Override
public Void visitRelationship(Relationship.Update relationship, Void parameter) {
String[] disallowedProperties = Constants.Type.of(Relationship.class).getMappedProperties();
checkProperties(relationship.getProperties(), disallowedProperties);
updateProperties(entity, relationship.getProperties(), disallowedProperties);
return null;
}
@Override
public Void visitData(DataEntity.Update data, Void parameter) {
common(data.getName(), data.getProperties(), DataEntity.class);
StructuredData dataValue = data.getValue();
if (dataValue == null) {
dataValue = StructuredData.get().undefined();
}
Element newData = persist(dataValue);
//update the existing hasData relationship to have an explicit end time
Edge hasData =
hwk__(entity).outE(Relationships.WellKnown.hasData.name()).has(__to.name(), Long.MAX_VALUE)
.next();
hasData.property(__to.name(), discriminator.getTime().toEpochMilli());
//and create a new relationship
relate(discriminator, entity, newData, Relationships.WellKnown.hasData.name(), null);
return null;
}
@Override
public Void visitOperationType(OperationType.Update operationType, Void parameter) {
common(operationType.getName(), operationType.getProperties(), OperationType.class);
return null;
}
private Vertex common(String name, Map<String, Object> properties,
Class<? extends AbstractElement<?, ?>> entityType) {
checkNoUpdatesAfter(discriminator, entity);
Class<?> actualType = extractType(entity);
if (!actualType.equals(entityType)) {
throw new IllegalArgumentException("Update object doesn't correspond to the actual type of the" +
" entity.");
}
String[] disallowedProperties = Constants.Type.of(entityType).getMappedProperties();
checkProperties(properties, disallowedProperties);
Iterator<Edge> lastStateEdgeIt = hwk__(entity).outE(__inState.name()).has(__to.name(), Long.MAX_VALUE);
if (!lastStateEdgeIt.hasNext()) {
throw new InconsistenStateException(
"Could not find the latest state of entity with canonical path: " +
extractCanonicalPath(entity));
}
Edge lastStateEdge = lastStateEdgeIt.next();
long time = discriminator.getTime().toEpochMilli();
lastStateEdge.property(__to.name(), time);
Vertex oldState = lastStateEdge.inVertex();
Vertex state = context.getGraph().addVertex(Constants.Type.of(actualType).stateVertexLabel());
ElementHelper.propertyValueMap(oldState).forEach(state::property);
setNonNullProperty(state, Constants.Property.name.name(), name);
updateProperties(state, properties, disallowedProperties);
Edge newStateEdge = ((Vertex) entity).addEdge(__inState.name(), state);
newStateEdge.property(__from.name(), time);
newStateEdge.property(__to.name(), Long.MAX_VALUE);
newStateEdge.property(__changeKind.name(), Action.updated().asEnum().ordinal());
return state;
}
}, null);
}
@Override
public void markDeleted(Discriminator discriminator, Element entity) {
long time = discriminator.getTime().toEpochMilli();
if (entity instanceof Vertex) {
checkNoUpdatesAfter(discriminator, entity);
Iterator<Edge> es = hwk__(entity).bothE().has(__to.name(), Long.MAX_VALUE);
while (es.hasNext()) {
Edge e = es.next();
if (e.label().equals(__inState.name())) {
ChangeKind priorState = ChangeKind.values()[e.<Integer>property(__changeKind.name()).value()];
e.property(__changeKind.name(), ChangeKind.deleteOf(priorState).ordinal());
}
e.property(__to.name(), time);
}
} else {
entity.property(__to.name(), time);
}
}
private void checkNoUpdatesAfter(Discriminator discriminator, Element entity) {
//fail if there has been an update AFTER the discriminator time on the vertex
if (discriminator.getTime().toEpochMilli() < Long.MAX_VALUE) {
Iterator<Edge> check = hwk__(entity).outE(__inState.name()).has(__to.name(),
P.inside(discriminator.getTime().toEpochMilli(), Long.MAX_VALUE));
if (check.hasNext()) {
throw new IllegalArgumentException(
"The element has been updated after the designated time of deletion: " +
discriminator.getTime());
}
}
}
@Override
public void eradicate(Element entity) {
if (entity instanceof Vertex) {
removeHashNodeOf((Vertex) entity);
}
entity.remove();
}
@Override
public void deleteStructuredData(Element dataRepresentation) {
if (!StructuredData.class.equals(extractType(dataRepresentation))) {
throw new IllegalArgumentException("The supplied element is not a data entity's data.");
}
Iterator<Element> dataElements =
getTransitiveClosureOverImpl(null, dataRepresentation, outgoing, contains.name()).iterator();
closeAfter(dataElements, () -> {
// we know the closure is constructed eagerly in this impl, so this loop is OK.
while (dataElements.hasNext()) {
eradicate(dataElements.next());
}
eradicate(dataRepresentation);
return null;
});
}
@Override
public void commit() throws CommitFailureException {
try {
context.commit();
Log.LOG.trace("Transaction committed: " + context.getGraph());
} catch (Exception e) {
throw new CommitFailureException(e);
}
}
@Override
public void rollback() {
context.rollback();
}
@Override
public boolean isBackendInternal(Element element) {
String match;
Enum<?>[] values;
if (element instanceof Vertex) {
match = element.<String>property(__type.name()).value();
values = Constants.InternalType.values();
} else {
match = element.label();
values = Constants.InternalEdge.values();
}
for (Enum<?> v : values) {
if (v.name().equals(match)) {
return true;
}
}
return false;
}
@Override
public void close() throws Exception {
context.getGraph().close();
}
@Override public boolean requiresRollbackAfterFailure(Throwable t) {
return context.requiresRollbackAfterFailure(t);
}
@Override
public <T extends Entity<?, U>, U extends Entity.Update>
EntityHistory<T> getHistory(Element entity, Class<T> entityType, Instant from, Instant to) {
if (entity instanceof Edge) {
throw new IllegalArgumentException("History not supported for relationships.");
}
long toTimestamp = to.toEpochMilli();
long fromTimestamp = from.toEpochMilli();
GraphTraversal<Edge, Edge> nonDeleteCheck = has(__from.name(), P.gte(fromTimestamp));
if (toTimestamp < Long.MAX_VALUE) {
nonDeleteCheck.has(__from.name(), P.lt(toTimestamp));
}
GraphTraversal<Edge, Edge> deleteCheck = __.<Edge>has(__changeKind.name(), P.within(
ChangeKind.delete_after_create.ordinal(), ChangeKind.delete_after_update.ordinal()))
.has(__to.name(), P.gte(fromTimestamp));
if (toTimestamp < Long.MAX_VALUE) {
deleteCheck.has(__to.name(), P.lt(toTimestamp));
}
GraphTraversal<Edge, Edge> initialStateCheck = __.or(
__.has(__from.name(), P.lt(fromTimestamp)).has(__to.name(), P.gte(fromTimestamp)),
__.has(__from.name(), P.eq(fromTimestamp)).has(__to.name(), P.eq(fromTimestamp))
);
GraphTraversal<?, Edge> q = context.getGraph().traversal().V(entity)
.outE(__inState.name())
.or(nonDeleteCheck, deleteCheck, initialStateCheck);
List<Edge> results = q.toList();
List<EntityStateChange<T>> changes = results.stream().flatMap(e -> {
ChangeKind changeKind = ChangeKind.values()[e.<Integer>property(__changeKind.name()).value()];
long st = e.<Long>property(__from.name()).value();
Instant start = Instant.ofEpochMilli(st);
long end = e.<Long>property(__to.name()).value();
T ent = convert(Discriminator.time(start), entity, entityType);
if (changeKind == ChangeKind.create) {
return Stream.of(new EntityStateChange<>(Action.created(), ent, start));
} else if (changeKind == ChangeKind.update) {
return Stream.of(new EntityStateChange<T>(Action.updated(), ent, start));
} else if (end < toTimestamp) {
if (st >= fromTimestamp) {
EntityStateChange<T> incomingChange;
if (changeKind == ChangeKind.delete_after_create) {
incomingChange = new EntityStateChange<>(Action.created(), ent, start);
} else {
incomingChange = new EntityStateChange<T>(Action.updated(), ent, start);
}
return Stream.of(incomingChange,
new EntityStateChange<>(Action.deleted(), ent, Instant.ofEpochMilli(end)));
} else {
return Stream.of(
new EntityStateChange<>(Action.deleted(), ent, Instant.ofEpochMilli(end)));
}
} else {
return Stream.of(new EntityStateChange<T>(Action.updated(), ent, start));
}
}).sorted().collect(toList());
T initialState;
if (changes.isEmpty()) {
initialState = null;
} else {
EntityStateChange<T> first = changes.get(0);
if (from.compareTo(first.getOccurrenceTime()) > 0) {
changes.remove(0);
initialState = first.getEntity();
} else {
initialState = null;
}
}
return new EntityHistory<T>(changes, initialState);
}
private StructuredData loadStructuredData(Discriminator discriminator, Vertex owner,
Relationships.WellKnown owningEdge) {
Iterator<Vertex> it = hwk__(owner).outE(owningEdge.name()).restrictTo(discriminator).inV();
if (!it.hasNext()) {
closeIfNeeded(it);
return null;
}
return loadStructuredData(closeAfter(it, it::next), true);
}
private StructuredData loadStructuredData(Vertex root, boolean recurse) {
StructuredData.Type type = StructuredData.Type.valueOf((String) root.property(
Constants.Property.__structuredDataType.name()).value());
switch (type) {
case bool:
return StructuredData.get()
.bool((Boolean) root.property(Constants.Property.__structuredDataValue_b.name()).value());
case integral:
return StructuredData.get()
.integral((Long) root.property(Constants.Property.__structuredDataValue_i.name()).value());
case floatingPoint:
return StructuredData.get()
.floatingPoint(
(Double) root.property(Constants.Property.__structuredDataValue_f.name()).value());
case undefined:
return StructuredData.get().undefined();
case string:
return StructuredData.get()
.string((String) root.property(Constants.Property.__structuredDataValue_s.name()).value());
case list:
StructuredData.ListBuilder lst = StructuredData.get().list();
if (recurse) {
loadStructuredDataList(root, lst);
}
return lst.build();
case map:
StructuredData.MapBuilder mp = StructuredData.get().map();
if (recurse) {
loadStructuredDataMap(root, mp);
}
return mp.build();
default:
throw new IllegalArgumentException("Unknown structured data type stored in db: " + type);
}
}
private void loadStructuredDataList(Vertex root, StructuredData.AbstractListBuilder<?> bld) {
Comparator<Vertex> orderFn = (a, b) -> {
Integer idxA = (Integer) a.property(Constants.Property.__structuredDataIndex.name()).value();
Integer idxB = (Integer) b.property(Constants.Property.__structuredDataIndex.name()).value();
return idxA - idxB;
};
Iterator<Vertex> it = __(root).out(contains.name()).order().by(orderFn);
while (it.hasNext()) {
Vertex child = it.next();
StructuredData.Type type = StructuredData.Type.valueOf(
(String) child.property(Constants.Property.__structuredDataType.name()).value());
switch (type) {
case bool:
bld.addBool((Boolean) child.property(Constants.Property.__structuredDataValue_b.name()).value());
break;
case integral:
bld.addIntegral((Long) child.property(Constants.Property.__structuredDataValue_i.name()).value());
break;
case floatingPoint:
bld.addFloatingPoint((Double) child.property(Constants.Property.__structuredDataValue_f.name())
.value());
break;
case undefined:
bld.addUndefined();
break;
case string:
bld.addString((String) child.property(Constants.Property.__structuredDataValue_s.name()).value());
break;
case list:
StructuredData.InnerListBuilder<?> lst = bld.addList();
loadStructuredDataList(child, lst);
lst.closeList();
break;
case map:
StructuredData.InnerMapBuilder<?> mp = bld.addMap();
loadStructuredDataMap(child, mp);
mp.closeMap();
break;
default:
throw new IllegalArgumentException("Unknown structured data type stored in db: " + type);
}
}
}
private void loadStructuredDataMap(Vertex root, StructuredData.AbstractMapBuilder<?> bld) {
Comparator<Vertex> orderFn = (a, b) -> {
Integer idxA = (Integer) a.property(Constants.Property.__structuredDataIndex.name()).value();
Integer idxB = (Integer) b.property(Constants.Property.__structuredDataIndex.name()).value();
return idxA - idxB;
};
Iterator<Vertex> it = __(root).out(contains.name()).order().by(orderFn);
while (it.hasNext()) {
Vertex v = it.next();
String key = (String) v.property(Constants.Property.__structuredDataKey.name()).value();
String type = (String) v.property(Constants.Property.__structuredDataType.name()).value();
switch (StructuredData.Type.valueOf(type)) {
case bool:
bld.putBool(key, (Boolean) v.property(Constants.Property.__structuredDataValue_b.name()).value());
break;
case integral:
bld.putIntegral(key, (Long) v.property(Constants.Property.__structuredDataValue_i.name()).value());
break;
case floatingPoint:
bld.putFloatingPoint(key, (Double) v.property(Constants.Property.__structuredDataValue_f.name())
.value());
break;
case undefined:
bld.putUndefined(key);
break;
case string:
bld.putString(key, (String) v.property(Constants.Property.__structuredDataValue_s.name()).value());
break;
case list:
StructuredData.InnerListBuilder<?> lst = bld.putList(key);
loadStructuredDataList(v, lst);
lst.closeList();
break;
case map:
StructuredData.InnerMapBuilder<?> mp = bld.putMap(key);
loadStructuredDataMap(v, mp);
mp.closeMap();
break;
default:
throw new IllegalArgumentException("Unknown structured data type stored in db: " + type);
}
}
}
public InputStream getGraphSON(Discriminator discriminator, String tenantId) {
PipedInputStream in = new PipedInputStream();
new Thread(() -> {
try (PipedOutputStream out = new PipedOutputStream(in)) {
GraphSONWriter.build().create().writeGraph(out, context.getGraph());
} catch (IOException e) {
throw new IllegalStateException("Unable to create the GraphSON dump.", e);
}
}).start();
return in;
}
private void drainIfNeeded(GraphTraversal<?, ?> pipeline) {
if (context.needsDraining()) {
pipeline.iterate();
}
}
private void closeIfNeeded(Iterator<?> it) {
if (context.needsDraining() && it instanceof AutoCloseable) {
try {
((AutoCloseable) it).close();
} catch (Exception e) {
throw new IllegalStateException("Failed to close a closeable result iterator.", e);
}
}
}
private <R> R closeAfter(Iterator<?> it, Supplier<R> payload) {
R ret = payload.get();
closeIfNeeded(it);
return ret;
}
private <R, S, E> R drainAfter(GraphTraversal<S, E> pipeline, Supplier<R> payload) {
R ret = payload.get();
drainIfNeeded(pipeline);
return ret;
}
private <T, U> Page<U> page(GraphTraversal<?, ? extends T> traversal, Pager pager, Function<T, U> transform) {
@SuppressWarnings("unchecked")
GraphTraversal<?, Map<String, Object>> paged = applyOrdering(traversal, pager)
.fold().as("results", "total").select("results", "total")
.by(__.coalesce(__.range(Scope.local, pager.getStart(), pager.getEnd()), __.constant(emptyList())))
.by(__.count(Scope.local));
if (!paged.hasNext()) {
return new Page<>(Collections.emptyIterator(), pager, 0);
}
Map<String, Object> data = paged.next();
long total = (Long) data.get("total");
Object res = data.get("results");
@SuppressWarnings("unchecked")
List<T> results = res instanceof List ? (List<T>) res : Collections.singletonList((T) res);
return new Page<>(results.stream().map(transform).iterator(), pager, total);
}
private <S, E> GraphTraversal<S, E> applyOrdering(GraphTraversal<S, E> traversal, Pager pager) {
boolean specific = pager.getOrder().stream().anyMatch(Order::isSpecific);
if (!specific) {
return traversal;
}
traversal.order();
pager.getOrder().stream().filter(Order::isSpecific).forEach(o -> {
String prop = Constants.Property.mapUserDefined(o.getField());
traversal.by(prop, toTinkerpopOrder(o.getDirection()));
});
return traversal;
}
private static final class Pair<F, S> {
public F first;
public S second;
public Pair(F first, S second) {
this.first = first;
this.second = second;
}
}
}