All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.usergrid.persistence.cassandra.RelationManagerImpl Maven / Gradle / Ivy

There is a newer version: 0.0.27.1
Show newest version
/*******************************************************************************
 * Copyright 2012 Apigee Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package org.usergrid.persistence.cassandra;

import static java.lang.String.CASE_INSENSITIVE_ORDER;
import static java.util.Arrays.asList;
import static me.prettyprint.hector.api.factory.HFactory.createIndexedSlicesQuery;
import static me.prettyprint.hector.api.factory.HFactory.createMultigetSliceQuery;
import static me.prettyprint.hector.api.factory.HFactory.createMutator;
import static org.apache.commons.codec.binary.Base64.decodeBase64;
import static org.apache.commons.codec.binary.Base64.encodeBase64URLSafeString;
import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.commons.lang.StringUtils.removeEnd;
import static org.usergrid.persistence.Results.fromIdList;
import static org.usergrid.persistence.Results.fromRefList;
import static org.usergrid.persistence.Results.Level.IDS;
import static org.usergrid.persistence.Results.Level.REFS;
import static org.usergrid.persistence.Schema.COLLECTION_ROLES;
import static org.usergrid.persistence.Schema.DICTIONARY_COLLECTIONS;
import static org.usergrid.persistence.Schema.DICTIONARY_CONNECTED_ENTITIES;
import static org.usergrid.persistence.Schema.DICTIONARY_CONNECTED_TYPES;
import static org.usergrid.persistence.Schema.DICTIONARY_CONNECTING_ENTITIES;
import static org.usergrid.persistence.Schema.DICTIONARY_CONNECTING_TYPES;
import static org.usergrid.persistence.Schema.INDEX_CONNECTIONS;
import static org.usergrid.persistence.Schema.PROPERTY_ASSOCIATED;
import static org.usergrid.persistence.Schema.PROPERTY_COLLECTION_NAME;
import static org.usergrid.persistence.Schema.PROPERTY_CONNECTION;
import static org.usergrid.persistence.Schema.PROPERTY_CURSOR;
import static org.usergrid.persistence.Schema.PROPERTY_INACTIVITY;
import static org.usergrid.persistence.Schema.PROPERTY_ITEM;
import static org.usergrid.persistence.Schema.PROPERTY_ITEM_TYPE;
import static org.usergrid.persistence.Schema.PROPERTY_NAME;
import static org.usergrid.persistence.Schema.PROPERTY_TITLE;
import static org.usergrid.persistence.Schema.PROPERTY_TYPE;
import static org.usergrid.persistence.Schema.TYPE_APPLICATION;
import static org.usergrid.persistence.Schema.TYPE_CONNECTION;
import static org.usergrid.persistence.Schema.TYPE_ENTITY;
import static org.usergrid.persistence.Schema.TYPE_MEMBER;
import static org.usergrid.persistence.Schema.TYPE_ROLE;
import static org.usergrid.persistence.Schema.defaultCollectionName;
import static org.usergrid.persistence.Schema.getDefaultSchema;
import static org.usergrid.persistence.SimpleEntityRef.ref;
import static org.usergrid.persistence.cassandra.ApplicationCF.ENTITY_COMPOSITE_DICTIONARIES;
import static org.usergrid.persistence.cassandra.ApplicationCF.ENTITY_CONNECTIONS;
import static org.usergrid.persistence.cassandra.ApplicationCF.ENTITY_DICTIONARIES;
import static org.usergrid.persistence.cassandra.ApplicationCF.ENTITY_ID_SETS;
import static org.usergrid.persistence.cassandra.ApplicationCF.ENTITY_INDEX;
import static org.usergrid.persistence.cassandra.ApplicationCF.ENTITY_INDEX_ENTRIES;
import static org.usergrid.persistence.cassandra.CassandraPersistenceUtils.addDeleteToMutator;
import static org.usergrid.persistence.cassandra.CassandraPersistenceUtils.addInsertToMutator;
import static org.usergrid.persistence.cassandra.CassandraPersistenceUtils.batchExecute;
import static org.usergrid.persistence.cassandra.CassandraPersistenceUtils.key;
import static org.usergrid.persistence.cassandra.CassandraService.INDEX_ENTRY_LIST_COUNT;
import static org.usergrid.persistence.cassandra.ConnectionRefImpl.CONNECTION_ENTITY_CONNECTION_TYPE;
import static org.usergrid.persistence.cassandra.GeoIndexManager.batchDeleteLocationInConnectionsIndex;
import static org.usergrid.persistence.cassandra.GeoIndexManager.batchRemoveLocationFromCollectionIndex;
import static org.usergrid.persistence.cassandra.GeoIndexManager.batchStoreLocationInCollectionIndex;
import static org.usergrid.persistence.cassandra.GeoIndexManager.batchStoreLocationInConnectionsIndex;
import static org.usergrid.persistence.cassandra.IndexUpdate.indexValueCode;
import static org.usergrid.persistence.cassandra.IndexUpdate.toIndexableValue;
import static org.usergrid.persistence.cassandra.IndexUpdate.validIndexableValue;
import static org.usergrid.utils.ClassUtils.cast;
import static org.usergrid.utils.CompositeUtils.setEqualityFlag;
import static org.usergrid.utils.CompositeUtils.setGreaterThanEqualityFlag;
import static org.usergrid.utils.ConversionUtils.bytebuffer;
import static org.usergrid.utils.ConversionUtils.bytes;
import static org.usergrid.utils.ConversionUtils.string;
import static org.usergrid.utils.ConversionUtils.uuid;
import static org.usergrid.utils.InflectionUtils.singularize;
import static org.usergrid.utils.MapUtils.addMapSet;
import static org.usergrid.utils.UUIDUtils.getTimestampInMicros;
import static org.usergrid.utils.UUIDUtils.newTimeUUID;

import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;

import me.prettyprint.cassandra.model.IndexedSlicesQuery;
import me.prettyprint.cassandra.serializers.ByteBufferSerializer;
import me.prettyprint.cassandra.serializers.LongSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.serializers.UUIDSerializer;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.AbstractComposite.ComponentEquality;
import me.prettyprint.hector.api.beans.DynamicComposite;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.OrderedRows;
import me.prettyprint.hector.api.beans.Row;
import me.prettyprint.hector.api.beans.Rows;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.MultigetSliceQuery;
import me.prettyprint.hector.api.query.QueryResult;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.usergrid.persistence.*;
import org.usergrid.persistence.IndexBucketLocator.IndexType;
import org.usergrid.persistence.Results.Level;
import org.usergrid.persistence.cassandra.GeoIndexManager.EntityLocationRef;
import org.usergrid.persistence.cassandra.IndexUpdate.IndexEntry;
import org.usergrid.persistence.entities.Group;
import org.usergrid.persistence.query.ir.AllNode;
import org.usergrid.persistence.query.ir.QuerySlice;
import org.usergrid.persistence.query.ir.SearchVisitor;
import org.usergrid.persistence.query.ir.SliceNode;
import org.usergrid.persistence.query.ir.WithinNode;
import org.usergrid.persistence.schema.CollectionInfo;
import org.usergrid.utils.IndexUtils;
import org.usergrid.utils.MapUtils;
import org.usergrid.utils.StringUtils;

import com.beoui.geocell.model.Point;
import com.yammer.metrics.annotation.Metered;

public class RelationManagerImpl implements RelationManager {

    private static final Logger logger = LoggerFactory
            .getLogger(RelationManagerImpl.class);

    private EntityManagerImpl em;
    private CassandraService cass;
    private UUID applicationId;
    private EntityRef headEntity;
    private IndexBucketLocator indexBucketLocator;

    public static final StringSerializer se = new StringSerializer();
    public static final ByteBufferSerializer be = new ByteBufferSerializer();
    public static final UUIDSerializer ue = new UUIDSerializer();
    public static final LongSerializer le = new LongSerializer();
    private static final UUID NULL_ID = new UUID(0, 0);

    public RelationManagerImpl() {
    }

    public RelationManagerImpl init(EntityManagerImpl em,
            CassandraService cass, UUID applicationId, EntityRef headEntity,
            IndexBucketLocator indexBucketLocator) {
        this.em = em;
        this.applicationId = applicationId;
        this.cass = cass;
        this.headEntity = headEntity;
        this.indexBucketLocator = indexBucketLocator;

        return this;
    }

    public ApplicationContext getApplicationContext() {
        return em.getApplicationContext();
    }

    RelationManagerImpl getRelationManager(EntityRef headEntity) {
        RelationManagerImpl rmi = new RelationManagerImpl();
        rmi.init(em, cass, applicationId, headEntity, indexBucketLocator);
        return rmi;
        //return applicationContext.getAutowireCapableBeanFactory()
        //        .createBean(RelationManagerImpl.class)
        //        .init(em, cass, applicationId, headEntity, indexBucketLocator);
    }

    Entity getHeadEntity() throws Exception {
        Entity entity = null;
        if (headEntity instanceof Entity) {
            entity = (Entity) headEntity;
        } else {
            entity = em.get(headEntity);
            headEntity = entity;
        }
        return entity;
    }

    /**
     * Gets the connections.
     *
     * @param applicationId
     *            the application id
     * @param connection
     *            the connection
     * @return list of connections
     * @throws Exception
     *             the exception
     */
    public List getConnections(ConnectionRefImpl connection,
            boolean includeConnectionEntities) throws Exception {
        Keyspace ko = cass.getApplicationKeyspace(applicationId);

        IndexedSlicesQuery q = createIndexedSlicesQuery(
                ko, ue, se, be);
        q.setColumnFamily(ENTITY_CONNECTIONS.toString());
        q.setColumnNames(ConnectionRefImpl.getColumnNames());
        connection.addIndexExpressionsToQuery(q);
        QueryResult> r = q.execute();
        OrderedRows rows = r.get();
        List connections = new ArrayList();
        logger.debug("{} indexed connection row(s) retrieved", rows.getCount() );
        for (Row row : rows) {
            UUID entityId = row.getKey();

            logger.debug("Indexed connection {} found", entityId.toString());

            ConnectionRefImpl c = ConnectionRefImpl.loadFromColumns(row
                    .getColumnSlice().getColumns());

            String entityType = c.getConnectedEntityType();
            if (!includeConnectionEntities
                    && TYPE_CONNECTION.equalsIgnoreCase(entityType)) {
                logger.debug("Skipping loopback connection {}", entityId.toString());
                continue;
            }
            connections.add(c);
        }

        logger.debug("Returing {} connection(s)", connections.size());
        return connections;
    }

    public List getConnectionsWithEntity(
            UUID participatingEntityId) throws Exception {
        Keyspace ko = cass.getApplicationKeyspace(applicationId);

        List connections = new ArrayList();
        List idColumns = ConnectionRefImpl.getIdColumnNames();

        for (String idColumn : idColumns) {
            IndexedSlicesQuery q = createIndexedSlicesQuery(
                    ko, ue, se, be);
            q.setColumnFamily(ENTITY_CONNECTIONS.toString());
            q.setColumnNames(ConnectionRefImpl.getColumnNames());
            q.addEqualsExpression(idColumn, bytebuffer(participatingEntityId));
            QueryResult> r = q.execute();
            OrderedRows rows = r.get();
            for (Row row : rows) {
                UUID entityId = row.getKey();

                logger.debug("Indexed Connection {} found",entityId.toString());

                ConnectionRefImpl c = ConnectionRefImpl.loadFromColumns(row
                        .getColumnSlice().getColumns());

                connections.add(c);
            }
        }

        return connections;
    }

    public List getConnections(List connectionIds)
            throws Exception {

        List connections = new ArrayList();

        Map resultSet = new LinkedHashMap();

        Keyspace ko = cass.getApplicationKeyspace(applicationId);
        MultigetSliceQuery q = createMultigetSliceQuery(
                ko, ue, se, be);
        q.setColumnFamily(ENTITY_CONNECTIONS.toString());
        q.setKeys(connectionIds);
        q.setColumnNames(ConnectionRefImpl.getColumnNamesSet().toArray(
                new String[0]));
        QueryResult> r = q.execute();
        Rows rows = r.get();

        for (Row row : rows) {
            ConnectionRefImpl connection = ConnectionRefImpl
                    .loadFromColumns(row.getColumnSlice().getColumns());

            resultSet.put(row.getKey(), connection);
        }

        for (UUID connectionId : connectionIds) {
            ConnectionRefImpl connection = resultSet.get(connectionId);
            connections.add(connection);
        }

        return connections;
    }

    /**
     * Gets the cF key for subkey.
     *
     * @param collection
     *            the collection
     * @param properties
     *            the properties
     * @return row key
     */
    public Object getCFKeyForSubkey(CollectionInfo collection, SliceNode node) {

        // only bother if we have subkeys
        if (!collection.hasSubkeys()) {
            return null;
        }

        Set fields_used = null;
        Object best_key = null;
        int most_keys_matched = 0;

        List combos = collection.getSubkeyCombinations();

        for (String[] combo : combos) {

            int keys_matched = 0;
            List subkey_props = new ArrayList();
            Set subkey_names = new LinkedHashSet();

            for (String subkey_name : combo) {

                QuerySlice slice = node.getSlice(subkey_name);

                // no slice for this property, or not an equals, skip it
                if (slice == null || !slice.isEquals()) {
                    continue;
                }

                Object subkey_value = null;

                if (subkey_name != null) {

                    subkey_value = slice.getStart().getValue();

                    if (subkey_value != null) {
                        keys_matched++;
                        subkey_names.add(subkey_name);
                    }
                }

                subkey_props.add(subkey_value);
            }

            Object subkey_key = key(subkey_props.toArray());

            if (keys_matched > most_keys_matched) {
                best_key = subkey_key;
                fields_used = subkey_names;
            }
        }

        // Remove any fields that we used in constructing the row key, it's
        // already been joined
        // by adding it to the row key
        if (fields_used != null) {
            for (String field : fields_used) {
                node.removeSlice(field, collection);
            }
        }

        return best_key;
    }

    /**
     * Batch update collection index.
     *
     * @param batch
     *            the batch
     * @param applicationId
     *            the application id
     * @param ownerType
     *            the owner type
     * @param ownerId
     *            the owner id
     * @param jointOwnerId
     *            the joint owner id
     * @param collectionName
     *            the collection name
     * @param entityType
     *            the entity type
     * @param entityId
     *            the entity id
     * @param entityProperties
     *            the entity properties
     * @param entryName
     *            the entry name
     * @param entryValue
     *            the entry value
     * @param isSet
     *            the is set
     * @param removeSetEntry
     *            the remove set entry
     * @param timestamp
     *            the timestamp
     * @return batch
     * @throws Exception
     *             the exception
     */
    @Metered(group="core",name="RelationManager_batchUpdateCollectionIndex")
    public IndexUpdate batchUpdateCollectionIndex(IndexUpdate indexUpdate,
            EntityRef owner, String collectionName) throws Exception {

        logger.debug("batchUpdateCollectionIndex");

        Entity indexedEntity = indexUpdate.getEntity();

        String bucketId = indexBucketLocator.getBucket(applicationId,
                IndexType.COLLECTION, indexedEntity.getUuid(),
                indexedEntity.getType(), indexUpdate.getEntryName());

        CollectionInfo collection = getDefaultSchema().getCollection(
                owner.getType(), collectionName);

        // the root name without the bucket
        // entity_id,collection_name,prop_name,
        Object index_name = null;
        // entity_id,collection_name,prop_name, bucketId
        Object index_key = null;

        // entity_id,collection_name,collected_entity_id,prop_name

        for (IndexEntry entry : indexUpdate.getPrevEntries()) {

            if (entry.getValue() != null) {

                index_name = key(owner.getUuid(), collectionName,
                        entry.getPath());

                index_key = key(index_name, bucketId);

                addDeleteToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                        index_key, entry.getIndexComposite(),
                        indexUpdate.getTimestamp());

                if (collection != null) {
                    if (collection.hasSubkeys()) {
                        List combos = collection
                                .getSubkeyCombinations();
                        for (String[] combo : combos) {
                            List subkey_props = new ArrayList();
                            for (String subkey_name : combo) {
                                Object subkey_value = null;
                                if (subkey_name != null) {
                                    subkey_value = indexUpdate.getEntity()
                                            .getProperty(subkey_name);
                                }
                                subkey_props.add(subkey_value);
                            }
                            Object subkey_key = key(subkey_props.toArray());

                            // entity_id,collection_name,prop_name
                            Object index_subkey_key = key(owner.getUuid(),
                                    collectionName, subkey_key, entry.getPath());

                            addDeleteToMutator(indexUpdate.getBatch(),
                                    ENTITY_INDEX, index_subkey_key,
                                    entry.getIndexComposite(),
                                    indexUpdate.getTimestamp());

                        }
                    }
                }

                if ("location.coordinates".equals(entry.getPath())) {
                    EntityLocationRef loc = new EntityLocationRef(
                            indexUpdate.getEntity(), entry.getTimestampUuid(),
                            entry.getValue().toString());
                    batchRemoveLocationFromCollectionIndex(
                            indexUpdate.getBatch(), indexBucketLocator,
                            applicationId, index_name, loc);
                }

            } else {
                logger.error("Unexpected condition - deserialized property value is null");
            }
        }

        if ((indexUpdate.getNewEntries().size() > 0)
                && (!indexUpdate.isMultiValue() || (indexUpdate.isMultiValue() && !indexUpdate
                        .isRemoveListEntry()))) {

            for (IndexEntry indexEntry : indexUpdate.getNewEntries()) {

                // byte valueCode = indexEntry.getValueCode();

                index_name = key(owner.getUuid(), collectionName,
                        indexEntry.getPath());

                index_key = key(index_name, bucketId);

                // int i = 0;

                addInsertToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                        index_key, indexEntry.getIndexComposite(), null,
                        indexUpdate.getTimestamp());

                // Add subkey indexes

                if (collection != null) {
                    if (collection.hasSubkeys()) {
                        List combos = collection
                                .getSubkeyCombinations();
                        for (String[] combo : combos) {
                            List subkey_props = new ArrayList();
                            for (String subkey_name : combo) {
                                Object subkey_value = null;
                                if (subkey_name != null) {
                                    subkey_value = indexUpdate.getEntity()
                                            .getProperty(subkey_name);
                                }
                                subkey_props.add(subkey_value);
                            }
                            Object subkey_key = key(subkey_props.toArray());

                            // entity_id,collection_name,prop_name
                            Object index_subkey_key = key(owner.getUuid(),
                                    collectionName, subkey_key,
                                    indexEntry.getPath(), bucketId);

                            addInsertToMutator(indexUpdate.getBatch(),
                                    ENTITY_INDEX, index_subkey_key,
                                    indexEntry.getIndexComposite(), null,
                                    indexUpdate.getTimestamp());

                        }
                    }
                }

                if ("location.coordinates".equals(indexEntry.getPath())) {
                    EntityLocationRef loc = new EntityLocationRef(
                            indexUpdate.getEntity(),
                            indexEntry.getTimestampUuid(), indexEntry
                                    .getValue().toString());
                    batchStoreLocationInCollectionIndex(indexUpdate.getBatch(),
                            indexBucketLocator, applicationId, index_name,
                            indexedEntity.getUuid(), loc);
                }

                // i++;
            }

        }

        for (String index : indexUpdate.getIndexesSet()) {
            addInsertToMutator(
                    indexUpdate.getBatch(),
                    ENTITY_DICTIONARIES,
                    key(owner.getUuid(), collectionName,
                            Schema.DICTIONARY_INDEXES), index, null,
                    indexUpdate.getTimestamp());
        }

        return indexUpdate;
    }

    @Override
    @Metered(group="core",name="RelationManager_getCollectionIndexes")
    public Set getCollectionIndexes(String collectionName)
            throws Exception {

        // TODO TN, read all buckets here
        List> results = cass.getAllColumns(
                cass.getApplicationKeyspace(applicationId),
                ENTITY_DICTIONARIES,
                key(headEntity.getUuid(), collectionName,
                        Schema.DICTIONARY_INDEXES), se, se);
        Set indexes = new TreeSet();
        if (results != null) {
            for (HColumn column : results) {
                String propertyName = column.getName();
                if (!propertyName.endsWith(".keywords")) {
                    indexes.add(column.getName());
                }
            }
        }
        return indexes;
    }

    public Map> getContainingCollections()
            throws Exception {
        Map> results = new LinkedHashMap>();

        Keyspace ko = cass.getApplicationKeyspace(applicationId);

        // TODO TN get all buckets here

        List> containers = cass
                .getAllColumns(
                        ko,
                        ENTITY_COMPOSITE_DICTIONARIES,
                        key(headEntity.getUuid(),
                                Schema.DICTIONARY_CONTAINER_ENTITIES),
                        EntityManagerFactoryImpl.dce, be);
        if (containers != null) {
            for (HColumn container : containers) {
                DynamicComposite composite = container.getName();
                if (composite != null) {
                    String ownerType = (String) composite.get(0);
                    String collectionName = (String) composite.get(1);
                    UUID ownerId = (UUID) composite.get(2);
                    addMapSet(results, new SimpleEntityRef(ownerType, ownerId),
                            collectionName);
                    if(logger.isDebugEnabled()){
                        logger.debug( " {} ( {} ) is in collection {} ( {} )." , new Object[]{headEntity.getType(), headEntity.getUuid(),ownerType,collectionName, ownerId });
                    }
                }
            }
        }
        EntityRef applicationRef = new SimpleEntityRef(TYPE_APPLICATION,
                applicationId);
        if (!results.containsKey(applicationRef)) {
            addMapSet(results, applicationRef,
                    defaultCollectionName(headEntity.getType()));
        }
        return results;

    }

    @SuppressWarnings("unchecked")
    public void batchCreateCollectionMembership(Mutator batch,
            EntityRef ownerRef, String collectionName, EntityRef itemRef,
            EntityRef membershipRef, UUID timestampUuid) throws Exception {

        long timestamp = getTimestampInMicros(timestampUuid);

        if (membershipRef == null) {
            membershipRef = new SimpleCollectionRef(ownerRef, collectionName,
                    itemRef);
        }

        Map properties = new TreeMap(
                CASE_INSENSITIVE_ORDER);
        properties.put(PROPERTY_TYPE, membershipRef.getType());
        properties.put(PROPERTY_COLLECTION_NAME, collectionName);
        properties.put(PROPERTY_ITEM, itemRef.getUuid());
        properties.put(PROPERTY_ITEM_TYPE, itemRef.getType());

        em.batchCreate(batch, membershipRef.getType(), null, properties,
                membershipRef.getUuid(), timestampUuid);

        addInsertToMutator(
                batch,
                ENTITY_COMPOSITE_DICTIONARIES,
                key(membershipRef.getUuid(),
                        Schema.DICTIONARY_CONTAINER_ENTITIES),
                asList(ownerRef.getType(), collectionName, ownerRef.getUuid()),
                membershipRef.getUuid(), timestamp);

    }

    /**
     * Batch add to collection.
     *
     * @param batch
     *            the batch
     * @param applicationId
     *            the application id
     * @param ownerType
     *            the owner type
     * @param ownerId
     *            the owner id
     * @param jointOwnerId
     *            the joint owner id
     * @param collectionName
     *            the collection name
     * @param entityType
     *            the entity type
     * @param entityId
     *            the entity id
     * @param entityProperties
     *            the entity properties
     * @param timestamp
     *            the timestamp
     * @return batch
     * @throws Exception
     *             the exception
     */
    public Mutator batchAddToCollection(Mutator batch,
            String collectionName, Entity entity, UUID timestampUuid)
            throws Exception {
        List ids = new ArrayList(1);
        ids.add(headEntity.getUuid());
        return batchAddToCollections(batch, headEntity.getType(), ids,
                collectionName, entity, timestampUuid);
    }

    @SuppressWarnings("unchecked")
    @Metered(group="core",name="RelationManager_batchAddToCollections")
    public Mutator batchAddToCollections(Mutator batch,
            String ownerType, List ownerIds, String collectionName,
            Entity entity, UUID timestampUuid) throws Exception {

        long timestamp = getTimestampInMicros(timestampUuid);

        if (Schema.isAssociatedEntityType(entity.getType())) {
            logger.error("Cant add an extended type to any collection",
                    new Throwable());
            return batch;
        }

        Map membershipRefs = new LinkedHashMap();

        for (UUID ownerId : ownerIds) {

            CollectionRef membershipRef = new SimpleCollectionRef(
                    new SimpleEntityRef(ownerType, ownerId), collectionName,
                    entity);

            membershipRefs.put(ownerId, membershipRef);

            // get the bucket this entityId needs to be inserted into
            String bucketId = indexBucketLocator.getBucket(applicationId,
                    IndexType.COLLECTION, entity.getUuid(), collectionName);

            Object collections_key = key(ownerId,
                    Schema.DICTIONARY_COLLECTIONS, collectionName, bucketId);

            // Insert in main collection

            addInsertToMutator(batch, ENTITY_ID_SETS, collections_key,
                    entity.getUuid(), membershipRef.getUuid(), timestamp);

            addInsertToMutator(
                    batch,
                    ENTITY_COMPOSITE_DICTIONARIES,
                    key(entity.getUuid(), Schema.DICTIONARY_CONTAINER_ENTITIES),
                    asList(ownerType, collectionName, ownerId),
                    membershipRef.getUuid(), timestamp);

        }
        // Insert in subkeyed collections
        Schema schema = getDefaultSchema();
        CollectionInfo collection = schema.getCollection(ownerType,
                collectionName);
        if (collection != null) {
            if (collection.hasSubkeys()) {
                List combos = collection.getSubkeyCombinations();
                for (String[] combo : combos) {
                    List subkey_props = new ArrayList();
                    for (String subkey_name : combo) {
                        Object subkey_value = null;
                        if (subkey_name != null) {
                            subkey_value = entity.getProperty(subkey_name);
                        }
                        subkey_props.add(subkey_value);
                    }
                    for (UUID ownerId : ownerIds) {
                        addInsertToMutator(
                                batch,
                                ENTITY_ID_SETS,
                                key(ownerId, Schema.DICTIONARY_COLLECTIONS,
                                        collectionName, subkey_props.toArray()),
                                entity.getUuid(), membershipRefs.get(ownerId)
                                        .getUuid(), timestamp);
                    }

                }
            }
        }

        // Add property indexes
        for (String propertyName : entity.getProperties().keySet()) {
            boolean indexed_property = schema.isPropertyIndexed(entity.getType(), propertyName);
            if (indexed_property) {
                boolean collection_indexes_property = schema
                        .isPropertyIndexedInCollection(ownerType,
                                collectionName, propertyName);
                boolean item_schema_has_property = schema
                        .hasProperty(entity.getType(), propertyName);
                boolean fulltext_indexed = schema
                        .isPropertyFulltextIndexed(entity.getType(), propertyName);
                if (collection_indexes_property || !item_schema_has_property) {
                    Object propertyValue = entity.getProperty(propertyName);
                    IndexUpdate indexUpdate = batchStartIndexUpdate(batch,
                            entity, propertyName, propertyValue, timestampUuid,
                            item_schema_has_property, false, false,
                            fulltext_indexed, true);
                    for (UUID ownerId : ownerIds) {
                        EntityRef owner = new SimpleEntityRef(ownerType,
                                ownerId);
                        batchUpdateCollectionIndex(indexUpdate, owner,
                                collectionName);
                    }
                }
            }
        }

        // Add set property indexes

        Set dictionaryNames = em.getDictionaryNames(entity);

        for (String dictionaryName : dictionaryNames) {
            boolean has_dictionary = schema.hasDictionary(
                    entity.getType(), dictionaryName);
            boolean dictionary_indexed = schema
                    .isDictionaryIndexedInCollection(ownerType, collectionName,
                            dictionaryName);

            if (dictionary_indexed || !has_dictionary) {
                Set elementValues = em.getDictionaryAsSet(entity,
                        dictionaryName);
                for (Object elementValue : elementValues) {
                    IndexUpdate indexUpdate = batchStartIndexUpdate(batch,
                            entity, dictionaryName, elementValue,
                            timestampUuid, has_dictionary, true, false, false,
                            true);
                    for (UUID ownerId : ownerIds) {
                        EntityRef owner = new SimpleEntityRef(ownerType,
                                ownerId);
                        batchUpdateCollectionIndex(indexUpdate, owner,
                                collectionName);
                    }
                }
            }
        }

        for (UUID ownerId : ownerIds) {
            EntityRef owner = new SimpleEntityRef(ownerType, ownerId);
            batchCreateCollectionMembership(batch, owner, collectionName,
                    entity, membershipRefs.get(ownerId), timestampUuid);
        }

        return batch;
    }

    /**
     * Batch remove from collection.
     *
     * @param batch
     *            the batch
     * @param applicationId
     *            the application id
     * @param ownerType
     *            the owner type
     * @param ownerId
     *            the owner id
     * @param collectionName
     *            the collection name
     * @param entityType
     *            the entity type
     * @param entityId
     *            the entity id
     * @param entityProperties
     *            the entity properties
     * @param timestamp
     *            the timestamp
     * @return batch
     * @throws Exception
     *             the exception
     */
    public Mutator batchRemoveFromCollection(
            Mutator batch, String collectionName, Entity entity,
            UUID timestampUuid) throws Exception {
        return this.batchRemoveFromCollection(batch, collectionName, entity,
                false, timestampUuid);
    }

    @SuppressWarnings("unchecked")
    @Metered(group="core",name="RelationManager_batchRemoveFromCollection")
    public Mutator batchRemoveFromCollection(
            Mutator batch, String collectionName, Entity entity,
            boolean force, UUID timestampUuid) throws Exception {

        long timestamp = getTimestampInMicros(timestampUuid);

        if (!force && headEntity.getUuid().equals(applicationId)) {
            // Can't remove entities from root collections
            return batch;
        }

        Object collections_key = key(headEntity.getUuid(),
                Schema.DICTIONARY_COLLECTIONS, collectionName,
                indexBucketLocator.getBucket(applicationId,
                        IndexType.COLLECTION, entity.getUuid(), collectionName));

        // Remove property indexes

        Schema schema = getDefaultSchema();
        for (String propertyName : entity.getProperties().keySet()) {
            boolean collection_indexes_property = schema
                    .isPropertyIndexedInCollection(headEntity.getType(),
                            collectionName, propertyName);
            boolean item_schema_has_property = schema.hasProperty(
                    entity.getType(), propertyName);
            boolean fulltext_indexed = schema
                    .isPropertyFulltextIndexed(entity.getType(), propertyName);
            if (collection_indexes_property || !item_schema_has_property) {
                IndexUpdate indexUpdate = batchStartIndexUpdate(batch, entity,
                        propertyName, null, timestampUuid,
                        item_schema_has_property, false, false,
                        fulltext_indexed);
                batchUpdateCollectionIndex(indexUpdate, headEntity,
                        collectionName);
            }
        }

        // Remove set indexes

        Set dictionaryNames = em.getDictionaryNames(entity);

        for (String dictionaryName : dictionaryNames) {
            boolean has_dictionary = schema.hasDictionary(
                    entity.getType(), dictionaryName);
            boolean dictionary_indexed = schema
                    .isDictionaryIndexedInCollection(headEntity.getType(),
                            collectionName, dictionaryName);

            if (dictionary_indexed || !has_dictionary) {
                Set elementValues = em.getDictionaryAsSet(entity,
                        dictionaryName);
                for (Object elementValue : elementValues) {
                    IndexUpdate indexUpdate = batchStartIndexUpdate(batch,
                            entity, dictionaryName, elementValue,
                            timestampUuid, has_dictionary, true, true, false);
                    batchUpdateCollectionIndex(indexUpdate, headEntity,
                            collectionName);
                }
            }
        }

        // Delete actual property

        addDeleteToMutator(batch, ENTITY_ID_SETS, collections_key,
                entity.getUuid(), timestamp);

        // Delete from subkeyed collections

        CollectionInfo collection = schema.getCollection(
                headEntity.getType(), collectionName);
        if (collection != null) {
            if (collection.hasSubkeys()) {
                List combos = collection.getSubkeyCombinations();
                for (String[] combo : combos) {
                    List subkey_props = new ArrayList();
                    for (String subkey_name : combo) {
                        Object subkey_value = null;
                        if (subkey_name != null) {
                            subkey_value = entity.getProperty(subkey_name);
                        }
                        subkey_props.add(subkey_value);
                    }
                    addDeleteToMutator(batch, ENTITY_ID_SETS,
                            key(collections_key, subkey_props.toArray()),
                            entity.getUuid(), timestamp);

                }
            }
        }

        addDeleteToMutator(
                batch,
                ENTITY_COMPOSITE_DICTIONARIES,
                key(entity.getUuid(), Schema.DICTIONARY_CONTAINER_ENTITIES),
                asList(headEntity.getType(), collectionName,
                        headEntity.getUuid()), timestamp);

        if (!headEntity.getType().equalsIgnoreCase(TYPE_APPLICATION)
                && !Schema.isAssociatedEntityType(entity.getType())) {
            em.deleteEntity(new SimpleCollectionRef(headEntity, collectionName,
                    entity).getUuid());
        }

        return batch;
    }

    @Metered(group="core",name="RelationManager_batchDeleteConnectionIndexEntries")
    public Mutator batchDeleteConnectionIndexEntries(
            IndexUpdate indexUpdate, IndexEntry entry,
            ConnectionRefImpl connection, UUID[] index_keys) throws Exception {

        // entity_id,prop_name
        Object property_index_key = key(index_keys[ConnectionRefImpl.ALL],
                INDEX_CONNECTIONS, entry.getPath(),
                indexBucketLocator.getBucket(applicationId,
                        IndexType.CONNECTION,
                        index_keys[ConnectionRefImpl.ALL], entry.getPath()));

        // entity_id,entity_type,prop_name
        Object entity_type_prop_index_key = key(
                index_keys[ConnectionRefImpl.BY_ENTITY_TYPE],
                INDEX_CONNECTIONS, entry.getPath(),
                indexBucketLocator.getBucket(applicationId,
                        IndexType.CONNECTION,
                        index_keys[ConnectionRefImpl.BY_ENTITY_TYPE],
                        entry.getPath()));

        // entity_id,connection_type,prop_name
        Object connection_type_prop_index_key = key(
                index_keys[ConnectionRefImpl.BY_CONNECTION_TYPE],
                INDEX_CONNECTIONS, entry.getPath(),
                indexBucketLocator.getBucket(applicationId,
                        IndexType.CONNECTION,
                        index_keys[ConnectionRefImpl.BY_CONNECTION_TYPE],
                        entry.getPath()));

        // entity_id,connection_type,entity_type,prop_name
        Object connection_type_and_entity_type_prop_index_key = key(
                index_keys[ConnectionRefImpl.BY_CONNECTION_AND_ENTITY_TYPE],
                INDEX_CONNECTIONS,
                entry.getPath(),
                indexBucketLocator
                        .getBucket(
                                applicationId,
                                IndexType.CONNECTION,
                                index_keys[ConnectionRefImpl.BY_CONNECTION_AND_ENTITY_TYPE],
                                entry.getPath()));

        // composite(property_value,connected_entity_id,connection_type,entity_type,entry_timestamp)
        addDeleteToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                property_index_key, entry.getIndexComposite(
                        connection.getConnectedEntityId(),
                        connection.getConnectionType(),
                        connection.getConnectedEntityType()),
                indexUpdate.getTimestamp());

        // composite(property_value,connected_entity_id,connection_type,entry_timestamp)
        addDeleteToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                entity_type_prop_index_key, entry.getIndexComposite(
                        connection.getConnectedEntityId(),
                        connection.getConnectionType()),
                indexUpdate.getTimestamp());

        // composite(property_value,connected_entity_id,entity_type,entry_timestamp)
        addDeleteToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                connection_type_prop_index_key, entry.getIndexComposite(
                        connection.getConnectedEntityId(),
                        connection.getConnectedEntityType()),
                indexUpdate.getTimestamp());

        // composite(property_value,connected_entity_id,entry_timestamp)
        addDeleteToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                connection_type_and_entity_type_prop_index_key,
                entry.getIndexComposite(connection.getConnectedEntityId()),
                indexUpdate.getTimestamp());

        return indexUpdate.getBatch();
    }

    @Metered(group="core",name="RelationManager_batchAddConnectionIndexEntries")
    public Mutator batchAddConnectionIndexEntries(
            IndexUpdate indexUpdate, IndexEntry entry,
            ConnectionRefImpl connection, UUID[] index_keys) {

        // entity_id,prop_name
        Object property_index_key = key(index_keys[ConnectionRefImpl.ALL],
                INDEX_CONNECTIONS, entry.getPath(),
                indexBucketLocator.getBucket(applicationId,
                        IndexType.CONNECTION,
                        index_keys[ConnectionRefImpl.ALL], entry.getPath()));

        // entity_id,entity_type,prop_name
        Object entity_type_prop_index_key = key(
                index_keys[ConnectionRefImpl.BY_ENTITY_TYPE],
                INDEX_CONNECTIONS, entry.getPath(),
                indexBucketLocator.getBucket(applicationId,
                        IndexType.CONNECTION,
                        index_keys[ConnectionRefImpl.BY_ENTITY_TYPE],
                        entry.getPath()));

        // entity_id,connection_type,prop_name
        Object connection_type_prop_index_key = key(
                index_keys[ConnectionRefImpl.BY_CONNECTION_TYPE],
                INDEX_CONNECTIONS, entry.getPath(),
                indexBucketLocator.getBucket(applicationId,
                        IndexType.CONNECTION,
                        index_keys[ConnectionRefImpl.BY_CONNECTION_TYPE],
                        entry.getPath()));

        // entity_id,connection_type,entity_type,prop_name
        Object connection_type_and_entity_type_prop_index_key = key(
                index_keys[ConnectionRefImpl.BY_CONNECTION_AND_ENTITY_TYPE],
                INDEX_CONNECTIONS,
                entry.getPath(),
                indexBucketLocator
                        .getBucket(
                                applicationId,
                                IndexType.CONNECTION,
                                index_keys[ConnectionRefImpl.BY_CONNECTION_AND_ENTITY_TYPE],
                                entry.getPath()));

        // composite(property_value,connected_entity_id,connection_type,entity_type,entry_timestamp)
        addInsertToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                property_index_key, entry.getIndexComposite(
                        connection.getConnectedEntityId(),
                        connection.getConnectionType(),
                        connection.getConnectedEntityType()),
                connection.getUuid(), indexUpdate.getTimestamp());

        // composite(property_value,connected_entity_id,connection_type,entry_timestamp)
        addInsertToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                entity_type_prop_index_key, entry.getIndexComposite(
                        connection.getConnectedEntityId(),
                        connection.getConnectionType()), connection.getUuid(),
                indexUpdate.getTimestamp());

        // composite(property_value,connected_entity_id,entity_type,entry_timestamp)
        addInsertToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                connection_type_prop_index_key, entry.getIndexComposite(
                        connection.getConnectedEntityId(),
                        connection.getConnectedEntityType()),
                connection.getUuid(), indexUpdate.getTimestamp());

        // composite(property_value,connected_entity_id,entry_timestamp)
        addInsertToMutator(indexUpdate.getBatch(), ENTITY_INDEX,
                connection_type_and_entity_type_prop_index_key,
                entry.getIndexComposite(connection.getConnectedEntityId()),
                connection.getUuid(), indexUpdate.getTimestamp());

        return indexUpdate.getBatch();
    }

    /**
     * Batch update connection index.
     *
     * @param batch
     *            the batch
     * @param applicationId
     *            the application id
     * @param connection
     *            the connection
     * @param entryName
     *            the entry name
     * @param entryValue
     *            the entry value
     * @param isSet
     *            the is set
     * @param removeListEntry
     *            the remove set entry
     * @param timestamp
     *            the timestamp
     * @return batch
     * @throws Exception
     *             the exception
     */
    @Metered(group="core",name="RelationManager_batchUpdateConnectionIndex")
    public IndexUpdate batchUpdateConnectionIndex(IndexUpdate indexUpdate,
            ConnectionRefImpl connection) throws Exception {

        // UUID connection_id = connection.getId();

        UUID[] index_keys = connection.getIndexIds();

        // Delete all matching entries from entry list
        for (IndexEntry entry : indexUpdate.getPrevEntries()) {

            if (entry.getValue() != null) {

                batchDeleteConnectionIndexEntries(indexUpdate, entry,
                        connection, index_keys);

                if ("location.coordinates".equals(entry.getPath())) {
                    EntityLocationRef loc = new EntityLocationRef(
                            indexUpdate.getEntity(), entry.getTimestampUuid(),
                            entry.getValue().toString());
                    batchDeleteLocationInConnectionsIndex(
                            indexUpdate.getBatch(), indexBucketLocator,
                            applicationId, index_keys, entry.getPath(), loc);
                }

            } else {
                logger.error("Unexpected condition - deserialized property value is null");
            }
        }

        if ((indexUpdate.getNewEntries().size() > 0)
                && (!indexUpdate.isMultiValue() || (indexUpdate.isMultiValue() && !indexUpdate
                        .isRemoveListEntry()))) {

            for (IndexEntry indexEntry : indexUpdate.getNewEntries()) {

                batchAddConnectionIndexEntries(indexUpdate, indexEntry,
                        connection, index_keys);

                if ("location.coordinates".equals(indexEntry.getPath())) {
                    EntityLocationRef loc = new EntityLocationRef(
                            indexUpdate.getEntity(),
                            indexEntry.getTimestampUuid(), indexEntry
                                    .getValue().toString());
                    batchStoreLocationInConnectionsIndex(
                            indexUpdate.getBatch(), indexBucketLocator,
                            applicationId, index_keys, indexEntry.getPath(),
                            loc);
                }
            }

            /*
             * addInsertToMutator(batch, EntityCF.SETS, key(connection_id,
             * Schema.INDEXES_SET), indexEntry.getKey(), null, false,
             * timestamp); }
             *
             * addInsertToMutator(batch, EntityCF.SETS, key(connection_id,
             * Schema.INDEXES_SET), entryName, null, false, timestamp);
             */
        }

        for (String index : indexUpdate.getIndexesSet()) {
            addInsertToMutator(
                    indexUpdate.getBatch(),
                    ENTITY_DICTIONARIES,
                    key(connection.getConnectingIndexId(),
                            Schema.DICTIONARY_INDEXES), index, null,
                    indexUpdate.getTimestamp());
        }

        return indexUpdate;
    }

    public Set getConnectionIndexes(ConnectionRefImpl connection)
            throws Exception {
        List> results = cass.getAllColumns(
                cass.getApplicationKeyspace(applicationId),
                ENTITY_DICTIONARIES,
                key(connection.getConnectingIndexId(),
                        Schema.DICTIONARY_INDEXES), se, se);
        Set indexes = new TreeSet();
        if (results != null) {
            for (HColumn column : results) {
                String propertyName = column.getName();
                if (!propertyName.endsWith(".keywords")) {
                    indexes.add(column.getName());
                }
            }
        }
        return indexes;
    }

    /**
     * Batch update backword connections property indexes.
     *
     * @param batch
     *            the batch
     * @param applicationId
     *            the application id
     * @param itemType
     *            the item type
     * @param itemId
     *            the item id
     * @param propertyName
     *            the property name
     * @param propertyValue
     *            the property value
     * @param timestamp
     *            the timestamp
     * @return batch
     * @throws Exception
     *             the exception
     */
    @Metered(group="core",name="RelationManager_batchUpdateBackwardConnectionsPropertyIndexes")
    public IndexUpdate batchUpdateBackwardConnectionsPropertyIndexes(
            IndexUpdate indexUpdate) throws Exception {

        logger.debug("batchUpdateBackwordConnectionsPropertyIndexes");

        boolean entitySchemaHasProperty = indexUpdate.isSchemaHasProperty();

        if (entitySchemaHasProperty) {
            if (!getDefaultSchema().isPropertyIndexedInConnections(
                    indexUpdate.getEntity().getType(),
                    indexUpdate.getEntryName())) {
                return indexUpdate;
            }
        }

        List connections = getConnections(
                ConnectionRefImpl.toConnectedEntity(indexUpdate.getEntity()),
                true);

        for (ConnectionRefImpl connection : connections) {

            batchUpdateConnectionIndex(indexUpdate, connection);
        }

        return indexUpdate;
    }

    /**
     * Batch update backword connections set indexes.
     *
     * @param batch
     *            the batch
     * @param applicationId
     *            the application id
     * @param itemType
     *            the item type
     * @param itemId
     *            the item id
     * @param setName
     *            the set name
     * @param property
     *            the property
     * @param elementValue
     *            the set value
     * @param removeFromSet
     *            the remove from set
     * @param timestamp
     *            the timestamp
     * @return batch
     * @throws Exception
     *             the exception
     */
    @Metered(group="core",name="RelationManager_batchUpdateBackwardConnectionsDictionaryIndexes")
    public IndexUpdate batchUpdateBackwardConnectionsDictionaryIndexes(
            IndexUpdate indexUpdate) throws Exception {

        logger.debug("batchUpdateBackwardConnectionsListIndexes");

        boolean entityHasDictionary = getDefaultSchema()
                .isDictionaryIndexedInConnections(
                        indexUpdate.getEntity().getType(),
                        indexUpdate.getEntryName());
        if (!entityHasDictionary) {
            return indexUpdate;
        }

        List connections = getConnections(
                ConnectionRefImpl.toConnectedEntity(indexUpdate.getEntity()),
                true);

        for (ConnectionRefImpl connection : connections) {

            batchUpdateConnectionIndex(indexUpdate, connection);
        }

        return indexUpdate;
    }

    @SuppressWarnings("unchecked")
    @Metered(group="core",name="RelationManager_batchUpdateEntityConnection")
    public Mutator batchUpdateEntityConnection(
            Mutator batch, boolean disconnect,
            ConnectionRefImpl connection, UUID timestampUuid) throws Exception {

        long timestamp = getTimestampInMicros(timestampUuid);

        Entity connectedEntity = em.loadPartialEntity(connection
                .getConnectedEntityId());
        if (connectedEntity == null) {
            return batch;
        }

        // Create connection for requested params

        Object connection_id = connection.getUuid();
        Map columns = connection.toColumnMap();

        if (disconnect) {
            addDeleteToMutator(batch, ENTITY_CONNECTIONS, connection_id,
                    timestamp, columns.keySet().toArray());

            addDeleteToMutator(
                    batch,
                    ENTITY_COMPOSITE_DICTIONARIES,
                    key(connection.getConnectingEntityId(),
                            DICTIONARY_CONNECTED_ENTITIES,
                            connection.getConnectionType()),
                    asList(connection.getConnectedEntityId(),
                            connection.getConnectedEntityType()), timestamp);

            addDeleteToMutator(
                    batch,
                    ENTITY_COMPOSITE_DICTIONARIES,
                    key(connection.getConnectedEntityId(),
                            DICTIONARY_CONNECTING_ENTITIES,
                            connection.getConnectionType()),
                    asList(connection.getConnectingEntityId(),
                            connection.getConnectingEntityType()), timestamp);

            // delete the connection path if there will be no connections left
            boolean delete = true;
            for (ConnectionRefImpl c : getConnectionsWithEntity(connection.getConnectingEntityId())) {
              if (c.getConnectedEntity().getConnectionType().equals(connection.getConnectedEntity().getConnectionType())) {
                if (!c.getConnectedEntity().getUuid().equals(connection.getConnectedEntity().getUuid())) {
                  delete = false;
                  break;
                }
              }
            }
            if (delete) {
              addDeleteToMutator(
                batch,
                ENTITY_DICTIONARIES,
                key(connection.getConnectingEntityId(), DICTIONARY_CONNECTED_TYPES),
                connection.getConnectionType(), timestamp);
            }

            // delete the connection path if there will be no connections left
            delete = true;
            for (ConnectionRefImpl c : getConnectionsWithEntity(connection.getConnectedEntityId())) {
              if (c.getConnectedEntity().getConnectionType().equals(connection.getConnectedEntity().getConnectionType())) {
                if (!c.getConnectingEntity().getUuid().equals(connection.getConnectingEntity().getUuid())) {
                  delete = false;
                  break;
                }
              }
            }
            if (delete) {
              addDeleteToMutator(
                batch,
                ENTITY_DICTIONARIES,
                key(connection.getConnectedEntityId(), DICTIONARY_CONNECTING_TYPES),
                connection.getConnectionType(), timestamp);
            }

        } else {
            addInsertToMutator(batch, ENTITY_CONNECTIONS, connection_id,
                    columns, timestamp);

            addInsertToMutator(
                    batch,
                    ENTITY_COMPOSITE_DICTIONARIES,
                    key(connection.getConnectingEntityId(),
                            DICTIONARY_CONNECTED_ENTITIES,
                            connection.getConnectionType()),
                    asList(connection.getConnectedEntityId(),
                            connection.getConnectedEntityType()), timestamp,
                    timestamp);

            addInsertToMutator(
                    batch,
                    ENTITY_COMPOSITE_DICTIONARIES,
                    key(connection.getConnectedEntityId(),
                            DICTIONARY_CONNECTING_ENTITIES,
                            connection.getConnectionType()),
                    asList(connection.getConnectingEntityId(),
                            connection.getConnectingEntityType()), timestamp,
                    timestamp);

            // Add connection type to connections set
            addInsertToMutator(
                    batch,
                    ENTITY_DICTIONARIES,
                    key(connection.getConnectingEntityId(),
                            DICTIONARY_CONNECTED_TYPES),
                    connection.getConnectionType(), null, timestamp);

            // Add connection type to connections set
            addInsertToMutator(
                    batch,
                    ENTITY_DICTIONARIES,
                    key(connection.getConnectedEntityId(),
                            DICTIONARY_CONNECTING_TYPES),
                    connection.getConnectionType(), null, timestamp);
        }

        // Add property indexes

        // Iterate though all the properties of the connected entity

        Schema schema = getDefaultSchema();
        for (String propertyName : connectedEntity.getProperties().keySet()) {
            Object propertyValue = connectedEntity.getProperties().get(
                    propertyName);

            boolean indexed = schema.isPropertyIndexed(
                    connectedEntity.getType(), propertyName);

            boolean connection_indexes_property = schema
                    .isPropertyIndexedInConnections(connectedEntity.getType(),
                            propertyName);
            boolean item_schema_has_property = schema.hasProperty(
                    connectedEntity.getType(), propertyName);
            boolean fulltext_indexed = schema
                    .isPropertyFulltextIndexed(connectedEntity.getType(),
                            propertyName);
            // For each property, if the schema says it's indexed, update its
            // index

            if (indexed
                    && (connection_indexes_property || !item_schema_has_property)) {
                IndexUpdate indexUpdate = batchStartIndexUpdate(batch,
                        connectedEntity, propertyName, disconnect ? null
                                : propertyValue, timestampUuid,
                        item_schema_has_property, false, false,
                        fulltext_indexed);
                batchUpdateConnectionIndex(indexUpdate, connection);
            }
        }

        // Add indexes for the connected entity's list properties

        // Get the names of the list properties in the connected entity
        Set dictionaryNames = em.getDictionaryNames(connectedEntity);

        // For each list property, get the values in the list and
        // update the index with those values

        for (String dictionaryName : dictionaryNames) {
            boolean has_dictionary = schema.hasDictionary(
                    connectedEntity.getType(), dictionaryName);
            boolean dictionary_indexed = schema
                    .isDictionaryIndexedInConnections(
                            connectedEntity.getType(), dictionaryName);

            if (dictionary_indexed || !has_dictionary) {
                Set elementValues = em.getDictionaryAsSet(
                        connectedEntity, dictionaryName);
                for (Object elementValue : elementValues) {
                    IndexUpdate indexUpdate = batchStartIndexUpdate(batch,
                            connectedEntity, dictionaryName, elementValue,
                            timestampUuid, has_dictionary, true, disconnect,
                            false);
                    batchUpdateConnectionIndex(indexUpdate, connection);
                }
            }
        }

        if (disconnect) {
            cass.deleteRow(cass.getApplicationKeyspace(applicationId),
                    ENTITY_CONNECTIONS, connection_id, timestamp);

            // TODO any more rows to delete?
        }

        return batch;
    }

    public void updateEntityConnection(boolean disconnect,
            ConnectionRefImpl connection) throws Exception {

        UUID timestampUuid = newTimeUUID();
        Mutator batch = createMutator(
                cass.getApplicationKeyspace(applicationId), be);

        // Make or break the connection

        batchUpdateEntityConnection(batch, disconnect, connection,
                timestampUuid);

        // Make or break a connection from the connecting entity
        // to the connection itself

        ConnectionRefImpl loopback = connection
                .getConnectionToConnectionEntity();
        if (!disconnect) {
            em.insertEntity(CONNECTION_ENTITY_CONNECTION_TYPE,
                    loopback.getConnectedEntityId());
        }

        batchUpdateEntityConnection(batch, disconnect, loopback, timestampUuid);

        batchExecute(batch, CassandraService.RETRY_COUNT);

    }

    @Metered(group="core",name="RelationManager_batchDisconnect")
    public void batchDisconnect(Mutator batch, UUID timestampUuid)
            throws Exception {
        List connections = getConnectionsWithEntity(headEntity
                .getUuid());
        if (connections != null) {
            for (ConnectionRefImpl connection : connections) {
                batchUpdateEntityConnection(batch, true, connection,
                        timestampUuid);
            }
        }
    }

    public IndexUpdate batchStartIndexUpdate(Mutator batch,
            Entity entity, String entryName, Object entryValue,
            UUID timestampUuid, boolean schemaHasProperty,
            boolean isMultiValue, boolean removeListEntry,
            boolean fulltextIndexed) throws Exception {
        return batchStartIndexUpdate(batch, entity, entryName, entryValue,
                timestampUuid, schemaHasProperty, isMultiValue,
                removeListEntry, fulltextIndexed, false);
    }



    @Metered(group="core",name="RelationManager_batchStartIndexUpdate")
    public IndexUpdate batchStartIndexUpdate(Mutator batch,
            Entity entity, String entryName, Object entryValue,
            UUID timestampUuid, boolean schemaHasProperty,
            boolean isMultiValue, boolean removeListEntry,
            boolean fulltextIndexed, boolean skipRead) throws Exception {

        long timestamp = getTimestampInMicros(timestampUuid);

        IndexUpdate indexUpdate = new IndexUpdate(batch, entity, entryName,
                entryValue, schemaHasProperty, isMultiValue, removeListEntry,
                timestampUuid);

        // entryName = entryName.toLowerCase();

        // entity_id,connection_type,connected_entity_id,prop_name

        if (!skipRead) {

            List> entries = null;

            if (isMultiValue && validIndexableValue(entryValue)) {
                entries = cass.getColumns(cass
                        .getApplicationKeyspace(applicationId),
                        ENTITY_INDEX_ENTRIES, entity.getUuid(),
                        new DynamicComposite(entryName,
                                indexValueCode(entryValue),
                                toIndexableValue(entryValue)),
                        setGreaterThanEqualityFlag(new DynamicComposite(
                                entryName, indexValueCode(entryValue),
                                toIndexableValue(entryValue))),
                        INDEX_ENTRY_LIST_COUNT, false);
            } else {
                entries = cass.getColumns(cass
                        .getApplicationKeyspace(applicationId),
                        ENTITY_INDEX_ENTRIES, entity.getUuid(),
                        new DynamicComposite(entryName),
                        setGreaterThanEqualityFlag(new DynamicComposite(
                                entryName)), INDEX_ENTRY_LIST_COUNT, false);
            }

            if(logger.isDebugEnabled()){
                logger.debug("Found {} previous index entries for {} of entity {}" , new Object[]{entries.size(),entryName, entity.getUuid() });
            }

            // Delete all matching entries from entry list
            for (HColumn entry : entries) {
                UUID prev_timestamp = null;
                Object prev_value = null;
                String prev_obj_path = null;

                // new format:
                // composite(entryName,
                // value_code,prev_value,prev_timestamp,prev_obj_path) = null
                DynamicComposite composite = DynamicComposite
                        .fromByteBuffer(entry.getName().duplicate());
                prev_value = composite.get(2);
                prev_timestamp = (UUID) composite.get(3);
                if (composite.size() > 4) {
                    prev_obj_path = (String) composite.get(4);
                }

                if (prev_value != null) {

                    String entryPath = entryName;
                    if ((prev_obj_path != null) && (prev_obj_path.length() > 0)) {
                        entryPath = entryName + "." + prev_obj_path;
                    }

                    indexUpdate.addPrevEntry(entryPath, prev_value,prev_timestamp, entry.getName().duplicate());

                    // composite(property_value,connected_entity_id,entry_timestamp)
//                    addDeleteToMutator(batch, ENTITY_INDEX_ENTRIES,
//                            entity.getUuid(), entry.getName(), timestamp);

                } else {
                    logger.error("Unexpected condition - deserialized property value is null");
                }
            }
        }

        if (!isMultiValue || (isMultiValue && !removeListEntry)) {

            List> list = IndexUtils.getKeyValueList(
                    entryName, entryValue, fulltextIndexed);

            if (entryName.equalsIgnoreCase("location")
                    && (entryValue instanceof Map)) {
                @SuppressWarnings("rawtypes")
                double latitude = MapUtils.getDoubleValue((Map) entryValue,
                        "latitude");
                @SuppressWarnings("rawtypes")
                double longitude = MapUtils.getDoubleValue((Map) entryValue,
                        "longitude");
                list.add(new AbstractMap.SimpleEntry(
                        "location.coordinates", latitude + "," + longitude));
            }

            for (Map.Entry indexEntry : list) {

                if (validIndexableValue(indexEntry.getValue())) {
                    indexUpdate.addNewEntry(indexEntry.getKey(),
                            toIndexableValue(indexEntry.getValue()));
                }

            }

            if (isMultiValue) {
                addInsertToMutator(
                        batch,
                        ENTITY_INDEX_ENTRIES,
                        entity.getUuid(),
                        asList(entryName, indexValueCode(entryValue),
                                toIndexableValue(entryValue),
                                indexUpdate.getTimestampUuid()), null,
                        timestamp);
            } else {
                // int i = 0;

                for (Map.Entry indexEntry : list) {

                    String name = indexEntry.getKey();
                    if (name.startsWith(entryName + ".")) {
                        name = name.substring(entryName.length() + 1);
                    } else if (name.startsWith(entryName)) {
                        name = name.substring(entryName.length());
                    }

                    byte code = indexValueCode(indexEntry.getValue());
                    Object val = toIndexableValue(indexEntry.getValue());
                    addInsertToMutator(
                            batch,
                            ENTITY_INDEX_ENTRIES,
                            entity.getUuid(),
                            asList(entryName, code, val,
                                    indexUpdate.getTimestampUuid(), name),
                            null, timestamp);

                    indexUpdate.addIndex(indexEntry.getKey());
                }
            }

            indexUpdate.addIndex(entryName);

        }

        return indexUpdate;
    }

    @Metered(group="core",name="RelationManager_batchUpdatePropertyIndexes")
    public void batchUpdatePropertyIndexes(Mutator batch,
            String propertyName, Object propertyValue,
            boolean entitySchemaHasProperty, boolean noRead, UUID timestampUuid)
            throws Exception {

        Entity entity = getHeadEntity();

        UUID associatedId = null;
        String associatedType = null;

        if (Schema.isAssociatedEntityType(entity.getType())) {
            Object item = entity.getProperty(PROPERTY_ITEM);
            if ((item instanceof UUID)
                    && (entity.getProperty(PROPERTY_COLLECTION_NAME) instanceof String)) {
                associatedId = (UUID) item;
                associatedType = string(entity.getProperty(PROPERTY_ITEM_TYPE));
                String entryName = TYPE_MEMBER + "." + propertyName;
                if(logger.isDebugEnabled()){
                    logger.debug("Extended property {} ( {} ).{} indexed as {} ({})." + entryName, new Object[]{entity.getType(), entity.getUuid(),propertyName, associatedType ,associatedId });
                }
                propertyName = entryName;
            }
        }

        IndexUpdate indexUpdate = batchStartIndexUpdate(
                batch,
                entity,
                propertyName,
                propertyValue,
                timestampUuid,
                entitySchemaHasProperty,
                false,
                false,
                getDefaultSchema().isPropertyFulltextIndexed(entity.getType(),
                        propertyName), noRead);

        // Update collections

        String effectiveType = entity.getType();
        if (associatedType != null) {
            indexUpdate.setAssociatedId(associatedId);
            effectiveType = associatedType;
        }

        Map> containers = getDefaultSchema().getContainers(effectiveType);
        if (containers != null) {

            Map> containerEntities = null;
            if (noRead) {
                containerEntities = new LinkedHashMap>();
                EntityRef applicationRef = new SimpleEntityRef(
                        TYPE_APPLICATION, applicationId);
                addMapSet(containerEntities, applicationRef,
                        defaultCollectionName(entity.getType()));
            } else {
                containerEntities = getContainingCollections();
            }

            for (EntityRef containerEntity : containerEntities.keySet()) {
                if (containerEntity.getType().equals(TYPE_APPLICATION)
                        && Schema.isAssociatedEntityType(entity.getType())) {
                    logger.debug("Extended properties for {} not indexed by application", entity.getType());
                    continue;
                }
                Set collectionNames = containerEntities
                        .get(containerEntity);
                Set collections = containers
                        .get(containerEntity.getType());

                if (collections != null) {
                    for (CollectionInfo collection : collections) {
                        if (collectionNames.contains(collection.getName())) {
                            batchUpdateCollectionIndex(indexUpdate,
                                    containerEntity, collection.getName());
                        }
                    }
                }
            }
        }

        if (!noRead) {
            batchUpdateBackwardConnectionsPropertyIndexes(indexUpdate);
        }


        /**
         * We've updated the properties, add the deletes to the ledger
         *
         */

        for(IndexEntry entry: indexUpdate.getPrevEntries()){
          addDeleteToMutator(batch, ENTITY_INDEX_ENTRIES, entity.getUuid(), entry.getLedgerColumn(), indexUpdate.getTimestamp());
        }
    }

    public void batchUpdateSetIndexes(Mutator batch,
            String setName, Object elementValue, boolean removeFromSet,
            UUID timestampUuid) throws Exception {

        Entity entity = getHeadEntity();

        elementValue = getDefaultSchema().validateEntitySetValue(
                entity.getType(), setName, elementValue);

        IndexUpdate indexUpdate = batchStartIndexUpdate(batch, entity, setName,
                elementValue, timestampUuid, true, true, removeFromSet, false);

        // Update collections
        Map> containers = getDefaultSchema()
                .getContainersIndexingDictionary(entity.getType(), setName);

        if (containers != null) {
            Map> containerEntities = getContainingCollections();
            for (EntityRef containerEntity : containerEntities.keySet()) {
                if (containerEntity.getType().equals(TYPE_APPLICATION)
                        && Schema.isAssociatedEntityType(entity.getType())) {
                    logger.debug("Extended properties for {} not indexed by application", entity.getType());
                    continue;
                }
                Set collectionNames = containerEntities
                        .get(containerEntity);
                Set collections = containers
                        .get(containerEntity.getType());

                if (collections != null) {

                    for (CollectionInfo collection : collections) {
                        if (collectionNames.contains(collection.getName())) {

                            batchUpdateCollectionIndex(indexUpdate,
                                    containerEntity, collection.getName());
                        }
                    }
                }
            }
        }

        batchUpdateBackwardConnectionsDictionaryIndexes(indexUpdate);

    }

    /**
     * Process index results.
     *
     * @param results
     *            the results
     * @param compositeResults
     *            the composite results
     * @param compositeOffset
     *            the composite offset
     * @param connectionType
     *            the connection type
     * @param entityType
     *            the entity type
     * @param outputList
     *            the output list
     * @param outputDetails
     *            the output details
     * @throws Exception
     *             the exception
     */
    public Results getIndexResults(
            List> results,
            boolean compositeResults, String connectionType, String entityType,
            Level level) throws Exception {

        if (results == null) {
            return null;
        }

        Set idSet = new LinkedHashSet();
        List ids = null;
        List refs = null;
        Map> metadata = new LinkedHashMap>();

        if (level.ordinal() > REFS.ordinal()) {
            level = REFS;
        }

        if (compositeResults && (level != IDS)) {
            refs = new ArrayList();
        } else {
            ids = new ArrayList();
        }

        for (HColumn result : results) {

            UUID connectedEntityId = null;
            String cType = connectionType;
            String eType = entityType;
            UUID associatedEntityId = null;

            if (compositeResults) {
                List objects = DynamicComposite.fromByteBuffer(result
                        .getName().duplicate());
                connectedEntityId = (UUID) objects.get(2);
                if (refs != null) {
                    if ((connectionType == null) || (entityType == null)) {
                        if (connectionType != null) {
                            eType = StringUtils.ifString(objects.get(3));
                        } else if (entityType != null) {
                            cType = StringUtils.ifString(objects.get(3));
                        } else {
                            cType = StringUtils.ifString(objects.get(3));
                            eType = StringUtils.ifString(objects.get(4));
                        }
                    }
                }
            } else {
                connectedEntityId = uuid(result.getName());
            }

            ByteBuffer v = result.getValue();
            if ((v != null) && (v.remaining() >= 16)) {
                associatedEntityId = uuid(result.getValue());
            }

            if ((refs != null) && (eType != null)) {
                if (!idSet.contains(connectedEntityId)) {
                    refs.add(new SimpleEntityRef(eType, connectedEntityId));
                    idSet.add(connectedEntityId);
                } else {
                    logger.error("Duplicate entity uuid ("
                            + connectedEntityId
                            + ") found in index results, discarding but index appears inconsistent...");
                }
            }

            if (ids != null) {
                if (!idSet.contains(connectedEntityId)) {
                    ids.add(connectedEntityId);
                    idSet.add(connectedEntityId);
                } else {
                    logger.error("Duplicate entity uuid ({}) found in index results, discarding but index appears inconsistent...", connectedEntityId);
                }
            }

            if (cType != null) {
                MapUtils.putMapMap(metadata, connectedEntityId,
                        PROPERTY_CONNECTION, cType);
            }

            String cursor = encodeBase64URLSafeString(bytes(result.getName()));
            if (cursor != null) {
                MapUtils.putMapMap(metadata, connectedEntityId,
                        PROPERTY_CURSOR, cursor);
            }

            if (associatedEntityId != null) {
                MapUtils.putMapMap(metadata, connectedEntityId,
                        PROPERTY_ASSOCIATED, associatedEntityId);
            }

        }

        Results r = null;

        if ((refs != null) && (refs.size() > 0)) {
            r = fromRefList(refs);
        } else if ((ids != null) && (ids.size() > 0)) {
            r = fromIdList(ids);
        } else {
            r = new Results();
        }

        if (metadata.size() > 0) {
            r.setMetadata(metadata);
        }

        return r;
    }

    /**
     * Search index.
     *
     * @param applicationId
     *            the application id
     * @param indexKey
     *            the index key
     * @param searchName
     *            the search name
     * @param searchStartValue
     *            the search start value
     * @param searchFinishValue
     *            the search finish value
     * @param startResult
     *            the start result
     * @param count
     *            the count
     * @return the list
     * @throws Exception
     *             the exception
     */
    @Metered(group="core",name="RelationManager_searchIndex")
    public List> searchIndex(Object indexKey,
            String searchName, Object searchStartValue,
            Object searchFinishValue, UUID startResult, String cursor,
            int count, boolean reversed) throws Exception {

        searchStartValue = toIndexableValue(searchStartValue);
        searchFinishValue = toIndexableValue(searchFinishValue);

        if (NULL_ID.equals(startResult)) {
            startResult = null;
        }

        List> results = null;

        Object index_key = key(indexKey, searchName);
        Object start = null;
        Object finish = null;

        if ("*".equals(searchStartValue)) {
            if (isNotBlank(cursor)) {
                byte[] cursorBytes = decodeBase64(cursor);
                if (reversed) {
                    finish = cursorBytes;
                } else {
                    start = cursorBytes;
                }
            }

        } else if (StringUtils.isString(searchStartValue)
                && StringUtils.isStringOrNull(searchFinishValue)) {
            String strStartValue = searchStartValue.toString().toLowerCase()
                    .trim();

            String strFinishValue = strStartValue;
            if (searchFinishValue != null) {
                strFinishValue = searchFinishValue.toString().toLowerCase()
                        .trim();
            }

            if (strStartValue.endsWith("*")) {
                strStartValue = removeEnd(strStartValue, "*");
                finish = new DynamicComposite(indexValueCode(""), strStartValue
                        + "\uFFFF");
                if (isBlank(strStartValue)) {
                    strStartValue = "\0000";
                }
            } else {
                finish = new DynamicComposite(indexValueCode(""),
                        strFinishValue + "\u0000");
            }

            if (startResult != null) {
                start = new DynamicComposite(indexValueCode(strStartValue),
                        strStartValue, startResult);
            } else {
                start = new DynamicComposite(indexValueCode(strStartValue),
                        strStartValue);
            }

            if (isNotBlank(cursor)) {
                byte[] cursorBytes = decodeBase64(cursor);
                // if (Composite.validate(0, cursorBytes, false)) {
                start = cursorBytes;
                // }
            }

        } else if (StringUtils.isString(searchFinishValue)) {

            String strFinishValue = searchFinishValue.toString().toLowerCase()
                    .trim();

            finish = new DynamicComposite(indexValueCode(strFinishValue),
                    strFinishValue);

            if (isNotBlank(cursor)) {
                byte[] cursorBytes = decodeBase64(cursor);
                // if (Composite.validate(0, cursorBytes, false)) {
                start = cursorBytes;
                // }
            }

        } else if (searchStartValue != null) {
            if (searchFinishValue == null) {
                searchFinishValue = searchStartValue;
            }
            if (startResult != null) {
                start = new DynamicComposite(indexValueCode(searchStartValue),
                        searchStartValue, startResult);
            } else {
                start = new DynamicComposite(indexValueCode(searchStartValue),
                        searchStartValue);
            }
            finish = new DynamicComposite(indexValueCode(searchFinishValue),
                    searchFinishValue);
            setEqualityFlag((DynamicComposite) finish,
                    ComponentEquality.GREATER_THAN_EQUAL);

            if (isNotBlank(cursor)) {
                byte[] cursorBytes = decodeBase64(cursor);
                // if (Composite.validate(0, cursorBytes, false)) {
                start = cursorBytes;
                // }
            }

        }

        results = cass.getColumns(cass.getApplicationKeyspace(applicationId),
                ENTITY_INDEX, index_key, start, finish, count, reversed);

        return results;
    }


    private List> searchIndex(Object indexKey,
            QuerySlice slice, int count) throws Exception {

        Object start = getStart(slice);

        Object finish = getFinish(slice);

        if (slice.isReversed() && (start != null) && (finish != null)) {
            Object temp = start;
            start = finish;
            finish = temp;
        }

        Object keyPrefix = key(indexKey, slice.getPropertyName());

        IndexBucketScanner scanner = new IndexBucketScanner(cass,
                indexBucketLocator, ENTITY_INDEX, applicationId,
                IndexType.CONNECTION, keyPrefix, start, finish,
                slice.isReversed(), count, slice.getPropertyName());

        return scanner.load();

    }

    /**
     * Search the collection index using all the buckets for the given
     * collection
     *
     * @param indexKey
     * @param slice
     * @param count
     * @param collectionName
     * @return
     * @throws Exception
     */
    private List> searchIndexBuckets(
            Object indexKey, QuerySlice slice, int count, String collectionName)
            throws Exception {

        Object start = getStart(slice);

        Object finish = getFinish(slice);

        if (slice.isReversed() && (start != null) && (finish != null)) {
            Object temp = start;
            start = finish;
            finish = temp;
        }

        Object keyPrefix = key(indexKey, slice.getPropertyName());

        IndexBucketScanner scanner = new IndexBucketScanner(cass,
                indexBucketLocator, ENTITY_INDEX, applicationId,
                IndexType.COLLECTION, keyPrefix, start, finish,
                slice.isReversed(), count, collectionName);

        return scanner.load();

    }

    private Object getStart(QuerySlice slice) {
        Object start = null;

        if (slice.getCursor() != null) {
            start = slice.getCursor();
        } else if (slice.getStart() != null) {
            start = new DynamicComposite(slice.getStart().getCode(), slice
                    .getStart().getValue());
            if (!slice.getStart().isInclusive()) {
                setEqualityFlag((DynamicComposite) start,
                        ComponentEquality.GREATER_THAN_EQUAL);
            }
        }

        return start;
    }

    private Object getFinish(QuerySlice slice) {
        Object finish = null;

        if (slice.getFinish() != null) {
            finish = new DynamicComposite(slice.getFinish().getCode(), slice
                    .getFinish().getValue());
            if (slice.getFinish().isInclusive()) {
                setEqualityFlag((DynamicComposite) finish,
                        ComponentEquality.GREATER_THAN_EQUAL);
            }
        }

        return finish;
    }

    @Override
    @Metered(group="core",name="RelationManager_getOwners")
    public Map>> getOwners() throws Exception {
        Map> containerEntities = getContainingCollections();
        Map>> owners = new LinkedHashMap>>();

        for (EntityRef owner : containerEntities.keySet()) {
            Set collections = containerEntities.get(owner);
            for (String collection : collections) {
                MapUtils.addMapMapSet(owners, owner.getType(), owner.getUuid(),
                        collection);
            }
        }

        return owners;
    }

    @Override
    @Metered(group="core",name="RelationManager_getCollections")
    public Set getCollections() throws Exception {
        Entity e = getHeadEntity();

        Map collections = getDefaultSchema()
                .getCollections(e.getType());
        if (collections == null) {
            return null;
        }

        return collections.keySet();
    }

    @Override
    @Metered(group="core",name="RelationManager_getCollection_start_result")
    public Results getCollection(String collectionName, UUID startResult,
            int count, Results.Level resultsLevel, boolean reversed)
            throws Exception {
        // Entity_Collections
        // Key: entity_id,collection_name
        List ids = cass.getIdList(
                cass.getApplicationKeyspace(applicationId),
                key(headEntity.getUuid(), DICTIONARY_COLLECTIONS,
                        collectionName), startResult, null, count + 1,
                reversed, indexBucketLocator, applicationId, collectionName);

        Results results = null;

        if (resultsLevel == Results.Level.IDS) {
            results = Results.fromIdList(ids);
        } else {
            results = Results.fromIdList(ids, getDefaultSchema()
                    .getCollectionType(headEntity.getType(), collectionName));
        }

        return em.loadEntities(results, resultsLevel, count);
    }

    @Override
    @Metered(group="core",name="RelationManager_getCollectionSize")
    public long getCollectionSize(String collectionName) throws Exception {
        long result = 0;

        for (String bucketId : indexBucketLocator.getBuckets(applicationId,
                IndexType.COLLECTION, collectionName)) {

            result += cass.countColumns(
                    cass.getApplicationKeyspace(applicationId),
                    ENTITY_ID_SETS,
                    key(headEntity.getUuid(), DICTIONARY_COLLECTIONS,
                            collectionName, bucketId));
        }

        return result;
    }

    // @Override
    // public Results getCollection(String collectionName,
    // Map subkeyProperties, UUID startResult, int count,
    // Results.Level resultsLevel, boolean reversed) throws Exception {
    //
    // Entity e = getHeadEntity();
    //
    // CollectionInfo collection = getDefaultSchema().getCollection(
    // e.getType(), collectionName);
    //
    // Object subkey_key = getCFKeyForSubkey(collection, subkeyProperties,
    // null);
    //
    // Map ids = null;
    //
    // if (subkey_key != null) {
    // ids = cass
    // .getIdPairList(
    // cass.getApplicationKeyspace(applicationId),
    // key(e.getUuid(), DICTIONARY_COLLECTIONS,
    // collectionName, subkey_key), startResult,
    // null, count + 1, reversed);
    // } else {
    // ids = cass.getIdPairList(
    // cass.getApplicationKeyspace(applicationId),
    // key(e.getUuid(), DICTIONARY_COLLECTIONS, collectionName),
    // startResult, null, count + 1, reversed);
    // }
    //
    // Results results = Results.fromIdList(new ArrayList(ids.keySet()),
    // collection.getType());
    //
    // return em.loadEntities(results, resultsLevel, ids, count);
    // }

    @Override
    @Metered(group="core",name="RelationManager_getCollecitonForQuery")
    public Results getCollection(String collectionName, Query query,
            Results.Level resultsLevel) throws Exception {

        UUID startResult = query.getStartResult();
        String cursor = query.getCursor();
        if (cursor != null) {
            byte[] cursorBytes = decodeBase64(cursor);
            if ((cursorBytes != null) && (cursorBytes.length == 16)) {
                startResult = uuid(cursorBytes);
            }
        }
        int count = query.getLimit();

        Results results = getCollection(collectionName, startResult, count,
                resultsLevel, query.isReversed());

        if (results != null) {
            results.setQuery(query);
        }

        return results;

    }

    @Override
    @Metered(group="core",name="RelationManager_addToCollection")
    public Entity addToCollection(String collectionName, EntityRef itemRef)
            throws Exception {

        Entity entity = getHeadEntity();
        Entity itemEntity = em.get(itemRef);

        if (itemEntity == null) {
            return null;
        }

        CollectionInfo collection = getDefaultSchema().getCollection(
                entity.getType(), collectionName);
        if ((collection != null)
                && !collection.getType().equals(itemRef.getType())) {
            return null;
        }

        UUID timestampUuid = newTimeUUID();
        Mutator batch = createMutator(
                cass.getApplicationKeyspace(applicationId), be);

        batchAddToCollection(batch, collectionName, itemEntity, timestampUuid);

        if (collection.getLinkedCollection() != null) {
            getRelationManager(itemEntity).batchAddToCollection(batch,
                    collection.getLinkedCollection(), entity, timestampUuid);

        }

        batchExecute(batch, CassandraService.RETRY_COUNT);

        return itemEntity;
    }

    @Override
    @Metered(group="core",name="RelationManager_addToCollections")
    public Entity addToCollections(List owners, String collectionName)
            throws Exception {

        Entity itemEntity = getHeadEntity();

        Map> collectionsByType = new LinkedHashMap>();
        for (EntityRef owner : owners) {
            MapUtils.addMapList(collectionsByType, owner.getType(),
                    owner.getUuid());
        }

        UUID timestampUuid = newTimeUUID();
        Mutator batch = createMutator(
                cass.getApplicationKeyspace(applicationId), be);

        Schema schema = getDefaultSchema();
        for (Entry> entry : collectionsByType.entrySet()) {
            CollectionInfo collection = schema.getCollection(
                    entry.getKey(), collectionName);
            if ((collection != null)
                    && !collection.getType().equals(headEntity.getType())) {
                continue;
            }
            batchAddToCollections(batch, entry.getKey(), entry.getValue(),
                    collectionName, itemEntity, timestampUuid);

            if (collection.getLinkedCollection() != null) {
                logger.error("Bulk add to collections used on a linked collection, linked connection will not be updated");
            }
        }

        batchExecute(batch, CassandraService.RETRY_COUNT);

        return null;
    }

    @Override
    @Metered(group="core",name="RelationManager_createItemInCollection")
    public Entity createItemInCollection(String collectionName,
            String itemType, Map properties) throws Exception {

        if (headEntity.getUuid().equals(applicationId)) {
            if (itemType.equals(TYPE_ENTITY)) {
                itemType = singularize(collectionName);
            }
            if (itemType.equals(TYPE_ROLE)) {
                Long inactivity = (Long)properties.get(PROPERTY_INACTIVITY);
                if (inactivity == null) inactivity = 0L;
                return em.createRole((String)properties.get(PROPERTY_NAME),
                                     (String)properties.get(PROPERTY_TITLE),
                                      inactivity);
            }
            return em.create(itemType, properties);
        } else if (headEntity.getType().equals(Group.ENTITY_TYPE)
                && (collectionName.equals(COLLECTION_ROLES))) {
            UUID groupId = headEntity.getUuid();
            String roleName = (String) properties.get(PROPERTY_NAME);
            return em.createGroupRole(groupId, roleName,
                    (Long) properties.get(PROPERTY_INACTIVITY));
        }

        Entity entity = getHeadEntity();

        Entity itemEntity = null;

        CollectionInfo collection = getDefaultSchema().getCollection(
                entity.getType(), collectionName);
        if ((collection != null) && !collection.getType().equals(itemType)) {
            return null;
        }

        properties = getDefaultSchema().cleanUpdatedProperties(itemType,
                properties, true);

        itemEntity = em.create(itemType, properties);

        if (itemEntity != null) {
            UUID timestampUuid = newTimeUUID();
            Mutator batch = createMutator(
                    cass.getApplicationKeyspace(applicationId), be);

            batchAddToCollection(batch, collectionName, itemEntity,
                    timestampUuid);

            if (collection.getLinkedCollection() != null) {
                getRelationManager(itemEntity)
                        .batchAddToCollection(batch,
                                collection.getLinkedCollection(), entity,
                                timestampUuid);

            }

            batchExecute(batch, CassandraService.RETRY_COUNT);

        }

        return itemEntity;
    }

    @Override
    @Metered(group="core",name="RelationManager_removeFromCollection")
    public void removeFromCollection(String collectionName, EntityRef itemRef)
            throws Exception {

        if (headEntity.getUuid().equals(applicationId)) {
            if (collectionName.equals(COLLECTION_ROLES)) {
                Entity itemEntity = em.get(itemRef);
                if (itemEntity != null) {
                    RoleRef roleRef = SimpleRoleRef.forRoleEntity(itemEntity);
                    em.deleteRole(roleRef.getApplicationRoleName());
                    return;
                }
                em.delete(itemEntity);
                return;
            }
            em.delete(itemRef);
            return;
        }

        Entity entity = getHeadEntity();
        Entity itemEntity = em.get(itemRef);

        if (itemEntity == null) {
            return;
        }

        UUID timestampUuid = newTimeUUID();
        Mutator batch = createMutator(
                cass.getApplicationKeyspace(applicationId), be);

        batchRemoveFromCollection(batch, collectionName, itemEntity,
                timestampUuid);

        CollectionInfo collection = getDefaultSchema().getCollection(
                entity.getType(), collectionName);
        if ((collection != null) && (collection.getLinkedCollection() != null)) {
            getRelationManager(itemEntity).batchRemoveFromCollection(batch,
                    collection.getLinkedCollection(), entity, timestampUuid);
        }

        batchExecute(batch, CassandraService.RETRY_COUNT);

        if (headEntity.getType().equals(Group.ENTITY_TYPE)) {
            if (collectionName.equals(COLLECTION_ROLES)) {
                String path = (String)((DynamicEntity)itemRef).getMetadata("path");
                if (path.startsWith("/roles/")) {
                    RoleRef roleRef = SimpleRoleRef.forRoleEntity(itemEntity);
                    em.deleteRole(roleRef.getApplicationRoleName());
                }
            }
        }
    }

  @Metered(group="core",name="RelationManager_batchRemoveFromContainers")
    public void batchRemoveFromContainers(Mutator m,
            UUID timestampUuid) throws Exception {
        Entity entity = getHeadEntity();
        // find all the containing collections
        Map> containers = getContainingCollections();
        if (containers != null) {
            for (Entry> container : containers
                    .entrySet()) {
                for (String collectionName : container.getValue()) {
                    getRelationManager(container.getKey())
                            .batchRemoveFromCollection(m, collectionName,
                                    entity, true, timestampUuid);
                }
            }
        }
    }

    @Override
    @Metered(group="core",name="RelationManager_copyRelationships")
    public void copyRelationships(String srcRelationName,
            EntityRef dstEntityRef, String dstRelationName) throws Exception {

        headEntity = em.validate(headEntity);
        dstEntityRef = em.validate(dstEntityRef);

        CollectionInfo srcCollection = getDefaultSchema().getCollection(
                headEntity.getType(), srcRelationName);

        CollectionInfo dstCollection = getDefaultSchema().getCollection(
            dstEntityRef.getType(), dstRelationName);

        Results results = null;
        do {
            if (srcCollection != null) {
                results = em.getCollection(headEntity, srcRelationName, null,
                        5000, Level.REFS, false);
            } else {
                results = em.getConnectedEntities(headEntity.getUuid(),
                        srcRelationName, null, Level.REFS);
            }

            if ((results != null) && (results.size() > 0)) {
                List refs = results.getRefs();
                for (EntityRef ref : refs) {
                    if (dstCollection != null) {
                        em.addToCollection(dstEntityRef, dstRelationName, ref);
                    } else {
                        em.createConnection(dstEntityRef, dstRelationName, ref);
                    }
                }
            }
        } while ((results != null) && (results.hasMoreResults()));

    }

    @Override
    @Metered(group="core",name="RelationManager_searchCollection")
    public Results searchCollection(String collectionName, Query query)
            throws Exception {

        if (query == null) {
            query = new Query();
        }

        headEntity = em.validate(headEntity);

        CollectionInfo collection = getDefaultSchema().getCollection(
                headEntity.getType(), collectionName);

        query.setEntityType(collection.getType());

        boolean reversed = query.isReversed();

        // nothing to search for or sort, just grab ids
        if (!query.hasQueryPredicates() && !query.hasSortPredicates()) {
            List ids = query.getUuidIdentifiers();
            if (ids == null) {
                ids = cass.getIdList(
                    cass.getApplicationKeyspace(applicationId),
                    key(headEntity.getUuid(), DICTIONARY_COLLECTIONS,
                            collectionName), query.getStartResult(), null,
                    query.getLimit() + 1, reversed, indexBucketLocator,
                    applicationId, collectionName);
            }

            Results results = Results.fromIdList(ids, collection.getType());

            if (results != null) {
                results.setQuery(query);
            }

            return em.loadEntities(results, query.getResultsLevel(),
                    query.getLimit());
        }

        // we have something to search with, visit our tree and evaluate the
        // results

        QueryProcessor qp = new QueryProcessor(query, collection);
        SearchCollectionVisitor visitor = new SearchCollectionVisitor(query,
                qp, collection);
        qp.getFirstNode().visit(visitor);

        Results results = visitor.getResults();

        if (results == null) {
            return null;
        }

        Map ids = null;
        if (query.getResultsLevel() == Results.Level.LINKED_PROPERTIES) {
            ids = new LinkedHashMap();
            List resultIds = results.getIds();
            for (UUID id : resultIds) {
                ids.put(id, new SimpleCollectionRef(headEntity, collectionName,
                        ref(id)).getUuid());
            }
        }

        results = em.loadEntities(results, query.getResultsLevel(), ids,
                query.getLimit());

        if (results != null) {
            results.setQuery(query);
        }

        if (results.getLevel().ordinal() >= Results.Level.CORE_PROPERTIES
                .ordinal()) {
            List entities = results.getEntities();
            if (entities != null) {
                qp.sort(entities);
            }
        }

        // now we need to set the cursor from our tree evaluation for return
        results.setCursor(qp.getCursor());

        logger.debug("Query cursor: {}", results.getCursor());

        return results;
    }

    @Override
    @Metered(group="core",name="RelationManager_createConnection_connection_ref")
    public ConnectionRef createConnection(ConnectionRef connection)
            throws Exception {
        ConnectionRefImpl connectionImpl = new ConnectionRefImpl(connection);

        updateEntityConnection(false, connectionImpl);

        return connection;
    }

    @Override
    @Metered(group="core",name="RelationManager_createConnection_connectionType")
    public ConnectionRef createConnection(String connectionType,
            EntityRef connectedEntityRef) throws Exception {

        headEntity = em.validate(headEntity);
        connectedEntityRef = em.validate(connectedEntityRef);

        ConnectionRefImpl connection = new ConnectionRefImpl(headEntity,
                connectionType, connectedEntityRef);

        updateEntityConnection(false, connection);

        return connection;
    }

    @Override
    @Metered(group="core",name="RelationManager_createConnection_paired_connection_type")
    public ConnectionRef createConnection(String pairedConnectionType,
            EntityRef pairedEntity, String connectionType,
            EntityRef connectedEntityRef) throws Exception {

        ConnectionRefImpl connection = new ConnectionRefImpl(headEntity,
                new ConnectedEntityRefImpl(pairedConnectionType, pairedEntity),
                new ConnectedEntityRefImpl(connectionType, connectedEntityRef));

        updateEntityConnection(false, connection);

        return connection;
    }

    @Override
    @Metered(group="core",name="RelationManager_createConnection_connected_entity_ref")
    public ConnectionRef createConnection(ConnectedEntityRef... connections)
            throws Exception {

        ConnectionRefImpl connection = new ConnectionRefImpl(headEntity,
                connections);

        updateEntityConnection(false, connection);

        return connection;
    }

    @Override
    @Metered(group="core",name="RelationManager_connectionRef_type_entity")
    public ConnectionRef connectionRef(String connectionType,
            EntityRef connectedEntityRef) throws Exception {

        ConnectionRef connection = new ConnectionRefImpl(headEntity,
                connectionType, connectedEntityRef);

        return connection;
    }

    @Override
    @Metered(group="core",name="RelationManager_connectionRef_entity_to_entity")
    public ConnectionRef connectionRef(String pairedConnectionType,
            EntityRef pairedEntity, String connectionType,
            EntityRef connectedEntityRef) throws Exception {

        ConnectionRef connection = new ConnectionRefImpl(headEntity,
                new ConnectedEntityRefImpl(pairedConnectionType, pairedEntity),
                new ConnectedEntityRefImpl(connectionType, connectedEntityRef));

        return connection;

    }

    @Override
    @Metered(group="core",name="RelationManager_connectionRef_connections")
    public ConnectionRef connectionRef(ConnectedEntityRef... connections) {

        ConnectionRef connection = new ConnectionRefImpl(headEntity,
                connections);

        return connection;

    }

    @Override
    @Metered(group="core",name="RelationManager_deleteConnection")
    public void deleteConnection(ConnectionRef connectionRef) throws Exception {
        updateEntityConnection(true, new ConnectionRefImpl(connectionRef));
    }

    @Override
    @Metered(group="core",name="RelationManager_connectionExists")
    public boolean connectionExists(ConnectionRef connectionRef)
            throws Exception {
        List connections = getConnections(
                new ConnectionRefImpl(connectionRef), true);

        if (connections.size() > 0) {
            // TODO check here, are ghost tombstone rows a problem?
            return true;
        }

        return false;
    }

    @Override
    @Metered(group="core",name="RelationManager_getConnectionTypes_entity_id")
    public Set getConnectionTypes(UUID connectedEntityId)
            throws Exception {
        Set connection_types = new TreeSet(
                CASE_INSENSITIVE_ORDER);

        List connections = getConnections(
                new ConnectionRefImpl(headEntity, new ConnectedEntityRefImpl(
                        NULL_ID), new ConnectedEntityRefImpl(connectedEntityId)),
                false);

        for (ConnectionRefImpl connection : connections) {
            if ((connection.getConnectionType() != null)
                    && (connection.getFirstPairedConnectedEntityId() == null)) {
                connection_types.add(connection.getConnectionType());
            }
        }

        return connection_types;
    }

    @Override
    public Set getConnectionTypes() throws Exception {
        return getConnectionTypes(false);
    }

    @Override
    @Metered(group="core",name="RelationManager_getConnectionTypes")
    public Set getConnectionTypes(boolean filterConnection)
            throws Exception {
        Set connections = cast(em.getDictionaryAsSet(headEntity,
                Schema.DICTIONARY_CONNECTED_TYPES));
        if (connections == null) {
            return null;
        }
        if (filterConnection && (connections.size() > 0)) {
            connections.remove("connection");
        }
        return connections;
    }

    @Override
    @Metered(group="core",name="RelationManager_getConnectedEntities")
    public Results getConnectedEntities(String connectionType,
            String connectedEntityType, Results.Level resultsLevel)
            throws Exception {

        List connections = getConnections(
                new ConnectionRefImpl(headEntity, new ConnectedEntityRefImpl(
                        NULL_ID), new ConnectedEntityRefImpl(connectionType,
                        connectedEntityType, null)), false);

        Results results = Results.fromConnections(connections);
        results = em.loadEntities(results, resultsLevel, 0);
        return results;
    }

    @Override
    @Metered(group="core",name="RelationManager_getConnectingEntities")
    public Results getConnectingEntities(String connectionType,
            String connectedEntityType, Results.Level resultsLevel)
            throws Exception {

        List connections = getConnections(
                new ConnectionRefImpl(ref(),
                        new ConnectedEntityRefImpl(NULL_ID),
                        new ConnectedEntityRefImpl(connectionType, null,
                                headEntity.getUuid())), false);

        Results results = Results.fromConnections(connections, false);
        results = em.loadEntities(results, resultsLevel, 0);
        return results;
    }

    @Override
    public List getConnections(Query query)
            throws Exception {

        return null;
    }

    // @Override
    // public Results searchConnectedEntitiesForProperty(String connectionType,
    // String connectedEntityType, String propertyName,
    // Object searchStartValue, Object searchFinishValue,
    // UUID startResult, int count, boolean reversed, Level resultsLevel)
    // throws Exception {
    //
    // Query query = new Query();
    // query.
    //
    // Results r = searchConnections(new ConnectionRefImpl(headEntity,
    // new ConnectedEntityRefImpl(connectionType, connectedEntityType,
    // null)), propertyName, searchStartValue,
    // searchFinishValue, startResult, null, count + 1, reversed,
    // resultsLevel);
    //
    // return em.loadEntities(r, resultsLevel, count);
    // }

    @Override
    @Metered(group="core",name="RelationManager_searchConnectedEntities")
    public Results searchConnectedEntities(Query query) throws Exception {

        if (query == null) {
            query = new Query();
        }

        String connectedEntityType = query.getEntityType();
        String connectionType = query.getConnectionType();

        headEntity = em.validate(headEntity);

        if (!query.hasQueryPredicates() && !query.hasSortPredicates()) {
            List connections = getConnections(
                    new ConnectionRefImpl(headEntity,
                            new ConnectedEntityRefImpl(NULL_ID),
                            new ConnectedEntityRefImpl(connectionType,
                                    connectedEntityType, null)), false);

            Results results = Results.fromConnections(connections);
            results = em.loadEntities(results, query.getResultsLevel(),
                    query.getLimit());

            return results;

        }

        Results results = null;

        ConnectionRefImpl connectionRef = new ConnectionRefImpl(headEntity,
                new ConnectedEntityRefImpl(connectionType, connectedEntityType,
                        null));

        QueryProcessor qp = new QueryProcessor(query, null);
        SearchConnectionVisitor visitor = new SearchConnectionVisitor(query,
                qp, connectionRef);

        qp.getFirstNode().visit(visitor);

        results = visitor.getResults();

        if (results == null) {
            return null;
        }

        results = em.loadEntities(results, query.getResultsLevel(),
                query.getLimit());

        if (results != null) {
            results.setQuery(query);

        }

        return results;
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    @Metered(group="core",name="RelationManager_searchConnections")
    public List searchConnections(Query query) throws Exception {

        if (query == null) {
            return null;
        }

        headEntity = em.validate(headEntity);

        Results results = null;

        if (!query.hasQueryPredicates()) {
            return null;
        }

        QueryProcessor qp = new QueryProcessor(query, null);

        ConnectionRefImpl connectionRef = new ConnectionRefImpl(headEntity,
                new ConnectedEntityRefImpl(
                        ConnectionRefImpl.CONNECTION_ENTITY_CONNECTION_TYPE,
                        ConnectionRefImpl.CONNECTION_ENTITY_TYPE, null));

        SearchConnectionVisitor visitor = new SearchConnectionVisitor(query,
                qp, connectionRef);

        qp.getFirstNode().visit(visitor);

        results = visitor.getResults();

        // List l = query.getFilterPredicates();
        // int search_count = query.getLimit() + 1;
        //
        // if (l.size() > 1) {
        // search_count = DEFAULT_SEARCH_COUNT;
        // }
        // for (FilterPredicate f : l) {
        // String prop = f.getPropertyName();
        // Object startValue = f.getStartValue();
        // Object finishValue = f.getFinishValue();
        //
        // Results r = searchConnections(
        // new ConnectionRefImpl(
        // headEntity,
        // new ConnectedEntityRefImpl(
        // ConnectionRefImpl.CONNECTION_ENTITY_CONNECTION_TYPE,
        // ConnectionRefImpl.CONNECTION_ENTITY_TYPE,
        // null)), prop, startValue, finishValue,
        // query.getStartResult(), f.getCursor(), search_count,
        // query.isReversed(), Level.IDS);
        //
        // if (results != null) {
        // results.and(r);
        // } else {
        // results = r;
        // }
        // }
        //
        // if (results == null) {
        // return null;
        // }
        //
        // results.trim(query.getLimit());

        return (List) getConnections(results.getIds());

    }

    @Override
    public Set getConnectionIndexes(String connectionType)
            throws Exception {
        return getConnectionIndexes(new ConnectionRefImpl(headEntity,
                connectionType, null));
    }

    @Override
    public Object getAssociatedProperty(
            AssociatedEntityRef associatedEntityRef, String propertyName)
            throws Exception {

        return em.getProperty(associatedEntityRef, propertyName);
    }

    @Override
    public Map getAssociatedProperties(
            AssociatedEntityRef associatedEntityRef) throws Exception {

        return em.getProperties(associatedEntityRef);
    }

    @Override
    public void setAssociatedProperty(AssociatedEntityRef associatedEntityRef,
            String propertyName, Object propertyValue) throws Exception {

        em.setProperty(associatedEntityRef, propertyName, propertyValue);

    }

    /**
     * Simple search visitor that performs all the joining
     *
     * @author tnine
     *
     */
    private class SearchCollectionVisitor extends SearchVisitor {

        private final CollectionInfo collection;

        /**
         * @param query
         * @param collectionName
         */
        public SearchCollectionVisitor(Query query,
                QueryProcessor queryProcessor, CollectionInfo collection) {
            super(query, queryProcessor);
            this.collection = collection;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * org.usergrid.persistence.query.ir.NodeVisitor#visit(org.usergrid.
         * persistence.query.ir.SliceNode)
         */
        @Override
        public void visit(SliceNode node) throws Exception {

            // check if we have sub keys for equality clauses at this node
            // level. If so we can just use them as a row key for faster seek

            Results results = null;

            int limit = query.getLimit() + 1;

            Level resultsLevel = query.getResultsLevel();

            Object subKey = getCFKeyForSubkey(collection, node);

            for (QuerySlice slice : node.getAllSlices()) {

                // NOTE we explicitly do not append the slice value here. This
                // is done in the searchIndex method below
                Object indexKey = subKey == null ? key(headEntity.getUuid(),
                        collection.getName()) : key(headEntity.getUuid(),
                        collection.getName(), subKey);

                // update the cursor and order before we perform the slice
                // operation. Should be done after subkeying since this can
                // change the hash value of the slice
                queryProcessor.applyCursorAndSort(slice);

                List> columns = null;

                //nothing left to search for this range
                if (slice.isComplete()) {
                    columns = new ArrayList>();
                }
                //perform the search
                else {
                    columns = searchIndexBuckets(indexKey, slice, limit,
                            collection.getName());
                }

                Results r = getIndexResults(columns, true,
                        query.getConnectionType(), collection.getType(),
                        resultsLevel);

                if (r.size() > query.getLimit()) {
                    r.setCursorToLastResult();
                }

                // we've hit the last element in the index in a page. I.E 40
                // elements at 10 page size 4 times. Our cursor needs
                // to be max so we don't get any results in the next page call
                else {
                    r.setCursorMax();
                }

                queryProcessor.updateCursor(slice, r.getCursor());

                r = r.excludeCursorMetadataAttribute();

                if (results != null) {
                    results.and(r);
                } else {
                    results = r;
                }
            }

            this.results.push(results);

        }

      public void visit(AllNode node) throws Exception {

        String collectionName = collection.getName();

        List ids = cass.getIdList(
                cass.getApplicationKeyspace(applicationId),
                key(headEntity.getUuid(), DICTIONARY_COLLECTIONS, collectionName),
                query.getStartResult(),
                null,
                query.getLimit() + 1,
                query.isReversed(),
                indexBucketLocator,
                applicationId,
                collectionName);

        Results results = Results.fromIdList(ids, collection.getType());

        this.results.push(results);
      }

      /*
         * (non-Javadoc)
         *
         * @see
         * org.usergrid.persistence.query.ir.NodeVisitor#visit(org.usergrid.
         * persistence.query.ir.WithinNode)
         */
        @Override
        public void visit(WithinNode node) throws Exception {

            Results r = em.getGeoIndexManager().proximitySearchCollection(
                    getHeadEntity(), collection.getName(),
                    node.getPropertyName(),
                    new Point(node.getLattitude(), node.getLongitude()),
                    node.getDistance(), null, query.getLimit(), false,
                    query.getResultsLevel());

            results.push(r);
        }

    }

    /**
     * Simple search visitor that performs all the joining
     *
     * @author tnine
     *
     */
    private class SearchConnectionVisitor extends SearchVisitor {

        private final ConnectionRefImpl connection;

        /**
         * @param query
         * @param collectionName
         */
        public SearchConnectionVisitor(Query query,
                QueryProcessor queryProcessor, ConnectionRefImpl connection) {
            super(query, queryProcessor);
            this.connection = connection;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * org.usergrid.persistence.query.ir.NodeVisitor#visit(org.usergrid.
         * persistence.query.ir.SliceNode)
         */
        @Override
        public void visit(SliceNode node) throws Exception {

            Results results = null;

            int limit = query.getLimit() + 1;

            Level resultsLevel = query.getResultsLevel();

            for (QuerySlice slice : node.getAllSlices()) {

                // update the cursor and order before we perform the slice
                // operation
                queryProcessor.applyCursorAndSort(slice);

                List> columns = searchIndex(
                        key(connection.getIndexId(), INDEX_CONNECTIONS), slice,
                        limit);

                Results r = getIndexResults(columns, true,
                        connection.getConnectionType(),
                        connection.getConnectedEntityType(), resultsLevel);

                if (r.size() > query.getLimit()) {
                    r.setCursorToLastResult();
                }

                if (r.getCursor() != null) {
                    // TODO Todd finish this
                    // queryProcessor.updateCursor(slice, r.getCursor());
                }

                r = r.excludeCursorMetadataAttribute();

                if (results != null) {
                    results.and(r);
                } else {
                    results = r;
                }
            }
            //
            // String prop = f.getPropertyName();
            // Object startValue = f.getStartValue();
            // Object finishValue = f.getFinishValue();
            //
            // Results r = searchConnections(
            // new ConnectionRefImpl(
            // headEntity,
            // new ConnectedEntityRefImpl(
            // ConnectionRefImpl.CONNECTION_ENTITY_CONNECTION_TYPE,
            // ConnectionRefImpl.CONNECTION_ENTITY_TYPE,
            // null)), prop, startValue, finishValue,
            // query.getStartResult(), f.getCursor(), search_count,
            // query.isReversed(), Level.IDS);
            //
            // if (results != null) {
            // results.and(r);
            // } else {
            // results = r;
            // }
            // }

            this.results.push(results);

        }

        /*
         * (non-Javadoc)
         *
         * @see
         * org.usergrid.persistence.query.ir.NodeVisitor#visit(org.usergrid.
         * persistence.query.ir.WithinNode)
         */
        @Override
        public void visit(WithinNode node) throws Exception {

            Results r = em.getGeoIndexManager().proximitySearchConnections(
                    connection.getIndexId(), node.getPropertyName(),
                    new Point(node.getLattitude(), node.getLongitude()),
                    node.getDistance(), null, query.getLimit(), false,
                    query.getResultsLevel());

            results.push(r);
        }

      @Override
      public void visit(AllNode node) throws Exception {
        // todo: What do I do here?
      }
    }

}