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: &lt;urn:icecream>
+ *              Properties: &lt;urn:brand>
+ *                          &lt;urn:flavor>
+ *                          &lt;urn:ingredients>
+ *                          &lt;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: &lt;urn:icecream>
+ *              Properties: &lt;urn:brand>
+ *                          &lt;urn:flavor>
+ *                          &lt;urn:ingredients>
+ *                          &lt;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

Reply via email to