Java tutorial
/* * GRAKN.AI - THE KNOWLEDGE GRAPH * Copyright (C) 2018 Grakn Labs Ltd * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package grakn.core.server.kb.concept; import com.google.common.collect.Sets; import grakn.core.concept.Concept; import grakn.core.concept.ConceptId; import grakn.core.concept.Label; import grakn.core.concept.LabelId; import grakn.core.concept.thing.Attribute; import grakn.core.concept.thing.Relation; import grakn.core.concept.thing.Thing; import grakn.core.concept.type.AttributeType; import grakn.core.concept.type.RelationType; import grakn.core.concept.type.Role; import grakn.core.concept.type.SchemaConcept; import grakn.core.concept.type.Type; import grakn.core.server.exception.TransactionException; import grakn.core.server.kb.Schema; import grakn.core.server.kb.cache.Cache; import grakn.core.server.kb.structure.Casting; import grakn.core.server.kb.structure.EdgeElement; import grakn.core.server.kb.structure.VertexElement; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Vertex; import java.util.Arrays; import java.util.HashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; import static grakn.core.server.kb.Schema.EdgeProperty.ROLE_LABEL_ID; import static java.util.stream.Collectors.toSet; /** * A data instance in the graph belonging to a specific Type * Instances represent data in the graph. * Every instance belongs to a Type which serves as a way of categorising them. * Instances can relate to one another via Relation * * @param <T> The leaf interface of the object concept which extends Thing. * For example Entity or Relation. * @param <V> The type of the concept which extends Type of the concept. * For example EntityType or RelationType */ public abstract class ThingImpl<T extends Thing, V extends Type> extends ConceptImpl implements Thing { private final Cache<V> cachedType = new Cache<>(() -> { Optional<V> type = vertex().getEdgesOfType(Direction.OUT, Schema.EdgeLabel.ISA).map(EdgeElement::target) .flatMap(edge -> edge.getEdgesOfType(Direction.OUT, Schema.EdgeLabel.SHARD)) .map(EdgeElement::target).map(concept -> vertex().tx().factory().<V>buildConcept(concept)) .findAny(); return type.orElseThrow(() -> TransactionException.noType(this)); }); ThingImpl(VertexElement vertexElement) { super(vertexElement); } ThingImpl(VertexElement vertexElement, V type) { this(vertexElement); type((TypeImpl) type); track(); } /** * This Thing gets tracked for validation only if it has keys which need to be checked. */ private void track() { if (type().keys().findAny().isPresent()) { vertex().tx().cache().trackForValidation(this); } } public boolean isInferred() { return vertex().propertyBoolean(Schema.VertexProperty.IS_INFERRED); } /** * Deletes the concept as an Thing */ @Override public void delete() { //Remove links to relations and return them Set<Relation> relations = castingsInstance().map(casting -> { Relation relation = casting.getRelation(); Role role = casting.getRole(); relation.unassign(role, this); return relation; }).collect(toSet()); vertex().tx().statisticsDelta().decrement(type().label()); // decrement concept counts for non-reified edges - need to be explicitly handled before they are deleted by Janus this.edgeRelations() .forEach(relation -> vertex().tx().statisticsDelta().decrement(relation.type().label())); vertex().tx().cache().removedInstance(type().id()); deleteNode(); relations.forEach(relation -> { if (relation.type().isImplicit()) {//For now implicit relations die relation.delete(); } else { RelationImpl rel = (RelationImpl) relation; rel.cleanUp(); } }); } /** * This index is used by concepts such as casting and relations to speed up internal lookups * * @return The inner index value of some concepts. */ public String getIndex() { return vertex().property(Schema.VertexProperty.INDEX); } @Override public Stream<Attribute<?>> attributes(AttributeType... attributeTypes) { Set<ConceptId> attributeTypesIds = Arrays.stream(attributeTypes).map(Concept::id).collect(toSet()); return attributes(getShortcutNeighbours(true), attributeTypesIds); } @Override public Stream<Attribute<?>> keys(AttributeType... attributeTypes) { Set<ConceptId> attributeTypesIds = Arrays.stream(attributeTypes).map(Concept::id).collect(toSet()); Set<ConceptId> keyTypeIds = type().keys().map(Concept::id).collect(toSet()); if (!attributeTypesIds.isEmpty()) { keyTypeIds = Sets.intersection(attributeTypesIds, keyTypeIds); } if (keyTypeIds.isEmpty()) return Stream.empty(); return attributes(getShortcutNeighbours(true), keyTypeIds); } /** * Helper class which filters a Stream of Attribute to those of a specific set of AttributeType. * * @param conceptStream The Stream to filter * @param attributeTypesIds The AttributeType ConceptIds to filter to. * @return the filtered stream */ private <X extends Concept> Stream<Attribute<?>> attributes(Stream<X> conceptStream, Set<ConceptId> attributeTypesIds) { Stream<Attribute<?>> attributeStream = conceptStream .filter(concept -> concept.isAttribute() && !this.equals(concept)).map(Concept::asAttribute); if (!attributeTypesIds.isEmpty()) { attributeStream = attributeStream .filter(attribute -> attributeTypesIds.contains(attribute.type().id())); } return attributeStream; } /** * Castings are retrieved from the perspective of the Thing which is a role player in a Relation * * @return All the Casting which this instance is cast into the role */ Stream<Casting> castingsInstance() { return vertex().getEdgesOfType(Direction.IN, Schema.EdgeLabel.ROLE_PLAYER) .map(edge -> Casting.withThing(edge, this)); } private Set<Integer> implicitLabelsToIds(Set<Label> labels, Set<Schema.ImplicitType> implicitTypes) { return labels.stream().flatMap(label -> implicitTypes.stream().map(it -> it.getLabel(label))) .map(label -> vertex().tx().convertToId(label)).filter(id -> !id.equals(LabelId.invalid())) .map(LabelId::getValue).collect(toSet()); } <X extends Thing> Stream<X> getShortcutNeighbours(boolean ownerToValueOrdering, AttributeType... attributeTypes) { Set<AttributeType> completeAttributeTypes = new HashSet<>(Arrays.asList(attributeTypes)); if (completeAttributeTypes.isEmpty()) completeAttributeTypes.add(vertex().tx().getMetaAttributeType()); Set<Label> attributeHierarchyLabels = completeAttributeTypes.stream() .flatMap(t -> (Stream<AttributeType>) t.subs()).map(SchemaConcept::label).collect(toSet()); Set<Integer> ownerRoleIds = implicitLabelsToIds(attributeHierarchyLabels, Sets.newHashSet(Schema.ImplicitType.HAS_OWNER, Schema.ImplicitType.KEY_OWNER)); Set<Integer> valueRoleIds = implicitLabelsToIds(attributeHierarchyLabels, Sets.newHashSet(Schema.ImplicitType.HAS_VALUE, Schema.ImplicitType.KEY_VALUE)); //NB: need extra check cause it seems valid types can still produce invalid ids GraphTraversal<Vertex, Vertex> shortcutTraversal = !(ownerRoleIds.isEmpty() || valueRoleIds.isEmpty()) ? __.inE(Schema.EdgeLabel.ROLE_PLAYER.getLabel()).as("edge") .has(ROLE_LABEL_ID.name(), P.within(ownerToValueOrdering ? ownerRoleIds : valueRoleIds)) .outV().outE(Schema.EdgeLabel.ROLE_PLAYER.getLabel()) .has(ROLE_LABEL_ID.name(), P.within(ownerToValueOrdering ? valueRoleIds : ownerRoleIds)) .where(P.neq("edge")).inV() : __.inE(Schema.EdgeLabel.ROLE_PLAYER.getLabel()).as("edge").outV() .outE(Schema.EdgeLabel.ROLE_PLAYER.getLabel()).where(P.neq("edge")).inV(); GraphTraversal<Vertex, Vertex> attributeEdgeTraversal = __.outE(Schema.EdgeLabel.ATTRIBUTE.getLabel()) .inV(); //noinspection unchecked return vertex().tx().getTinkerTraversal().V().hasId(elementId()) .union(shortcutTraversal, attributeEdgeTraversal).toStream() .map(vertex -> vertex().tx().buildConcept(vertex)); } /** * @param roles An optional parameter which allows you to specify the role of the relations you wish to retrieve. * @return A set of Relations which the concept instance takes part in, optionally constrained by the Role Type. */ @Override public Stream<Relation> relations(Role... roles) { return Stream.concat(reifiedRelations(roles), edgeRelations(roles)); } private Stream<Relation> reifiedRelations(Role... roles) { GraphTraversal<Vertex, Vertex> traversal = vertex().tx().getTinkerTraversal().V().hasId(elementId()); if (roles.length == 0) { traversal.in(Schema.EdgeLabel.ROLE_PLAYER.getLabel()); } else { Set<Integer> roleTypesIds = Arrays.stream(roles).map(r -> r.labelId().getValue()).collect(toSet()); traversal.inE(Schema.EdgeLabel.ROLE_PLAYER.getLabel()) .has(Schema.EdgeProperty.ROLE_LABEL_ID.name(), P.within(roleTypesIds)).outV(); } return traversal.toStream().map(vertex -> vertex().tx().buildConcept(vertex)); } private Stream<Relation> edgeRelations(Role... roles) { Set<Role> roleSet = new HashSet<>(Arrays.asList(roles)); Stream<EdgeElement> stream = vertex().getEdgesOfType(Direction.BOTH, Schema.EdgeLabel.ATTRIBUTE); if (!roleSet.isEmpty()) { stream = stream.filter(edge -> { Role roleOwner = vertex().tx().getSchemaConcept( LabelId.of(edge.property(Schema.EdgeProperty.RELATION_ROLE_OWNER_LABEL_ID))); return roleSet.contains(roleOwner); }); } return stream.map(edge -> vertex().tx().factory().buildRelation(edge)); } @Override public Stream<Role> roles() { return castingsInstance().map(Casting::getRole); } @Override public T has(Attribute attribute) { relhas(attribute); return getThis(); } public Relation attributeInferred(Attribute attribute) { return attributeRelation(attribute, true); } @Override public Relation relhas(Attribute attribute) { return attributeRelation(attribute, false); } private Relation attributeRelation(Attribute attribute, boolean isInferred) { Schema.ImplicitType has = Schema.ImplicitType.HAS; Schema.ImplicitType hasValue = Schema.ImplicitType.HAS_VALUE; Schema.ImplicitType hasOwner = Schema.ImplicitType.HAS_OWNER; //Is this attribute a key to me? if (type().keys().anyMatch(key -> key.equals(attribute.type()))) { has = Schema.ImplicitType.KEY; hasValue = Schema.ImplicitType.KEY_VALUE; hasOwner = Schema.ImplicitType.KEY_OWNER; } Label label = attribute.type().label(); RelationType hasAttribute = vertex().tx().getSchemaConcept(has.getLabel(label)); Role hasAttributeOwner = vertex().tx().getSchemaConcept(hasOwner.getLabel(label)); Role hasAttributeValue = vertex().tx().getSchemaConcept(hasValue.getLabel(label)); if (hasAttribute == null || hasAttributeOwner == null || hasAttributeValue == null || type().playing().noneMatch(play -> play.equals(hasAttributeOwner))) { throw TransactionException.hasNotAllowed(this, attribute); } EdgeElement attributeEdge = addEdge(AttributeImpl.from(attribute), Schema.EdgeLabel.ATTRIBUTE); if (isInferred) attributeEdge.property(Schema.EdgeProperty.IS_INFERRED, true); vertex().tx().statisticsDelta().increment(hasAttribute.label()); return vertex().tx().factory().buildRelation(attributeEdge, hasAttribute, hasAttributeOwner, hasAttributeValue); } @Override public T unhas(Attribute attribute) { Role roleHasOwner = vertex().tx() .getSchemaConcept(Schema.ImplicitType.HAS_OWNER.getLabel(attribute.type().label())); Role roleKeyOwner = vertex().tx() .getSchemaConcept(Schema.ImplicitType.KEY_OWNER.getLabel(attribute.type().label())); Role roleHasValue = vertex().tx() .getSchemaConcept(Schema.ImplicitType.HAS_VALUE.getLabel(attribute.type().label())); Role roleKeyValue = vertex().tx() .getSchemaConcept(Schema.ImplicitType.KEY_VALUE.getLabel(attribute.type().label())); Stream<Relation> relations = relations(filterNulls(roleHasOwner, roleKeyOwner)); relations.filter(relation -> { Stream<Thing> rolePlayers = relation.rolePlayers(filterNulls(roleHasValue, roleKeyValue)); return rolePlayers.anyMatch(rolePlayer -> rolePlayer.equals(attribute)); }).forEach(Concept::delete); return getThis(); } /** * Returns an array with all the nulls filtered out. */ private Role[] filterNulls(Role... roles) { return Arrays.stream(roles).filter(Objects::nonNull).toArray(Role[]::new); } /** * @return The type of the concept casted to the correct interface */ public V type() { return cachedType.get(); } /** * @param type The type of this concept */ private void type(TypeImpl type) { if (type != null) { //noinspection unchecked cachedType.set((V) type); //We cache the type early because it turns out we use it EVERY time. So this prevents many db reads type.currentShard().link(this); setInternalType(type); } } /** * @param type Set property on vertex that stores Type label - this is needed by Analytics */ private void setInternalType(Type type) { vertex().property(Schema.VertexProperty.THING_TYPE_LABEL_ID, type.labelId().getValue()); } }