RYA-144 Mongo DB Entity index.
Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/c2a09dec Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/c2a09dec Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/c2a09dec Branch: refs/heads/master Commit: c2a09decdf8124d16d88246db53f05e51ea5a0a8 Parents: e00b688 Author: Kevin Chilton <[email protected]> Authored: Fri Sep 16 18:22:54 2016 -0400 Committer: pujav65 <[email protected]> Committed: Mon Mar 27 12:21:40 2017 -0400 ---------------------------------------------------------------------- .../indexing/entity/EntityIndexException.java | 57 +++ .../mvm/rya/indexing/entity/model/Entity.java | 329 ++++++++++++++++ .../mvm/rya/indexing/entity/model/Property.java | 83 ++++ .../mvm/rya/indexing/entity/model/Type.java | 102 +++++ .../rya/indexing/entity/model/TypedEntity.java | 242 ++++++++++++ .../indexing/entity/query/EntityQueryNode.java | 238 +++++++++++ .../entity/storage/CloseableIterator.java | 29 ++ .../indexing/entity/storage/EntityStorage.java | 153 ++++++++ .../indexing/entity/storage/TypeStorage.java | 106 +++++ .../entity/storage/mongo/ConvertingCursor.java | 76 ++++ .../entity/storage/mongo/DocumentConverter.java | 65 +++ .../storage/mongo/EntityDocumentConverter.java | 123 ++++++ .../storage/mongo/MongoEntityStorage.java | 244 ++++++++++++ .../entity/storage/mongo/MongoTypeStorage.java | 149 +++++++ .../storage/mongo/RyaTypeDocumentConverter.java | 69 ++++ .../storage/mongo/TypeDocumentConverter.java | 80 ++++ .../entity/update/BaseEntityIndexer.java | 266 +++++++++++++ .../indexing/entity/update/EntityIndexer.java | 31 ++ .../indexing/entity/update/EntityUpdater.java | 121 ++++++ .../entity/update/mongo/MongoEntityIndexer.java | 55 +++ .../entity/query/EntityQueryNodeTest.java | 194 +++++++++ .../mongo/EntityDocumentConverterTest.java | 60 +++ .../storage/mongo/MongoEntityStorageIT.java | 391 +++++++++++++++++++ .../entity/storage/mongo/MongoITBase.java | 81 ++++ .../storage/mongo/MongoTypeStorageIT.java | 174 +++++++++ .../mongo/RyaTypeDocumentConverterTest.java | 82 ++++ .../mongo/TypeDocumentConverterTest.java | 93 +++++ .../update/mongo/MongoEntityIndexerIT.java | 285 ++++++++++++++ 28 files changed, 3978 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/EntityIndexException.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/EntityIndexException.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/EntityIndexException.java new file mode 100644 index 0000000..a9e612c --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/EntityIndexException.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity; + +import mvm.rya.indexing.entity.model.TypedEntity; + +/** + * An operation over the {@link TypedEntity} index failed to complete. + */ +public class EntityIndexException extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with the specified detail message. The + * cause is not initialized, and may subsequently be initialized by + * a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public EntityIndexException(final String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause. <p>Note that the detail message associated with + * {@code cause} is <i>not</i> automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A <tt>null</tt> value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public EntityIndexException(final String message, final Throwable cause) { + super(message, cause); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Entity.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Entity.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Entity.java new file mode 100644 index 0000000..9ac5a3b --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Entity.java @@ -0,0 +1,329 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.model; + +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.Immutable; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; + +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.storage.EntityStorage; + +/** + * An {@link Entity} is a named concept that has at least one defined structure + * and a bunch of values that fit within each of those structures. A structure is + * defined by a {@link Type}. A value that fits within that Type is a {@link Property}. + * </p> + * For example, suppose we want to represent a type of icecream as an Entity. + * First we must define what properties an icecream entity may have: + * <pre> + * Type ID: <urn:icecream> + * Properties: <urn:brand> + * <urn:flavor> + * <urn:ingredients> + * <urn:nutritionalInformation> + * </pre> + * Now we can represent our icecream whose brand is "Awesome Icecream" and whose + * flavor is "Chocolate", but has no ingredients or nutritional information, as + * an Entity by doing the following: + * <pre> + * final Entity entity = Entity.builder() + * .setSubject(new RyaURI("urn:GTIN-14/00012345600012")) + * .setExplicitType(new RyaURI("urn:icecream")) + * .setProperty(new RyaURI("urn:icecream"), new Property(new RyaURI("urn:brand"), new RyaType(XMLSchema.STRING, "Awesome Icecream"))) + * .setProperty(new RyaURI("urn:icecream"), new Property(new RyaURI("urn:flavor"), new RyaType(XMLSchema.STRING, "Chocolate"))) + * .build(); + * </pre> + * The two types of Entities that may be created are implicit and explicit. + * An implicit Entity is one who has at least one {@link Property} that matches + * the {@link Type}, but nothing has explicitly indicated it is of that Type. + * Once something has done so, it is an explicitly typed Entity. + */ +@Immutable +@ParametersAreNonnullByDefault +public class Entity { + + private final RyaURI subject; + private final ImmutableList<RyaURI> explicitTypeIds; + + // First key is Type ID. + // Second key is Property Name. + // Value is the Property value for a specific type. + private final ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> properties; + + private final int version; + + /** + * To construct an instances of this class, use {@link Builder}. + */ + private Entity( + final RyaURI subject, + final ImmutableList<RyaURI> explicitTypeIds, + final ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> typeProperties, + int version) { + this.subject = requireNonNull(subject); + this.explicitTypeIds = requireNonNull(explicitTypeIds); + this.properties = requireNonNull(typeProperties); + this.version = version; + } + + /** + * @return Identifies the thing that is being represented as an Entity. + */ + public RyaURI getSubject() { + return subject; + } + + /** + * @return {@link Type}s that have been explicitly applied to the {@link Entity}. + */ + public ImmutableList<RyaURI> getExplicitTypeIds() { + return explicitTypeIds; + } + + /** + * @return All {@link Property}s that have been set for the Entity, grouped by Type ID. + */ + public ImmutableMap<RyaURI, ImmutableMap<RyaURI, Property>> getProperties() { + return properties; + } + + /** + * @return The version of this Entity. This value is used by the {@link EntityStorage} + * to prevent stale updates. + */ + public int getVersion() { + return version; + } + + @Override + public int hashCode() { + return Objects.hash(subject, explicitTypeIds, properties, version); + } + + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(o instanceof Entity) { + final Entity entity = (Entity) o; + return Objects.equals(subject, entity.subject) && + Objects.equals(explicitTypeIds, entity.explicitTypeIds) && + Objects.equals(properties, entity.properties) && + version == entity.version; + } + return false; + } + + /** + * Builds an {@link TypedEntity} using this object's values for the specified {@link Type}. + * + * @param typeId - The ID of the Type the TypedEntity will be for. (not null) + * @return A TypedEntity using this object's values if any properties for the Type + * are present or if the Type was explicitly set. Otherwise an empty {@link Optional}. + */ + public Optional<TypedEntity> makeTypedEntity(RyaURI typeId) { + requireNonNull(typeId); + + final boolean explicitlyHasType = explicitTypeIds.contains(typeId); + final boolean hasTypesProperties = properties.containsKey(typeId); + + // The case where the MongoEntity can be represented as the typeId's Type. + if(explicitlyHasType || hasTypesProperties) { + // Set required fields. + final TypedEntity.Builder builder = TypedEntity.builder() + .setId( subject ) + .setTypeId( typeId ) + .setExplicitelyTyped( explicitTypeIds.contains(typeId) ); + + // Set Type's properties if present. + if(properties.containsKey(typeId)) { + properties.get(typeId).forEach( (propertyName, property) -> builder.setProperty(property)); + } + + return Optional.of( builder.build() ); + } + + // This MongoEntity can not be represented by the typeId's Type. + return Optional.empty(); + } + + /** + * @return An empty instance of {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Create a {@link Builder} initialized with an {@link Entity}'s values. + * + * @param entity - The Entity the builder will be based on. (not null) + * @return A {@link Builder} loaded with {@code entity}'s values. + */ + public static Builder builder(Entity entity) { + return new Builder(entity); + } + + /** + * Builds instances of {@link Entity}. + */ + @ParametersAreNonnullByDefault + public static class Builder { + + private RyaURI subject = null; + private final List<RyaURI> explicitTypes = new ArrayList<>(); + private final Map<RyaURI, Map<RyaURI, Property>> properties = new HashMap<>(); + + private int version = 0; + + /** + * Constructs an empty instance of {@link Builder}. + */ + public Builder() { } + + /** + * Constructs an instance of {@link Builder}. + * + * @param entity - The Entity the builder will be based on. (not null) + */ + public Builder(Entity entity) { + requireNonNull(entity); + + subject = entity.getSubject(); + explicitTypes.addAll( entity.getExplicitTypeIds() ); + + for(final Entry<RyaURI, ImmutableMap<RyaURI, Property>> entry : entity.getProperties().entrySet()) { + this.properties.put(entry.getKey(), Maps.newHashMap(entry.getValue())); + } + + version = entity.getVersion(); + } + + /** + * @param subject - Identifies the {@link TypedEntity}. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setSubject(@Nullable final RyaURI subject) { + this.subject = subject; + return this; + } + + /** + * @param typeId - A {@link Type} that has been explicity set for the {@link TypedEntity}. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setExplicitType(@Nullable final RyaURI typeId) { + if(typeId != null) { + explicitTypes.add(typeId); + } + return this; + } + + /** + * Removed a Type ID from the set of explicit Type IDs. + * + * @param typeId - The Type ID to remove from the set of explicit types. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder unsetExplicitType(@Nullable final RyaURI typeId) { + if(typeId != null) { + explicitTypes.remove(typeId); + } + return this; + } + + /** + * Adds a {@link Property} for a specific {@link Type} of {@link TypedEntity}. + * + * @param typeId - The Type the Property is for. + * @param property - The Property values to add. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setProperty(@Nullable final RyaURI typeId, @Nullable final Property property) { + if(typeId != null && property != null) { + if(!properties.containsKey(typeId)) { + properties.put(typeId, new HashMap<>()); + } + + properties.get(typeId).put(property.getName(), property); + } + return this; + } + + /** + * Removes a {@link Property} for a specific {@link Type} of {@link TypedEntity}. + * + * @param typeId - The Type the Property will be removed from. + * @param propertyName - The name of the Property to remove. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder unsetProperty(@Nullable final RyaURI typeId, @Nullable final RyaURI propertyName) { + if(typeId != null && propertyName != null) { + if(properties.containsKey(typeId)) { + final Map<RyaURI, Property> typedProperties = properties.get(typeId); + if(typedProperties.containsKey(propertyName)) { + typedProperties.remove(propertyName); + } + } + } + return this; + } + + /** + * @param version - The version of this Entity. This value is used by the + * {@link EntityStorage} to prevent stale updates. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setVersion(int version) { + this.version = version; + return this; + } + + /** + * @return Builds an instance of {@link Entity} using this builder's values. + */ + public Entity build() { + final ImmutableMap.Builder<RyaURI, ImmutableMap<RyaURI, Property>> propertiesBuilder = ImmutableMap.builder(); + for(final Entry<RyaURI, Map<RyaURI, Property>> entry : properties.entrySet()) { + propertiesBuilder.put(entry.getKey(), ImmutableMap.copyOf( entry.getValue() )); + } + + return new Entity(subject, + ImmutableList.copyOf( explicitTypes ), + propertiesBuilder.build(), + version); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Property.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Property.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Property.java new file mode 100644 index 0000000..67c2fdb --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Property.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.model; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.Immutable; + +import mvm.rya.api.domain.RyaType; +import mvm.rya.api.domain.RyaURI; + +/** + * A value that has been set for an {@link TypedEntity}. + */ +@Immutable +@ParametersAreNonnullByDefault +public class Property { + + private final RyaURI name; + private final RyaType value; + + /** + * Constructs an instance of {@link Property}. + * + * @param name - Uniquely identifies the {@link Property}. (not null) + * @param value - The value of the {@link Property}. (not null) + */ + public Property(final RyaURI name, final RyaType value) { + this.name = requireNonNull(name); + this.value = requireNonNull(value); + } + + /** + * @return Uniquely identifies the {@link Property}. + */ + public RyaURI getName() { + return name; + } + + /** + * @return The value of the {@link Property}. + */ + public RyaType getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + @Override + public boolean equals(final Object o) { + if(this == o) { + return true; + } + if(o instanceof Property) { + final Property field = (Property) o; + return Objects.equals(name, field.name) && + Objects.equals(value, field.value); + } + return false; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Type.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Type.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Type.java new file mode 100644 index 0000000..26da572 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/Type.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.model; + +import static java.util.Objects.requireNonNull; + +import java.util.Objects; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.Immutable; + +import com.google.common.collect.ImmutableSet; + +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.storage.TypeStorage; + +/** + * Defines the structure of an {@link TypedEntity}. + * </p> + * For example, suppose you want a {@link Type} that defines what properties are + * available for icecream. It could be modeled like this: + * <pre> + * Type ID: <urn:icecream> + * Properties: <urn:brand> + * <urn:flavor> + * <urn:ingredients> + * <urn:nutritionalInformation> + * </pre> + */ +@Immutable +@ParametersAreNonnullByDefault +public class Type { + + /** + * Uniquely identifies the Type within a {@link TypeStorage}. + */ + private final RyaURI id; + + /** + * The names of {@link Property}s that may be part of an {@link TypedEntity} of this type. + */ + private final ImmutableSet<RyaURI> propertyNames; + + /** + * Constructs an instance of {@link Type}. + * + * @param id - Uniquely identifies the Type within a {@link TypeStorage}. (not null) + * @param propertyNames - The names of {@link Property}s that may be part of an {@link TypedEntity} of this type. (not null) + */ + public Type(final RyaURI id, final ImmutableSet<RyaURI> propertyNames) { + this.id = requireNonNull(id); + this.propertyNames = requireNonNull(propertyNames); + } + + /** + * @return Uniquely identifies the Type within a {@link TypeStorage}. + */ + public RyaURI getId() { + return id; + } + + /** + * @return The names of {@link Property}s that may be part of an {@link TypedEntity} of this type. + */ + public ImmutableSet<RyaURI> getPropertyNames() { + return propertyNames; + } + + @Override + public int hashCode() { + return Objects.hash(id, propertyNames); + } + + @Override + public boolean equals(final Object o) { + if(this == o) { + return true; + } + if(o instanceof Type) { + final Type type = (Type) o; + return Objects.equals(id, type.id) && + Objects.equals(propertyNames, type.propertyNames); + } + return false; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/TypedEntity.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/TypedEntity.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/TypedEntity.java new file mode 100644 index 0000000..1d2bc86 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/model/TypedEntity.java @@ -0,0 +1,242 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.model; + +import static java.util.Objects.requireNonNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.Immutable; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableMap; + +import mvm.rya.api.domain.RyaType; +import mvm.rya.api.domain.RyaURI; + +/** + * A {@link TypedEntity} is a view of an {@link Entity} that has had a specific + * {@link Type} applied to it. + */ +@Immutable +@ParametersAreNonnullByDefault +public class TypedEntity { + + /** + * The Subject of the {@link Entity} this view was derived from. + */ + private final RyaURI subject; + + /** + * The ID of the {@link Type} that defines the structure of this TypedEntity. + */ + private final RyaURI typeId; + + /** + * {@code true} if the Entity's Type has been explicitly set to the {@link #typeId} + * value; {@code false} if it is implicit (this Entity only exists because it has + * properties that happen to match that type's properties). + */ + private final boolean explicitlyTyped; + + /** + * The optional {@link Property} values of this {@link TypedEntity}. + * </p> + * They are mapped from property name to property object for quick lookup. + */ + private final ImmutableMap<RyaURI, Property> optionalFields; + + /** + * Constructs an instance of {@link TypedEntity}. + * + * @param subject - The Subject of the {@link Entity} this view was derived from. (not null) + * @param dataTypeId - The ID of the {@link Type} that defines the structure of this Entity. (not null) + * @param explicitlyTyped - {@code true} if the Entity's Type has been explicitly set to the + * {@link #typeId} value; {@code false} if it is implicit (this Entity only exists because + * it has properties that happen to match that type's properties). + * @param properties - The optional {@link Property} values of this {@link TypedEntity}. (not null) + */ + private TypedEntity(final RyaURI subject, + final RyaURI dataTypeId, + boolean explicitlyTyped, + final ImmutableMap<RyaURI, Property> optionalFields) { + this.subject = requireNonNull(subject); + this.typeId = requireNonNull(dataTypeId); + this.optionalFields = requireNonNull(optionalFields); + this.explicitlyTyped = explicitlyTyped; + } + + /** + * @return The Subject of the {@link Entity} this view was derived from. + */ + public RyaURI getSubject() { + return subject; + } + + /** + * @return The ID of the {@link Type} that defines the structure of this Entity. + */ + public RyaURI getTypeId() { + return typeId; + } + + /** + * @return {@code true} if the Entity's Type has been explicitly set to the {@link #typeId} + * value; {@code false} if it is implicit (this Entity only exists because it has + * properties that happen to match that type's properties). + */ + public boolean isExplicitlyTyped() { + return explicitlyTyped; + } + + /** + * @return The optional {@link Property} values of this {@link TypedEntity}. + */ + public ImmutableCollection<Property> getProperties() { + return optionalFields.values(); + } + + /** + * Get the value of a specific {@link Property} of this {@link TypedEntity} + * if the property has been set. + * + * @param propertyName - The name of {@link Property} that may be in this Entity. (not null) + * @return The value of the Property if it has been set. + */ + public Optional<RyaType> getPropertyValue(final RyaURI propertyName) { + requireNonNull(propertyName); + + final Property field = optionalFields.get(propertyName); + return field == null ? + Optional.absent() : + Optional.of( field.getValue() ); + } + + @Override + public int hashCode() { + return Objects.hash(subject, typeId, optionalFields); + } + + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(o instanceof TypedEntity) { + final TypedEntity other = (TypedEntity) o; + return Objects.equals(subject, other.subject) && + Objects.equals(typeId, other.typeId) && + Objects.equals(optionalFields, other.optionalFields); + } + return false; + } + + /** + * @return An empty instance of {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Makes a {@link Builder} that is populated with an existing {@link TypedEntity}. + * + * @param entity - The initial values of the builder. (not null) + * @return An instance of {@link Builder} loaded with {@code entity}'s values. + */ + public static Builder builder(TypedEntity entity) { + requireNonNull(entity); + + final Builder builder = builder() + .setId(entity.getSubject()) + .setTypeId(entity.getTypeId()); + + entity.getProperties().forEach(builder::setProperty); + + return builder; + } + + /** + * Builds instances of {@link TypedEntity}. + */ + @ParametersAreNonnullByDefault + public static class Builder { + + private RyaURI subject; + private RyaURI typeId; + private boolean explicitlyTyped = false; + private final Map<RyaURI, Property> properties = new HashMap<>(); + + /** + * @param subject - The Subject of the {@link Entity} this view was derived from. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setId(@Nullable final RyaURI subject) { + this.subject = subject; + return this; + } + + /** + * @param typeId - The ID of the {@link Type} that defines the structure of this Entity. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setTypeId(@Nullable final RyaURI typeId) { + this.typeId = typeId; + return this; + } + + /** + * @param explicitlyTyped - {@code true} if the Entity's Type has been explicitly + * set to the {@link #typeId} value; {@code false} if it is implicit (this Entity + * only exists because it has properties that happen to match that type's properties). + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setExplicitelyTyped(boolean explicitlyTyped) { + this.explicitlyTyped = explicitlyTyped; + return this; + } + + /** + * @param property - A {@link Property} of the {@link TypedEntity}. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setProperty(@Nullable final Property property) { + if(property != null) { + properties.put(property.getName(), property); + } + return this; + } + + /** + * @return An instance of {@link TypedEntity} built with this builder's values. + */ + public TypedEntity build() { + return new TypedEntity( + subject, + typeId, + explicitlyTyped, + ImmutableMap.copyOf(properties)); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/query/EntityQueryNode.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/query/EntityQueryNode.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/query/EntityQueryNode.java new file mode 100644 index 0000000..76e6146 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/query/EntityQueryNode.java @@ -0,0 +1,238 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.query; + +import static java.util.Objects.requireNonNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Optional; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.query.BindingSet; +import org.openrdf.query.QueryEvaluationException; +import org.openrdf.query.algebra.StatementPattern; +import org.openrdf.query.algebra.Var; +import org.openrdf.query.algebra.evaluation.impl.ExternalSet; + +import com.google.common.collect.ImmutableMap; + +import info.aduna.iteration.CloseableIteration; +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.model.Type; +import mvm.rya.indexing.entity.storage.EntityStorage; +import mvm.rya.rdftriplestore.evaluation.ExternalBatchingIterator; + +/** + * TODO impl, test, doc + */ +@ParametersAreNonnullByDefault +public class EntityQueryNode extends ExternalSet implements ExternalBatchingIterator { + + /** + * The RyaURI that when used as the Predicate of a Statement Pattern indicates the Type of the Entities. + */ + private static final RyaURI TYPE_ID_URI = new RyaURI(RDF.TYPE.toString()); + + // Provided at construction time. + private final Type type; + private final Collection<StatementPattern> patterns; + private final EntityStorage entities; + + // Information about the subject of the patterns. + private final boolean subjectIsConstant; + private final Optional<String> subjectConstant; + private final Optional<String> subjectVar; + + // Information about the objects of the patterns. + + // XXX what does this map? property name -> binding variable? + // for any property of the entity that has a variable, have to fill it in? + private final ImmutableMap<RyaURI, String> objectVariables; + + + /** + * Constructs an instance of {@link EntityQueryNode}. + * + * @param type - The type of {@link Entity} this node matches. (not null) + * @param patterns - The query StatementPatterns that are solved using an + * Entity of the Type. (not null) + * @param entities - The {@link EntityStorage} that will be searched to match + * {@link BindingSet}s when evaluating a query. (not null) + */ + public EntityQueryNode(final Type type, final Collection<StatementPattern> patterns, final EntityStorage entities) throws IllegalStateException { + this.type = requireNonNull(type); + this.patterns = requireNonNull(patterns); + this.entities = requireNonNull(entities); + + // Subject based preconditions. + verifySameSubjects(patterns); + + // Predicate based preconditions. + verifyAllPredicatesAreConstants(patterns); + verifyHasCorrectTypePattern(type, patterns); + verifyAllPredicatesPartOfType(type, patterns); + + // The Subject may either be constant or a variable. + final Var subject = patterns.iterator().next().getSubjectVar(); + subjectIsConstant = subject.isConstant(); + if(subjectIsConstant) { + subjectConstant = Optional.of( subject.getValue().toString() ); + subjectVar = Optional.empty(); + } else { + subjectConstant = Optional.empty(); + subjectVar = Optional.of( subject.getName() ); + } + + // TODO Also, map each variable that is in an Object spot each variable can be mapped to a property name as well + // Processing note: + // Any constant that appears in the Object portion of the SP will be used to make sure they match. + + objectVariables = null; + } + + /** + * Verify the Subject for all of the patterns is the same. + * + * @param patterns - The patterns to check. + * @throws IllegalStateException If all of the Subjects are not the same. + */ + private static void verifySameSubjects(Collection<StatementPattern> patterns) throws IllegalStateException { + requireNonNull(patterns); + + final Iterator<StatementPattern> it = patterns.iterator(); + final Var subject = it.next().getSubjectVar(); + + while(it.hasNext()) { + final StatementPattern pattern = it.next(); + if(!pattern.getSubjectVar().equals(subject)) { + throw new IllegalStateException("At least one of the patterns has a different subject from the others. " + + "All subjects must be the same."); + } + } + } + + /** + * Verifies all of the Statement Patterns have Constants for their predicates. + * + * @param patterns - The patterns to check. (not null) + * @throws IllegalStateException A pattern has a variable predicate. + */ + private static void verifyAllPredicatesAreConstants(Collection<StatementPattern> patterns) throws IllegalStateException { + requireNonNull(patterns); + + for(final StatementPattern pattern : patterns) { + if(!pattern.getPredicateVar().isConstant()) { + throw new IllegalStateException("The Predicate of a Statement Pattern must be constant. Pattern: " + pattern); + } + } + } + + /** + * Verifies a single Statement Pattern defines the Type of Entity this query node matches. + * + * @param type - The expected Type. (not null) + * @param patterns - The patterns to check. (not null) + * @throws IllegalStateException No Type or the wrong Type is specified by the patterns. + */ + private static void verifyHasCorrectTypePattern(Type type, Collection<StatementPattern> patterns) throws IllegalStateException { + requireNonNull(type); + requireNonNull(patterns); + + boolean typeFound = false; + + for(final StatementPattern pattern : patterns) { + final RyaURI predicate = new RyaURI(pattern.getPredicateVar().getValue().toString()); + + if(predicate.equals(TYPE_ID_URI)) { + final RyaURI typeId = new RyaURI( pattern.getObjectVar().getValue().stringValue() ); + if(typeId.equals(type.getId())) { + typeFound = true; + } else { + throw new IllegalStateException("Statement Pattern encountred for a Type that does not match the expected Type." + + " Expected Type = '" + type.getId().getData() + "' Found Type = '" + typeId.getData() + "'"); + } + } + } + + if(!typeFound) { + throw new IllegalStateException("The collection of Statement Patterns that this node matches must define which Type they match."); + } + } + + /** + * Verify all of the patterns have predicates that match one of the Type's property names. + * + * @param type - The Type the patterns match. (not null) + * @param patterns - The patterns to check. + * @throws IllegalStateException If any of the non-type defining Statement Patterns + * contain a predicate that does not match one of the Type's property names. + */ + private static void verifyAllPredicatesPartOfType(Type type, Collection<StatementPattern> patterns) throws IllegalStateException { + requireNonNull(type); + requireNonNull(patterns); + + for(final StatementPattern pattern : patterns) { + // Skip TYPE patterns. + final RyaURI predicate = new RyaURI( pattern.getPredicateVar().getValue().toString() ); + if(predicate.equals(TYPE_ID_URI)) { + continue; + } + + if(!type.getPropertyNames().contains(predicate)) { + throw new IllegalStateException("The Predicate of a Statement Pattern must be a property name for the Type. " + + "Type ID: '" + type.getId().getData() + "' Pattern: " + pattern); + } + } + } + + + + + + + @Override + public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(Collection<BindingSet> bindingSets) throws QueryEvaluationException { + // TODO Auto-generated method stub + return null; + } + + @Override + public CloseableIteration<BindingSet, QueryEvaluationException> evaluate(BindingSet bindingSet) throws QueryEvaluationException { + requireNonNull(bindingSet); + + // ... ok, so if the subject needs to be filled in, then we need to see if the subject variable is in the binding set. + // if it is, fetch that value and then fetch the entity for the subject. + + // if it isn't, fetch the entity for the constant? + + + // RETURN AN EMPTY ITERATION IF IT CAN NOT FILL IT IN! + + // for all variables in the OBJECT portion of the SPs, fill 'em in using the entity that is stored in the index. + + // x = alice's SSN + // y = blue <-- how do i know this is for urn:eye property? FROM THE STATEMENT PATTERN. look for the ?y in the SP, and that has the property name in it. + + // TODO Auto-generated method stub + return null; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/CloseableIterator.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/CloseableIterator.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/CloseableIterator.java new file mode 100644 index 0000000..ab7c7d8 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/CloseableIterator.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage; + +import java.io.Closeable; +import java.util.Iterator; + +/** + * An {@link Iterator} that is also {@link Closeable}. + * + * @param <T> - The type of object that will be iterated over. + */ +public interface CloseableIterator<T> extends Iterator<T>, Closeable { } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/EntityStorage.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/EntityStorage.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/EntityStorage.java new file mode 100644 index 0000000..545beaa --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/EntityStorage.java @@ -0,0 +1,153 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage; + +import java.util.Optional; +import java.util.Set; + +import javax.annotation.ParametersAreNonnullByDefault; + +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.EntityIndexException; +import mvm.rya.indexing.entity.model.Entity; +import mvm.rya.indexing.entity.model.Property; +import mvm.rya.indexing.entity.model.Type; +import mvm.rya.indexing.entity.model.TypedEntity; + +/** + * Stores and provides access to {@link Entity}s. + */ +@ParametersAreNonnullByDefault +public interface EntityStorage { + + /** + * Creates a new {@link Entity} within the storage. The new Entity's subject must be unique. + * + * @param entity - The {@link Entity} to create. (not null) + * @throws EntityAlreadyExistsException An {@link Entity} could not be created because one already exists for the Subject. + * @throws EntityStorageException A problem occurred while creating the Entity. + */ + public void create(Entity entity) throws EntityAlreadyExistsException, EntityStorageException; + + /** + * Get an {@link Entity} from the storage by its subject. + * + * @param subject - Identifies which {@link Entity} to get. (not null) + * @return The {@link Entity} if one exists for the subject. + * @throws EntityStorageException A problem occurred while fetching the Entity from the storage. + */ + public Optional<Entity> get(RyaURI subject) throws EntityStorageException; + + /** + * Update the state of an {@link Entity}. + * + * @param old - The Entity the changes were applied to. (not null) + * @param updated - The updated Entity to store. (not null) + * @throws StaleUpdateException The {@code old} Entity does not match any Entities that are stored. + * @throws EntityStorageException A problem occurred while updating the Entity within the storage. + */ + public void update(Entity old, Entity updated) throws StaleUpdateException, EntityStorageException; + + /** + * Search the stored {@link Entity}s that have a specific {@link Type} as + * well as the provided {@link Property} values. + * + * @param type - The {@link Type} of the Entities. (not null) + * @param properties - The {@link Property} values that must be set on the Entity. (not null) + * @return A {@link CloseableIterator} over the {@link TypedEntity}s that match the search parameters. + * @throws EntityStorageException A problem occurred while searching the storage. + */ + public CloseableIterator<TypedEntity> search(Type type, Set<Property> properties) throws EntityStorageException; + + /** + * Deletes an {@link Entity} from the storage. + * + * @param subject -Identifies which {@link Entity} to delete. (not null) + * @return {@code true} if something was deleted; otherwise {@code false}. + * @throws EntityStorageException A problem occurred while deleting from the storage. + */ + public boolean delete(RyaURI subject) throws EntityStorageException; + + /** + * Indicates a problem while interacting with an {@link EntityStorage}. + */ + public static class EntityStorageException extends EntityIndexException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with the specified detail message. The + * cause is not initialized, and may subsequently be initialized by + * a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public EntityStorageException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause. <p>Note that the detail message associated with + * {@code cause} is <i>not</i> automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A <tt>null</tt> value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public EntityStorageException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * An {@link Entity} could not be created because one already exists for the Subject. + */ + public static class EntityAlreadyExistsException extends EntityStorageException { + private static final long serialVersionUID = 1L; + + public EntityAlreadyExistsException(String message) { + super(message); + } + + public EntityAlreadyExistsException(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * An {@link TypedEntity} could not be updated because the old state does not + * match the current state. + */ + public static class StaleUpdateException extends EntityStorageException { + private static final long serialVersionUID = 1L; + + public StaleUpdateException(String message) { + super(message); + } + + public StaleUpdateException(String message, Throwable cause) { + super(message, cause); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/TypeStorage.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/TypeStorage.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/TypeStorage.java new file mode 100644 index 0000000..8d99001 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/TypeStorage.java @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage; + +import javax.annotation.ParametersAreNonnullByDefault; + +import com.google.common.base.Optional; + +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.EntityIndexException; +import mvm.rya.indexing.entity.model.Type; + +/** + * Stores and provides access to {@link Type}s. + */ +@ParametersAreNonnullByDefault +public interface TypeStorage { + + /** + * Creates a new {@link Type} within the storage. The new Type's ID must be unique. + * + * @param type - The {@link Type} to create. (not null) + * @throws TypeStorageException A problem occurred while creating the Type. + */ + public void create(Type type) throws TypeStorageException; + + /** + * Get a {@link Type} from the storage by its ID. + * + * @param typeId - The {@link Type}'s ID. (not null) + * @return The {@link Type} if one exists for the ID. + * @throws TypeStorageException A problem occurred while fetching from the storage. + */ + public Optional<Type> get(RyaURI typeId) throws TypeStorageException; + + /** + * Get all {@link Type}s that include a specific {@link Property} name. + * + * @param propertyName - The name to search for. (not null) + * @return All {@link Type}s that include {@code propertyName}. + * @throws TypeStorageException A problem occurred while searching for the Types + * that have the Property name. + */ + public CloseableIterator<Type> search(RyaURI propertyName) throws TypeStorageException; + + /** + * Deletes a {@link Type} from the storage. + * + * @param typeId - The ID of the {@link Type} to delete. (not null) + * @return {@code true} if something was deleted; otherwise {@code false}. + * @throws TypeStorageException A problem occurred while deleting from the storage. + */ + public boolean delete(RyaURI typeId) throws TypeStorageException; + + /** + * A problem occurred while interacting with a {@link TypeStorage}. + */ + public static class TypeStorageException extends EntityIndexException { + private static final long serialVersionUID = 1L; + + /** + * Constructs a new exception with the specified detail message. The + * cause is not initialized, and may subsequently be initialized by + * a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public TypeStorageException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause. <p>Note that the detail message associated with + * {@code cause} is <i>not</i> automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A <tt>null</tt> value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public TypeStorageException(String message, Throwable cause) { + super(message, cause); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/ConvertingCursor.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/ConvertingCursor.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/ConvertingCursor.java new file mode 100644 index 0000000..0015d34 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/ConvertingCursor.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage.mongo; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.util.function.Function; + +import org.bson.Document; + +import com.mongodb.client.MongoCursor; + +import mvm.rya.indexing.entity.storage.CloseableIterator; + +/** + * Converts the {@link Document}s that are returned by a {@link MongoCursor} + * using a {@link DocumentConverter} every time {@link #next()} is invoked. + * + * @param <T> - The type of object the Documents are converted into. + */ +public class ConvertingCursor<T> implements CloseableIterator<T> { + + private final Converter<T> converter; + private final MongoCursor<Document> cursor; + + /** + * Constructs an instance of {@link ConvertingCursor}. + * + * @param converter - Converts the {@link Document}s returned by {@code cursor}. (not null) + * @param cursor - Retrieves the {@link Document}s from a Mongo DB instance. (not null) + */ + public ConvertingCursor(Converter<T> converter, MongoCursor<Document> cursor) { + this.converter = requireNonNull(converter); + this.cursor = requireNonNull(cursor); + } + + @Override + public boolean hasNext() { + return cursor.hasNext(); + } + + @Override + public T next() { + return converter.apply( cursor.next() ); + } + + @Override + public void close() throws IOException { + cursor.close(); + } + + /** + * Converts a {@link Document} into some other object. + * + * @param <R> The type of object the Document is converted into. + */ + @FunctionalInterface + public static interface Converter<R> extends Function<Document, R> { } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/DocumentConverter.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/DocumentConverter.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/DocumentConverter.java new file mode 100644 index 0000000..05a3720 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/DocumentConverter.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage.mongo; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.bson.Document; + +/** + * Converts an object to/from a {@link Document}. + * + * @param <T> - The type of object that is converted to/from a {@link Document}. + */ +@ParametersAreNonnullByDefault +public interface DocumentConverter<T> { + + /** + * Converts an object into a {@link Document}. + * + * @param object - The object to convert. (not null) + * @return A {@link Document} representing the object. + * @throws DocumentConverterException A problem occurred while converting the object. + */ + public Document toDocument(T object) throws DocumentConverterException; + + /** + * Converts a {@link Document} into the target object. + * + * @param document - The document to convert. (not null) + * @return The target object representation of the document. + * @throws DocumentConverterException A problem occurred while converting the {@link Document}. + */ + public T fromDocument(Document document) throws DocumentConverterException; + + /** + * A problem occurred while converting an object while using a {@link DocumentConverter}. + */ + public static class DocumentConverterException extends Exception { + private static final long serialVersionUID = 1L; + + public DocumentConverterException(String message) { + super(message); + } + + public DocumentConverterException(String message, Throwable cause) { + super(message, cause); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java new file mode 100644 index 0000000..0197fca --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/EntityDocumentConverter.java @@ -0,0 +1,123 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage.mongo; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.bson.Document; + +import mvm.rya.api.domain.RyaType; +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.model.Entity; +import mvm.rya.indexing.entity.model.Property; + +/** + * Converts between {@link Entity} and {@link Document}. + */ +@ParametersAreNonnullByDefault +public class EntityDocumentConverter implements DocumentConverter<Entity> { + + public static final String SUBJECT = "_id"; + public static final String EXPLICIT_TYPE_IDS = "explicitTypeIds"; + public static final String PROPERTIES = "properties"; + public static final String VERSION = "version"; + + private final RyaTypeDocumentConverter ryaTypeConverter = new RyaTypeDocumentConverter(); + + @Override + public Document toDocument(Entity entity) { + requireNonNull(entity); + + final Document doc = new Document(); + doc.append(SUBJECT, entity.getSubject().getData()); + + doc.append(EXPLICIT_TYPE_IDS, entity.getExplicitTypeIds().stream() + .map(explicitTypeId -> explicitTypeId.getData()) + .collect(Collectors.toList())); + + final Document propertiesDoc = new Document(); + for(final RyaURI typeId : entity.getProperties().keySet()) { + final Document typePropertiesDoc = new Document(); + entity.getProperties().get(typeId) + .forEach((propertyNameUri, property) -> { + final String propertyName = property.getName().getData(); + final RyaType value = property.getValue(); + typePropertiesDoc.append(propertyName, ryaTypeConverter.toDocument(value)); + }); + propertiesDoc.append(typeId.getData(), typePropertiesDoc); + } + doc.append(PROPERTIES, propertiesDoc); + + doc.append(VERSION, entity.getVersion()); + + return doc; + } + + @Override + public Entity fromDocument(Document document) throws DocumentConverterException { + requireNonNull(document); + + // Preconditions. + if(!document.containsKey(SUBJECT)) { + throw new DocumentConverterException("Could not convert document '" + document + + "' because its '" + SUBJECT + "' field is missing."); + } + + if(!document.containsKey(EXPLICIT_TYPE_IDS)) { + throw new DocumentConverterException("Could not convert document '" + document + + "' because its '" + EXPLICIT_TYPE_IDS + "' field is missing."); + } + + if(!document.containsKey(PROPERTIES)) { + throw new DocumentConverterException("Could not convert document '" + document + + "' because its '" + PROPERTIES + "' field is missing."); + } + + if(!document.containsKey(VERSION)) { + throw new DocumentConverterException("Could not convert document '" + document + + "' because its '" + VERSION + "' field is missing."); + } + + // Perform the conversion. + final Entity.Builder builder = Entity.builder() + .setSubject( new RyaURI(document.getString(SUBJECT)) ); + + ((List<String>)document.get(EXPLICIT_TYPE_IDS)).stream() + .forEach(explicitTypeId -> builder.setExplicitType(new RyaURI(explicitTypeId))); + + final Document propertiesDoc = (Document) document.get(PROPERTIES); + for(final String typeId : propertiesDoc.keySet()) { + final Document typePropertiesDoc = (Document) propertiesDoc.get(typeId); + for(final String propertyName : typePropertiesDoc.keySet()) { + final Document value = (Document) typePropertiesDoc.get(propertyName); + final RyaType propertyValue = ryaTypeConverter.fromDocument( value ); + builder.setProperty(new RyaURI(typeId), new Property(new RyaURI(propertyName), propertyValue)); + } + } + + builder.setVersion( document.getInteger(VERSION) ); + + return builder.build(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoEntityStorage.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoEntityStorage.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoEntityStorage.java new file mode 100644 index 0000000..33ac03b --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoEntityStorage.java @@ -0,0 +1,244 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage.mongo; + +import static java.util.Objects.requireNonNull; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.bson.Document; +import org.bson.conversions.Bson; + +import com.google.common.base.Joiner; +import com.mongodb.ErrorCategory; +import com.mongodb.MongoClient; +import com.mongodb.MongoException; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Filters; + +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.model.Entity; +import mvm.rya.indexing.entity.model.Property; +import mvm.rya.indexing.entity.model.Type; +import mvm.rya.indexing.entity.model.TypedEntity; +import mvm.rya.indexing.entity.storage.CloseableIterator; +import mvm.rya.indexing.entity.storage.EntityStorage; +import mvm.rya.indexing.entity.storage.mongo.ConvertingCursor.Converter; +import mvm.rya.indexing.entity.storage.mongo.DocumentConverter.DocumentConverterException; + +/** + * A Mongo DB implementation of {@link EntityStorage}. + */ +@ParametersAreNonnullByDefault +public class MongoEntityStorage implements EntityStorage { + + private static final String COLLECTION_NAME = "entity-entities"; + + private static final EntityDocumentConverter ENTITY_CONVERTER = new EntityDocumentConverter(); + + /** + * A client connected to the Mongo instance that hosts the Rya instance. + */ + private final MongoClient mongo; + + /** + * The name of the Rya instance the {@link TypedEntity}s are for. + */ + private final String ryaInstanceName; + + /** + * Constructs an instance of {@link MongoEntityStorage}. + * + * @param mongo - A client connected to the Mongo instance that hosts the Rya instance. (not null) + * @param ryaInstanceName - The name of the Rya instance the {@link TypedEntity}s are for. (not null) + */ + public MongoEntityStorage(MongoClient mongo, String ryaInstanceName) { + this.mongo = requireNonNull(mongo); + this.ryaInstanceName = requireNonNull(ryaInstanceName); + } + + @Override + public void create(Entity entity) throws EntityStorageException { + requireNonNull(entity); + + try { + mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .insertOne( ENTITY_CONVERTER.toDocument(entity) ); + + } catch(final MongoException e) { + final ErrorCategory category = ErrorCategory.fromErrorCode( e.getCode() ); + if(category == ErrorCategory.DUPLICATE_KEY) { + throw new EntityAlreadyExistsException("Failed to create Entity with Subject '" + entity.getSubject().getData() + "'.", e); + } + throw new EntityStorageException("Failed to create Entity with Subject '" + entity.getSubject().getData() + "'.", e); + } + } + + @Override + public void update(Entity old, Entity updated) throws StaleUpdateException, EntityStorageException { + requireNonNull(old); + requireNonNull(updated); + + // The updated entity must have the same Subject as the one it is replacing. + if(!old.getSubject().equals(updated.getSubject())) { + throw new EntityStorageException("The old Entity and the updated Entity must have the same Subject. " + + "Old Subject: " + old.getSubject().getData() + ", Updated Subject: " + updated.getSubject().getData()); + } + + // Make sure the updated Entity has a higher verison. + if(old.getVersion() >= updated.getVersion()) { + throw new EntityStorageException("The old Entity's version must be less than the updated Entity's version." + + " Old version: " + old.getVersion() + " Updated version: " + updated.getVersion()); + } + + final Set<Bson> filters = new HashSet<>(); + + // Must match the old entity's Subject. + filters.add( makeSubjectFilter(old.getSubject()) ); + + // Must match the old entity's Version. + filters.add( makeVersionFilter(old.getVersion()) ); + + // Do a find and replace. + final Bson oldEntityFilter = Filters.and(filters); + final Document updatedDoc = ENTITY_CONVERTER.toDocument(updated); + + final MongoCollection<Document> collection = mongo.getDatabase(ryaInstanceName).getCollection(COLLECTION_NAME); + if(collection.findOneAndReplace(oldEntityFilter, updatedDoc) == null) { + throw new StaleUpdateException("Could not update the Entity with Subject '" + updated.getSubject().getData() + "."); + } + } + + @Override + public Optional<Entity> get(RyaURI subject) throws EntityStorageException { + requireNonNull(subject); + + try { + final Document document = mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .find( Filters.eq(EntityDocumentConverter.SUBJECT, subject.getData()) ) + .first(); + + return document == null ? + Optional.empty() : + Optional.of( ENTITY_CONVERTER.fromDocument(document) ); + + } catch(final MongoException | DocumentConverterException e) { + throw new EntityStorageException("Could not get the Entity with Subject '" + subject.getData() + "'.", e); + } + } + + @Override + public CloseableIterator<TypedEntity> search(final Type type, final Set<Property> properties) throws EntityStorageException { + requireNonNull(type); + requireNonNull(properties); + + try { + // Match the specified Property values. + final Set<Bson> filters = properties.stream() + .flatMap(property -> makePropertyFilters(type.getId(), property)) + .collect(Collectors.toSet()); + + // Only match explicitly Typed entities. + filters.add( makeExplicitTypeFilter(type.getId()) ); + + // Get a cursor over the Mongo Document that represent the search results. + final MongoCursor<Document> cursor = mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .find(Filters.and(filters)) + .iterator(); + + // Define that Converter that converts from Document into TypedEntity. + final Converter<TypedEntity> converter = document -> { + try { + final Entity entity = ENTITY_CONVERTER.fromDocument(document); + final Optional<TypedEntity> typedEntity = entity.makeTypedEntity( type.getId() ); + if(!typedEntity.isPresent()) { + throw new RuntimeException("Entity with Subject '" + entity.getSubject() + + "' could not be cast into Type '" + type.getId() + "'."); + } + return typedEntity.get(); + + } catch (final DocumentConverterException e) { + throw new RuntimeException("Document '" + document + "' could not be parsed into an Entity.", e); + } + }; + + // Return a cursor that performs the conversion. + return new ConvertingCursor<TypedEntity>(converter, cursor); + + } catch(final MongoException e) { + throw new EntityStorageException("Could not search Entity.", e); + } + } + + @Override + public boolean delete(RyaURI subject) throws EntityStorageException { + requireNonNull(subject); + + try { + final Document deleted = mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .findOneAndDelete( makeSubjectFilter(subject) ); + + return deleted != null; + + } catch(final MongoException e) { + throw new EntityStorageException("Could not delete the Entity with Subject '" + subject.getData() + "'.", e); + } + } + + private static Bson makeSubjectFilter(RyaURI subject) { + return Filters.eq(EntityDocumentConverter.SUBJECT, subject.getData()); + } + + private static Bson makeVersionFilter(int version) { + return Filters.eq(EntityDocumentConverter.VERSION, version); + } + + private static Bson makeExplicitTypeFilter(RyaURI typeId) { + return Filters.eq(EntityDocumentConverter.EXPLICIT_TYPE_IDS, typeId.getData()); + } + + private static Stream<Bson> makePropertyFilters(RyaURI typeId, Property property) { + final String propertyName = property.getName().getData(); + + // Must match the property's data type. + final String dataTypePath = Joiner.on(".").join( + new String[]{EntityDocumentConverter.PROPERTIES, typeId.getData(), propertyName, RyaTypeDocumentConverter.DATA_TYPE}); + final String propertyDataType = property.getValue().getDataType().stringValue(); + final Bson dataTypeFilter = Filters.eq(dataTypePath, propertyDataType); + + // Must match the property's value. + final String valuePath = Joiner.on(".").join( + new String[]{EntityDocumentConverter.PROPERTIES, typeId.getData(), propertyName, RyaTypeDocumentConverter.VALUE}); + final String propertyValue = property.getValue().getData(); + final Bson valueFilter = Filters.eq(valuePath, propertyValue); + + return Stream.of(dataTypeFilter, valueFilter); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/c2a09dec/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoTypeStorage.java ---------------------------------------------------------------------- diff --git a/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoTypeStorage.java b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoTypeStorage.java new file mode 100644 index 0000000..dbeff56 --- /dev/null +++ b/extras/indexing/src/main/java/mvm/rya/indexing/entity/storage/mongo/MongoTypeStorage.java @@ -0,0 +1,149 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 mvm.rya.indexing.entity.storage.mongo; + +import static java.util.Objects.requireNonNull; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.bson.Document; +import org.bson.conversions.Bson; + +import com.google.common.base.Optional; +import com.mongodb.MongoClient; +import com.mongodb.MongoException; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Filters; + +import mvm.rya.api.domain.RyaURI; +import mvm.rya.indexing.entity.model.Type; +import mvm.rya.indexing.entity.storage.CloseableIterator; +import mvm.rya.indexing.entity.storage.TypeStorage; +import mvm.rya.indexing.entity.storage.mongo.DocumentConverter.DocumentConverterException; + +/** + * A Mongo DB implementation of {@link TypeStorage}. + */ +@ParametersAreNonnullByDefault +public class MongoTypeStorage implements TypeStorage { + + private static final String COLLECTION_NAME = "entity-types"; + + private static final TypeDocumentConverter TYPE_CONVERTER = new TypeDocumentConverter(); + + /** + * A client connected to the Mongo instance that hosts the Rya instance. + */ + private final MongoClient mongo; + + /** + * The name of the Rya instance the {@link Type}s are for. + */ + private final String ryaInstanceName; + + /** + * Constructs an instance of {@link MongoTypeStorage}. + * + * @param mongo - A client connected to the Mongo instance that hosts the Rya instance. (not null) + * @param ryaInstanceName - The name of the Rya instance the {@link Type}s are for. (not null) + */ + public MongoTypeStorage(MongoClient mongo, String ryaInstanceName) { + this.mongo = requireNonNull(mongo); + this.ryaInstanceName = requireNonNull(ryaInstanceName); + } + + @Override + public void create(Type type) throws TypeStorageException { + requireNonNull(type); + + try { + mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .insertOne(TYPE_CONVERTER.toDocument(type)); + + } catch(final MongoException e) { + throw new TypeStorageException("Failed to create Type with ID '" + type.getId().getData() + "'.", e); + } + } + + @Override + public Optional<Type> get(RyaURI typeId) throws TypeStorageException { + requireNonNull(typeId); + + try { + final Document document = mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .find( makeIdFilter(typeId) ) + .first(); + + return document == null ? + Optional.absent() : + Optional.of( TYPE_CONVERTER.fromDocument(document) ); + + } catch(final MongoException | DocumentConverterException e) { + throw new TypeStorageException("Could not get the Type with ID '" + typeId.getData() + "'.", e); + } + } + + @Override + public CloseableIterator<Type> search(RyaURI propertyName) throws TypeStorageException { + requireNonNull(propertyName); + + try { + // Create a Filter that finds Types who have the provided property names. + final Bson byPropertyName = Filters.eq(TypeDocumentConverter.PROPERTY_NAMES, propertyName.getData()); + + final MongoCursor<Document> cursor = mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .find( byPropertyName ) + .iterator(); + + return new ConvertingCursor<Type>(document -> { + try { + return TYPE_CONVERTER.fromDocument(document); + } catch (final Exception e) { + throw new RuntimeException("Could not convert the Document '" + document + "' into a Type.", e); + } + }, cursor); + + } catch(final MongoException e) { + throw new TypeStorageException("Could not fetch Types that include the property '" + propertyName.getData() + "'.", e); + } + } + + @Override + public boolean delete(RyaURI typeId) throws TypeStorageException { + requireNonNull(typeId); + + try { + final Document deleted = mongo.getDatabase(ryaInstanceName) + .getCollection(COLLECTION_NAME) + .findOneAndDelete( makeIdFilter(typeId) ); + + return deleted != null; + + } catch(final MongoException e) { + throw new TypeStorageException("Could not delete the Type with ID '" + typeId.getData() + "'.", e); + } + } + + private static Bson makeIdFilter(RyaURI typeId) { + return Filters.eq(TypeDocumentConverter.ID, typeId.getData()); + } +} \ No newline at end of file
