org.apache.usergrid.persistence.Schema.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.usergrid.persistence.Schema.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.usergrid.persistence;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Iterator;
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.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.node.ObjectNode;
import org.codehaus.jackson.smile.SmileFactory;
import org.codehaus.jackson.type.TypeReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.apache.usergrid.persistence.annotations.EntityCollection;
import org.apache.usergrid.persistence.annotations.EntityDictionary;
import org.apache.usergrid.persistence.annotations.EntityProperty;
import org.apache.usergrid.persistence.cassandra.CassandraPersistenceUtils;
import org.apache.usergrid.persistence.entities.Application;
import org.apache.usergrid.persistence.exceptions.PropertyTypeConversionException;
import org.apache.usergrid.persistence.schema.CollectionInfo;
import org.apache.usergrid.persistence.schema.DictionaryInfo;
import org.apache.usergrid.persistence.schema.EntityInfo;
import org.apache.usergrid.persistence.schema.PropertyInfo;
import org.apache.usergrid.utils.InflectionUtils;
import org.apache.usergrid.utils.JsonUtils;
import org.apache.usergrid.utils.MapUtils;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.reflect.FieldUtils;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.Row;

import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.usergrid.utils.ConversionUtils.bytebuffer;
import static org.apache.usergrid.utils.ConversionUtils.string;
import static org.apache.usergrid.utils.ConversionUtils.uuid;
import static org.apache.usergrid.utils.InflectionUtils.pluralize;
import static org.apache.usergrid.utils.InflectionUtils.singularize;
import static org.apache.usergrid.utils.JsonUtils.toJsonNode;
import static org.apache.usergrid.utils.MapUtils.hashMap;
import static org.apache.usergrid.utils.StringUtils.stringOrSubstringAfterLast;

/**
 * The controller class for determining Entity relationships as well as properties types. This class loads the entity
 * schema definition from a YAML file called usergrid-schema.yaml at the root of the classpath.
 *
 * @author edanuff
 */
public class Schema {

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

    public static final String DEFAULT_ENTITIES_PACKAGE = "org.apache.usergrid.persistence.entities";

    public static final String TYPE_APPLICATION = "application";
    public static final String TYPE_ENTITY = "entity";
    public static final String TYPE_ROLE = "role";
    public static final String TYPE_CONNECTION = "connection";
    public static final String TYPE_MEMBER = "member";

    public static final String PROPERTY_ACTIVATED = "activated";
    public static final String PROPERTY_COLLECTION_NAME = "collectionName";
    public static final String PROPERTY_CREATED = "created";
    public static final String PROPERTY_CONFIRMED = "confirmed";
    public static final String PROPERTY_DISABLED = "disabled";
    public static final String PROPERTY_UUID = "uuid";
    public static final String PROPERTY_EMAIL = "email";
    public static final String PROPERTY_ITEM = "item";
    public static final String PROPERTY_ITEM_TYPE = "itemType";
    public static final String PROPERTY_MEMBERSHIP = "membership";
    public static final String PROPERTY_METADATA = "metadata";
    public static final String PROPERTY_MODIFIED = "modified";
    public static final String PROPERTY_NAME = "name";
    public static final String PROPERTY_OWNER = "owner";
    public static final String PROPERTY_OWNER_TYPE = "ownerType";
    public static final String PROPERTY_PATH = "path";
    public static final String PROPERTY_PICTURE = "picture";
    public static final String PROPERTY_PUBLISHED = "published";
    public static final String PROPERTY_SECRET = "secret";
    public static final String PROPERTY_TIMESTAMP = "timestamp";
    public static final String PROPERTY_TITLE = "title";
    public static final String PROPERTY_TYPE = "type";
    public static final String PROPERTY_URI = "uri";
    public static final String PROPERTY_USERNAME = "username";
    public static final String PROPERTY_INACTIVITY = "inactivity";

    public static final String PROPERTY_CONNECTION = "connection";
    public static final String PROPERTY_ASSOCIATED = "associated";
    public static final String PROPERTY_CURSOR = "cursor";

    public static final String COLLECTION_ROLES = "roles";
    public static final String COLLECTION_USERS = "users";
    public static final String COLLECTION_GROUPS = "groups";

    public static final String INDEX_COLLECTIONS = "collections";
    public static final String INDEX_CONNECTIONS = "connections";

    public static final String DICTIONARY_PROPERTIES = "properties";
    public static final String DICTIONARY_SETS = "sets";
    public static final String DICTIONARY_COLLECTIONS = "collections";
    public static final String DICTIONARY_CONNECTIONS = "connections";
    public static final String DICTIONARY_INDEXES = "indexes";
    public static final String DICTIONARY_CONNECTING_TYPES = "connecting_types";
    public static final String DICTIONARY_CONNECTING_ENTITIES = "connecting_entities";
    public static final String DICTIONARY_CONNECTED_TYPES = "connected_types";
    public static final String DICTIONARY_CONNECTED_ENTITIES = "connected_entities";
    public static final String DICTIONARY_CONTAINER_ENTITIES = "container_entities";
    public static final String DICTIONARY_CREDENTIALS = "credentials";
    public static final String DICTIONARY_ROLENAMES = "rolenames";
    public static final String DICTIONARY_ROLETIMES = "roletimes";
    public static final String DICTIONARY_PERMISSIONS = "permissions";
    public static final String DICTIONARY_ID_SETS = "id_sets";
    public static final String DICTIONARY_COUNTERS = "counters";
    public static final String DICTIONARY_GEOCELL = "geocell";

    private static final List<String> entitiesPackage = new ArrayList<String>();
    private static final List<String> entitiesScanPath = new ArrayList<String>();

    @SuppressWarnings("rawtypes")
    public static Map<String, Class> DEFAULT_DICTIONARIES = hashMap(DICTIONARY_PROPERTIES, (Class) String.class)
            .map(DICTIONARY_SETS, String.class).map(DICTIONARY_INDEXES, String.class)
            .map(DICTIONARY_COLLECTIONS, String.class).map(DICTIONARY_CONNECTIONS, String.class)
            .map(DICTIONARY_CONNECTING_TYPES, String.class).map(DICTIONARY_CONNECTING_ENTITIES, String.class)
            .map(DICTIONARY_CONNECTED_TYPES, String.class).map(DICTIONARY_CONNECTED_ENTITIES, String.class)
            .map(DICTIONARY_CONTAINER_ENTITIES, String.class).map(DICTIONARY_CREDENTIALS, CredentialsInfo.class)
            .map(DICTIONARY_ROLENAMES, String.class).map(DICTIONARY_ROLETIMES, Long.class)
            .map(DICTIONARY_PERMISSIONS, String.class).map(DICTIONARY_ID_SETS, String.class);

    private static LoadingCache<String, String> baseEntityTypes = CacheBuilder.newBuilder()
            .expireAfterAccess(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
                public String load(String key) { // no checked exception
                    return createNormalizedEntityType(key, true);
                }
            });

    private static LoadingCache<String, String> nonbaseEntityTypes = CacheBuilder.newBuilder()
            .expireAfterAccess(10, TimeUnit.MINUTES).build(new CacheLoader<String, String>() {
                public String load(String key) { // no checked exception
                    return createNormalizedEntityType(key, false);
                }
            });

    private static LoadingCache<String, String> collectionNameCache = CacheBuilder.newBuilder().maximumSize(1000)
            .build(new CacheLoader<String, String>() {
                public String load(String key) { // no checked exception
                    return _defaultCollectionName(key);
                }
            });

    private final ObjectMapper mapper = new ObjectMapper();

    @SuppressWarnings("unused")
    private final SmileFactory smile = new SmileFactory();

    private final Map<String, Class<? extends Entity>> typeToEntityClass = new ConcurrentHashMap<String, Class<? extends Entity>>();

    private final Map<Class<? extends Entity>, String> entityClassToType = new ConcurrentHashMap<Class<? extends Entity>, String>();

    private final Map<Class<? extends Entity>, Map<String, PropertyDescriptor>> entityClassPropertyToDescriptor = new ConcurrentHashMap<Class<? extends Entity>, Map<String, PropertyDescriptor>>();

    private final Map<Class<? extends Entity>, EntityInfo> registeredEntityClasses = new ConcurrentHashMap<Class<? extends Entity>, EntityInfo>();

    Map<String, EntityInfo> entityMap = new TreeMap<String, EntityInfo>(String.CASE_INSENSITIVE_ORDER);

    Map<String, Map<String, Set<CollectionInfo>>> entityContainerCollections = new TreeMap<String, Map<String, Set<CollectionInfo>>>(
            String.CASE_INSENSITIVE_ORDER);

    Map<String, Map<String, Set<CollectionInfo>>> entityContainerCollectionsIndexingProperties = new TreeMap<String, Map<String, Set<CollectionInfo>>>(
            String.CASE_INSENSITIVE_ORDER);
    Map<String, Map<String, Set<CollectionInfo>>> entityContainerCollectionsIndexingDictionaries = new TreeMap<String, Map<String, Set<CollectionInfo>>>(
            String.CASE_INSENSITIVE_ORDER);
    Map<String, Map<String, Set<CollectionInfo>>> entityContainerCollectionsIndexingDynamicDictionaries = new TreeMap<String, Map<String, Set<CollectionInfo>>>(
            String.CASE_INSENSITIVE_ORDER);

    Map<String, Map<String, Map<String, Set<CollectionInfo>>>> entityPropertyContainerCollectionsIndexingProperty = new TreeMap<String, Map<String, Map<String, Set<CollectionInfo>>>>(
            String.CASE_INSENSITIVE_ORDER);

    Map<String, Map<String, Map<String, Set<CollectionInfo>>>> entityDictionaryContainerCollectionsIndexingDictionary = new TreeMap<String, Map<String, Map<String, Set<CollectionInfo>>>>(
            String.CASE_INSENSITIVE_ORDER);

    Map<String, PropertyInfo> allIndexedProperties = new TreeMap<String, PropertyInfo>(
            String.CASE_INSENSITIVE_ORDER);

    Map<String, PropertyInfo> allProperties = new TreeMap<String, PropertyInfo>(String.CASE_INSENSITIVE_ORDER);

    private static Schema instance;

    boolean initialized = false;

    public Schema() {
        setDefaultSchema(this);

        mapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    public static final Object initLock = new Object();

    public static void setDefaultSchema(Schema instance) {
        synchronized (initLock) {
            if (Schema.instance == null) {
                Schema.instance = instance;
            }
        }
    }

    public static Schema getDefaultSchema() {
        if (instance == null) {
            synchronized (initLock) {
                if (instance == null) {
                    logger.info("Initializing schema...");
                    instance = new Schema();
                    instance.init();
                    logger.info("Schema initialized");
                }
            }
        }
        return instance;
    }

    public void mapCollector(String entityType, String containerType, String collectionName,
            CollectionInfo collection) {

        MapUtils.addMapMapSet(entityContainerCollections, true, entityType, containerType, collection);

        if (!collection.getPropertiesIndexed().isEmpty()) {
            MapUtils.addMapMapSet(entityContainerCollectionsIndexingProperties, true, entityType, containerType,
                    collection);
            for (String propertyName : collection.getPropertiesIndexed()) {
                MapUtils.addMapMapMapSet(entityPropertyContainerCollectionsIndexingProperty, true, entityType,
                        propertyName, containerType, collection);
            }
        }

        if (!collection.getDictionariesIndexed().isEmpty()) {
            MapUtils.addMapMapSet(entityContainerCollectionsIndexingDictionaries, true, entityType, containerType,
                    collection);
            for (String dictionaryName : collection.getDictionariesIndexed()) {
                MapUtils.addMapMapMapSet(entityDictionaryContainerCollectionsIndexingDictionary, true, entityType,
                        dictionaryName, containerType, collection);
            }
        }

        if (collection.isIndexingDynamicDictionaries()) {
            MapUtils.addMapMapSet(entityContainerCollectionsIndexingDynamicDictionaries, true, entityType,
                    containerType, collection);
        }
    }

    private <T extends Annotation> T getAnnotation(Class<? extends Entity> entityClass,
            PropertyDescriptor descriptor, Class<T> annotationClass) {
        try {
            if ((descriptor.getReadMethod() != null)
                    && descriptor.getReadMethod().isAnnotationPresent(annotationClass)) {
                return descriptor.getReadMethod().getAnnotation(annotationClass);
            }
            if ((descriptor.getWriteMethod() != null)
                    && descriptor.getWriteMethod().isAnnotationPresent(annotationClass)) {
                return descriptor.getWriteMethod().getAnnotation(annotationClass);
            }
            Field field = FieldUtils.getField(entityClass, descriptor.getName(), true);
            if (field != null) {
                if (field.isAnnotationPresent(annotationClass)) {
                    return field.getAnnotation(annotationClass);
                }
            }
        } catch (Exception e) {
            logger.error("Could not retrieve the annotations", e);
        }
        return null;
    }

    public synchronized void registerEntity(Class<? extends Entity> entityClass) {
        logger.info("Registering {}", entityClass);
        EntityInfo e = registeredEntityClasses.get(entityClass);
        if (e != null) {
            return;
        }

        Map<String, PropertyDescriptor> propertyDescriptors = entityClassPropertyToDescriptor.get(entityClass);
        if (propertyDescriptors == null) {
            EntityInfo entity = new EntityInfo();

            String type = getEntityType(entityClass);

            propertyDescriptors = new LinkedHashMap<String, PropertyDescriptor>();
            Map<String, PropertyInfo> properties = new TreeMap<String, PropertyInfo>(String.CASE_INSENSITIVE_ORDER);
            Map<String, CollectionInfo> collections = new TreeMap<String, CollectionInfo>(
                    String.CASE_INSENSITIVE_ORDER);
            Map<String, DictionaryInfo> sets = new TreeMap<String, DictionaryInfo>(String.CASE_INSENSITIVE_ORDER);

            PropertyDescriptor[] descriptors = PropertyUtils.getPropertyDescriptors(entityClass);

            for (PropertyDescriptor descriptor : descriptors) {
                String name = descriptor.getName();

                EntityProperty propertyAnnotation = getAnnotation(entityClass, descriptor, EntityProperty.class);
                if (propertyAnnotation != null) {
                    if (isNotBlank(propertyAnnotation.name())) {
                        name = propertyAnnotation.name();
                    }
                    propertyDescriptors.put(name, descriptor);

                    PropertyInfo propertyInfo = new PropertyInfo(propertyAnnotation);
                    propertyInfo.setName(name);
                    propertyInfo.setType(descriptor.getPropertyType());

                    properties.put(name, propertyInfo);
                    // logger.info(propertyInfo);
                }

                EntityCollection collectionAnnotation = getAnnotation(entityClass, descriptor,
                        EntityCollection.class);
                if (collectionAnnotation != null) {
                    CollectionInfo collectionInfo = new CollectionInfo(collectionAnnotation);
                    collectionInfo.setName(name);
                    collectionInfo.setContainer(entity);

                    collections.put(name, collectionInfo);
                    // logger.info(collectionInfo);
                }

                EntityDictionary setAnnotation = getAnnotation(entityClass, descriptor, EntityDictionary.class);
                if (setAnnotation != null) {
                    DictionaryInfo setInfo = new DictionaryInfo(setAnnotation);
                    setInfo.setName(name);
                    // setInfo.setType(descriptor.getPropertyType());
                    sets.put(name, setInfo);
                    // logger.info(setInfo);
                }
            }

            if (!DynamicEntity.class.isAssignableFrom(entityClass)) {
                entity.setProperties(properties);
                entity.setCollections(collections);
                entity.setDictionaries(sets);
                entity.mapCollectors(this, type);

                entityMap.put(type, entity);

                allProperties.putAll(entity.getProperties());

                Set<String> propertyNames = entity.getIndexedProperties();
                for (String propertyName : propertyNames) {
                    PropertyInfo property = entity.getProperty(propertyName);
                    if ((property != null) && !allIndexedProperties.containsKey(propertyName)) {
                        allIndexedProperties.put(propertyName, property);
                    }
                }
            }

            entityClassPropertyToDescriptor.put(entityClass, propertyDescriptors);

            registeredEntityClasses.put(entityClass, entity);
        }
    }

    public synchronized void init() {
        if (!initialized) {
            initialized = true;
            addEntitiesPackage(DEFAULT_ENTITIES_PACKAGE);
            scanEntities();
        }
    }

    @SuppressWarnings("unchecked")
    public void scanEntities() {
        synchronized (entitiesScanPath) {
            for (String path : entitiesScanPath) {
                ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
                        true);
                provider.addIncludeFilter(new AssignableTypeFilter(TypedEntity.class));

                Set<BeanDefinition> components = provider.findCandidateComponents(path);
                for (BeanDefinition component : components) {
                    try {
                        Class<?> cls = Class.forName(component.getBeanClassName());
                        if (Entity.class.isAssignableFrom(cls)) {
                            registerEntity((Class<? extends Entity>) cls);
                        }
                    } catch (ClassNotFoundException e) {
                        logger.error("Unable to get entity class ", e);
                    }
                }
                registerEntity(DynamicEntity.class);
            }
        }
    }

    public void addEntitiesPackage(String entityPackage) {
        if (!entitiesPackage.contains(entityPackage)) {
            entitiesPackage.add(entityPackage);
            String path = entityPackage.replaceAll("\\.", "/");
            synchronized (entitiesScanPath) {
                entitiesScanPath.add(path);
            }
        }
    }

    public void removeEntitiesPackage(String entityPackage) {
        entitiesPackage.remove(entityPackage);
        String path = entityPackage.replaceAll("\\.", "/");
        synchronized (entitiesScanPath) {
            entitiesScanPath.remove(path);
        }
    }

    @SuppressWarnings("unchecked")
    public List<String> getEntitiesPackage() {
        return (List<String>) ((ArrayList<String>) entitiesPackage).clone();
    }

    /** @return value */
    public Map<String, PropertyInfo> getAllIndexedProperties() {

        return allIndexedProperties;
    }

    public Set<String> getAllIndexedPropertyNames() {

        return allIndexedProperties.keySet();
    }

    public Set<String> getAllPropertyNames() {

        return allProperties.keySet();
    }

    public String[] getAllPropertyNamesAsArray() {

        Set<String> strings = allProperties.keySet();
        return strings.toArray(new String[strings.size()]);
    }

    /** @return value */
    public EntityInfo getEntityInfo(String entityType) {

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

        entityType = normalizeEntityType(entityType);

        if ("dynamicentity".equalsIgnoreCase(entityType)) {
            throw new IllegalArgumentException(entityType + " is not a valid entity type");
        }

        EntityInfo entity = entityMap.get(entityType);
        if (entity == null) {
            return getDynamicEntityInfo(entityType);
        }
        return entity;
    }

    public JsonNode getEntityJsonSchema(String entityType) {
        Class<?> cls = getEntityClass(entityType);
        if (cls == null) {
            cls = DynamicEntity.class;
        }
        try {
            JsonNode schemaNode = mapper.generateJsonSchema(cls).getSchemaNode();
            if (schemaNode != null) {
                JsonNode properties = schemaNode.get("properties");
                if (properties instanceof ObjectNode) {
                    Set<String> fieldsToRemove = new LinkedHashSet<String>();
                    Iterator<String> i = properties.getFieldNames();
                    while (i.hasNext()) {
                        String propertyName = i.next();
                        if (!hasProperty(entityType, propertyName)) {
                            fieldsToRemove.add(propertyName);
                        } else {
                            ObjectNode property = (ObjectNode) properties.get(propertyName);
                            if (isRequiredProperty(entityType, propertyName)) {
                                property.put("optional", false);
                            }
                        }
                    }
                    ((ObjectNode) properties).remove(fieldsToRemove);
                }
            }
            return schemaNode;
        } catch (Exception e) {
            logger.error("Unable to get schema for entity type " + entityType, e);
        }
        return null;
    }

    public String getEntityType(Class<? extends Entity> cls) {
        String type = entityClassToType.get(cls);
        if (type != null) {
            return type;
        }

        String className = cls.getName();
        boolean finded = false;
        for (String entityPackage : entitiesPackage) {
            String entityPackagePrefix = entityPackage + ".";
            if (className.startsWith(entityPackagePrefix)) {
                type = className.substring(entityPackagePrefix.length());
                type = InflectionUtils.underscore(type);
                finded = true;
            }
        }

        if (!finded) {
            type = className;
        }

        typeToEntityClass.put(type, cls);
        entityClassToType.put(cls, type);
        return type;
    }

    @SuppressWarnings("unchecked")
    private Class<? extends Entity> entityClassForName(String className) {
        try {
            @SuppressWarnings("rawtypes")
            Class cls = Class.forName(className);
            if (Entity.class.isAssignableFrom(cls)) {
                return cls;
            }
        } catch (ClassNotFoundException e) {
        }
        return null;
    }

    public Class<? extends Entity> getEntityClass(String type) {
        type = getAssociatedEntityType(type);
        Class<? extends Entity> cls = typeToEntityClass.get(type);
        if (cls != null) {
            return cls;
        }

        for (String entityPackage : entitiesPackage) {
            String entityPackagePrefix = entityPackage + ".";
            cls = entityClassForName(entityPackagePrefix + InflectionUtils.camelCase(type, true));
            if (cls == null) {
                cls = entityClassForName(entityPackagePrefix + type);
            }

            if (cls == null) {
                cls = entityClassForName(type);
            }

            if (cls != null) {
                break;
            }
        }

        if (cls == null) {
            cls = DynamicEntity.class;
        }

        typeToEntityClass.put(type, cls);
        entityClassToType.put(cls, type);
        return cls;
    }

    /** @return value */
    public boolean hasProperties(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.hasProperties();

    }

    /** @return value */
    public Set<String> getPropertyNames(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        return entity.getProperties().keySet();
    }

    /** @return value */
    public String[] getPropertyNamesAsArray(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return new String[0];
        }

        Set<String> strings = entity.getProperties().keySet();
        return strings.toArray(new String[strings.size()]);
    }

    /** @return value */
    public boolean hasProperty(String entityType, String propertyName) {

        if (propertyName.equals(PROPERTY_UUID) || propertyName.equals(PROPERTY_TYPE)) {
            return true;
        }

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.hasProperty(propertyName);

    }

    public String aliasProperty(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        return entity.getAliasProperty();
    }

    /** @return value */
    public boolean isPropertyMutable(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.isPropertyMutable(propertyName);

    }

    public boolean isPropertyUnique(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.isPropertyUnique(propertyName);

    }

    public boolean isPropertyIndexed(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity == null || !entity.hasProperty(propertyName) || entity.isPropertyIndexed(propertyName);

    }

    public boolean isPropertyFulltextIndexed(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity == null || !entity.hasProperty(propertyName)
                || entity.isPropertyFulltextIndexed(propertyName);

    }

    public boolean isPropertyTimestamp(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.isPropertyTimestamp(propertyName);

    }

    /** @return value */
    public Set<String> getRequiredProperties(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        return entity.getRequiredProperties();
    }

    /** @return value */
    public boolean isRequiredProperty(String entityType, String propertyName) {

        if (propertyName.equals(PROPERTY_UUID) || propertyName.equals(PROPERTY_TYPE)) {
            return true;
        }

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.isPropertyRequired(propertyName);

    }

    /** @return value */
    public Class<?> getPropertyType(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        PropertyInfo property = entity.getProperty(propertyName);
        if (property == null) {
            return null;
        }

        return property.getType();
    }

    /** @return value */
    public boolean isPropertyIndexedInCollection(String containerType, String collectionName, String propertyName) {

        CollectionInfo collection = getCollection(containerType, collectionName);
        return collection != null && collection.isPropertyIndexed(propertyName);

    }

    /** @return value */
    public boolean hasDictionaries(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.hasDictionaries();

    }

    /** @return value */
    public Set<String> getDictionaryNames(String entityType) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        return entity.getDictionaries().keySet();
    }

    /** @return value */
    public boolean hasDictionary(String entityType, String dictionaryName) {

        EntityInfo entity = getEntityInfo(entityType);
        return entity != null && entity.hasDictionary(dictionaryName);

    }

    /** @return value */
    public Class<?> getDictionaryKeyType(String entityType, String dictionaryName) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        DictionaryInfo set = entity.getDictionary(dictionaryName);
        if (set == null) {
            return null;
        }

        return set.getKeyType();
    }

    public Class<?> getDictionaryValueType(String entityType, String dictionaryName) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return null;
        }

        DictionaryInfo dictionary = entity.getDictionary(dictionaryName);
        if (dictionary == null) {
            return null;
        }

        return dictionary.getValueType();
    }

    /** @return value */
    public boolean isDictionaryIndexedInConnections(String entityType, String dictionaryName) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return false;
        }

        DictionaryInfo dictionary = entity.getDictionary(dictionaryName);
        return dictionary != null && dictionary.isKeysIndexedInConnections();

    }

    /** @return value */
    public boolean isDictionaryIndexedInCollection(String containerType, String collectionName,
            String dictionaryName) {

        CollectionInfo collection = getCollection(containerType, collectionName);
        return collection != null && collection.isDictionaryIndexed(dictionaryName);

    }

    /** @return value */
    public boolean hasCollection(String containerType, String collectionName) {

        return getCollection(containerType, collectionName) != null;
    }

    public boolean isCollectionPathBased(String containerType, String collectionName) {

        CollectionInfo collection = getCollection(containerType, collectionName);
        if (collection == null) {
            return false;
        }

        EntityInfo item = getEntityInfo(collection.getType());
        if (item == null) {
            return false;
        }

        PropertyInfo property = item.getAliasPropertyObject();
        return property != null && property.isPathBasedName();

    }

    public boolean isCollectionReversed(String containerType, String collectionName) {

        CollectionInfo collection = getCollection(containerType, collectionName);
        return collection != null && collection.isReversed();

    }

    public String getCollectionSort(String containerType, String collectionName) {

        CollectionInfo collection = getCollection(containerType, collectionName);
        if (collection == null) {
            return null;
        }

        return collection.getSort();
    }

    /** @return value */
    public CollectionInfo getCollection(String containerType, String collectionName) {

        containerType = normalizeEntityType(containerType, true);

        EntityInfo entity = getEntityInfo(containerType);
        if (entity == null) {
            return null;
        }

        CollectionInfo collection = entity.getCollection(collectionName);

        if ((collection == null) && (Application.ENTITY_TYPE.equalsIgnoreCase(containerType))) {
            collection = getDynamicApplicationCollection(collectionName);
        }

        return collection;
    }

    private CollectionInfo getDynamicApplicationCollection(String collectionName) {
        EntityInfo entity = getEntityInfo(Application.ENTITY_TYPE);
        if (entity == null) {
            return null;
        }

        CollectionInfo collection = entity.getCollection(collectionName);

        if (collection != null) {
            return collection;
        }

        collection = new CollectionInfo();
        collection.setName(collectionName);
        collection.setContainer(entity);
        collection.setType(normalizeEntityType(collectionName));
        Set<String> properties = new LinkedHashSet<String>();
        properties.add(PROPERTY_NAME);
        properties.add(PROPERTY_CREATED);
        properties.add(PROPERTY_MODIFIED);
        collection.setPropertiesIndexed(properties);
        // entity.getCollections().put(collectionName, collection);
        // mapCollector(collection.getType(), Application.ENTITY_TYPE,
        // collectionName, collection);

        return collection;
    }

    public String getCollectionType(String containerType, String collectionName) {

        containerType = normalizeEntityType(containerType);

        CollectionInfo collection = getCollection(containerType, collectionName);

        if (collection == null) {

            if (Application.ENTITY_TYPE.equalsIgnoreCase(containerType)) {
                return normalizeEntityType(collectionName);
            }
            return null;
        }

        return collection.getType();
    }

    /** @return value */
    public Map<String, CollectionInfo> getCollections(String entityType) {

        EntityInfo entity = getEntityInfo(normalizeEntityType(entityType, true));
        if (entity == null) {
            return null;
        }

        return entity.getCollections();
    }

    public Set<String> getCollectionNames(String entityType) {

        EntityInfo entity = getEntityInfo(normalizeEntityType(entityType, true));
        if (entity == null) {
            return null;
        }

        Map<String, CollectionInfo> map = entity.getCollections();

        if (map != null) {
            return map.keySet();
        }

        return null;
    }

    public java.util.List<String> getCollectionNamesAsList(String entityType) {
        Set<String> set = getCollectionNames(normalizeEntityType(entityType, true));
        if (set != null) {
            return new ArrayList<String>(set);
        }
        return null;
    }

    private Map<String, Set<CollectionInfo>> addDynamicApplicationCollectionAsContainer(
            Map<String, Set<CollectionInfo>> containers, String entityType) {

        Map<String, Set<CollectionInfo>> copy = new TreeMap<String, Set<CollectionInfo>>(
                String.CASE_INSENSITIVE_ORDER);
        if (containers != null) {
            copy.putAll(containers);
        }
        containers = copy;

        if (!containers.containsKey(Application.ENTITY_TYPE)) {
            MapUtils.addMapSet(containers, true, Application.ENTITY_TYPE,
                    getCollection(Application.ENTITY_TYPE, defaultCollectionName(entityType)));
        }

        return containers;
    }

    /** @return value */
    public Map<String, Set<CollectionInfo>> getContainers(String entityType) {

        entityType = normalizeEntityType(entityType);

        // Add the application as a container to all entities
        return addDynamicApplicationCollectionAsContainer(entityContainerCollections.get(entityType), entityType);
    }

    /** @return value */
    public CollectionInfo getContainerCollectionLinkedToCollection(String containerType, String collectionName) {

        CollectionInfo collection = getCollection(containerType, collectionName);
        if (collection == null) {
            return null;
        }

        String linkedCollection = collection.getLinkedCollection();
        if (linkedCollection == null) {
            return null;
        }

        return getCollection(collection.getType(), linkedCollection);
    }

    /** @return value */
    public Map<String, Set<CollectionInfo>> getContainersIndexingProperties(String entityType) {

        entityType = normalizeEntityType(entityType);

        // Add the application as a container indexing some properties by
        // default
        return addDynamicApplicationCollectionAsContainer(
                entityContainerCollectionsIndexingProperties.get(entityType), entityType);
    }

    /** @return value */
    public Map<String, Set<CollectionInfo>> getContainersIndexingDictionaries(String entityType) {

        entityType = normalizeEntityType(entityType);

        // Application does index any sets by default
        return entityContainerCollectionsIndexingDictionaries.get(entityType);
    }

    /** @return value */
    public Map<String, Set<CollectionInfo>> getContainersIndexingDynamicSetInfos(String entityType) {

        entityType = normalizeEntityType(entityType);

        // Application does index dynamic sets by default
        return entityContainerCollectionsIndexingDynamicDictionaries.get(entityType);
    }

    /** @return value */
    public Map<String, Set<CollectionInfo>> getContainersIndexingProperty(String entityType, String propertyName) {

        entityType = normalizeEntityType(entityType);

        Map<String, Map<String, Set<CollectionInfo>>> propertyContainerCollectionsIndexingPropertyInfo = entityPropertyContainerCollectionsIndexingProperty
                .get(entityType);

        // Application indexes name property by default
        if (propertyName.equalsIgnoreCase(PROPERTY_NAME) || propertyName.equalsIgnoreCase(PROPERTY_CREATED)
                || propertyName.equalsIgnoreCase(PROPERTY_MODIFIED)) {
            return addDynamicApplicationCollectionAsContainer(
                    propertyContainerCollectionsIndexingPropertyInfo != null
                            ? propertyContainerCollectionsIndexingPropertyInfo.get(propertyName)
                            : null,
                    entityType);
        }

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

        return propertyContainerCollectionsIndexingPropertyInfo.get(propertyName);
    }

    /** @return value */
    public Map<String, Set<CollectionInfo>> getContainersIndexingDictionary(String entityType,
            String dictionaryName) {

        entityType = normalizeEntityType(entityType);

        /*
         * if (entityType == null) { return null; }
         */

        Map<String, Map<String, Set<CollectionInfo>>> dictionaryContainerCollectionsIndexingDictionary = entityDictionaryContainerCollectionsIndexingDictionary
                .get(entityType);

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

        // Application does index any set by default
        return dictionaryContainerCollectionsIndexingDictionary.get(dictionaryName);
    }

    public static String defaultCollectionName(String entityType) {
        try {
            return collectionNameCache.get(entityType);
        } catch (ExecutionException ex) {
            ex.printStackTrace();
        }
        return _defaultCollectionName(entityType);
    }

    private static String _defaultCollectionName(String entityType) {
        entityType = normalizeEntityType(entityType);
        return pluralize(entityType);
    }

    public static String normalizeEntityType(String entityType) {
        return normalizeEntityType(entityType, false);
    }

    public static String getAssociatedEntityType(String entityType) {
        if (entityType == null) {
            return null;
        }
        entityType = stringOrSubstringAfterLast(entityType, ':');
        return normalizeEntityType(entityType, false);
    }

    public static String normalizeEntityType(String entityType, boolean baseType) {
        if (entityType == null) {
            return null;
        }
        return baseType ? baseEntityTypes.getUnchecked(entityType) : nonbaseEntityTypes.getUnchecked(entityType);
    }

    /** uncached - use normalizeEntityType() */
    private static String createNormalizedEntityType(String entityType, boolean baseType) {
        if (baseType) {
            int i = entityType.indexOf(':');
            if (i >= 0) {
                entityType = entityType.substring(0, i);
            }
        }
        entityType = entityType.toLowerCase();
        if (entityType.startsWith("org.apache.usergrid.persistence")) {
            entityType = stringOrSubstringAfterLast(entityType, '.');
        }
        entityType = singularize(entityType);

        if ("dynamicentity".equalsIgnoreCase(entityType)) {
            throw new IllegalArgumentException(entityType + " is not a valid entity type");
        }

        // entityType = capitalizeDelimiter(entityType, '.', '_');
        return entityType;
    }

    public static boolean isAssociatedEntityType(String entityType) {
        return entityType != null && entityType.contains(":");
    }

    /** @return value */
    public EntityInfo getDynamicEntityInfo(String entityType) {

        entityType = normalizeEntityType(entityType);

        EntityInfo entity = new EntityInfo();
        entity.setType(entityType);

        Map<String, PropertyInfo> properties = new LinkedHashMap<String, PropertyInfo>();
        PropertyInfo property = new PropertyInfo();
        property.setName(PROPERTY_UUID);
        property.setRequired(true);
        property.setType(UUID.class);
        property.setMutable(false);
        property.setBasic(true);
        properties.put(PROPERTY_UUID, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_TYPE);
        property.setRequired(true);
        property.setType(String.class);
        property.setMutable(false);
        property.setBasic(true);
        properties.put(PROPERTY_TYPE, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_NAME);
        property.setRequired(false);
        property.setType(String.class);
        property.setMutable(false);
        property.setAliasProperty(true);
        property.setIndexed(true);
        property.setBasic(true);
        property.setUnique(true);
        properties.put(PROPERTY_NAME, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_CREATED);
        property.setRequired(true);
        property.setType(Long.class);
        property.setMutable(false);
        property.setIndexed(true);
        properties.put(PROPERTY_CREATED, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_MODIFIED);
        property.setRequired(true);
        property.setType(Long.class);
        property.setIndexed(true);
        properties.put(PROPERTY_MODIFIED, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_ITEM);
        property.setRequired(false);
        property.setType(UUID.class);
        property.setMutable(false);
        property.setAliasProperty(false);
        property.setIndexed(false);
        properties.put(PROPERTY_ITEM, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_ITEM_TYPE);
        property.setRequired(false);
        property.setType(String.class);
        property.setMutable(false);
        property.setAliasProperty(false);
        property.setIndexed(false);
        properties.put(PROPERTY_ITEM_TYPE, property);

        property = new PropertyInfo();
        property.setName(PROPERTY_COLLECTION_NAME);
        property.setRequired(false);
        property.setType(String.class);
        property.setMutable(false);
        property.setAliasProperty(false);
        property.setIndexed(false);
        properties.put(PROPERTY_COLLECTION_NAME, property);

        entity.setProperties(properties);

        Map<String, DictionaryInfo> sets = new LinkedHashMap<String, DictionaryInfo>();

        DictionaryInfo set = new DictionaryInfo();
        set.setName(DICTIONARY_CONNECTIONS);
        set.setKeyType(String.class);
        sets.put(DICTIONARY_CONNECTIONS, set);

        entity.setDictionaries(sets);

        return entity;
    }

    public Map<String, Object> cleanUpdatedProperties(String entityType, Map<String, Object> properties) {
        return cleanUpdatedProperties(entityType, properties, false);
    }

    public Map<String, Object> cleanUpdatedProperties(String entityType, Map<String, Object> properties,
            boolean create) {

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

        entityType = normalizeEntityType(entityType);

        properties.remove(PROPERTY_UUID);
        properties.remove(PROPERTY_TYPE);
        properties.remove(PROPERTY_METADATA);
        properties.remove(PROPERTY_MEMBERSHIP);
        properties.remove(PROPERTY_CONNECTION);

        Iterator<Entry<String, Object>> iterator = properties.entrySet().iterator();
        while (iterator.hasNext()) {
            Entry<String, Object> entry = iterator.next();
            if (hasProperty(entityType, entry.getKey())) {
                if (!create && !isPropertyMutable(entityType, entry.getKey())) {
                    iterator.remove();
                    continue;
                }
                Object propertyValue = entry.getValue();
                if ((propertyValue instanceof String) && (propertyValue.equals(""))) {
                    propertyValue = null;
                }
                if ((propertyValue == null) && isRequiredProperty(entityType, entry.getKey())) {
                    iterator.remove();
                }
            }
        }
        return properties;
    }

    public Object validateEntityPropertyValue(String entityType, String propertyName, Object propertyValue)
            throws PropertyTypeConversionException {

        entityType = normalizeEntityType(entityType);

        if ((propertyValue instanceof String) && propertyValue.equals("")) {
            propertyValue = null;
        }

        if (!hasProperty(entityType, propertyName)) {
            return propertyValue;
        }

        /*
         * if (PROPERTY_TYPE.equals(propertyName)) { return
         * string(propertyValue); } else if (PROPERTY_ID.equals(propertyName)) {
         * return uuid(propertyValue); }
         */

        Class<?> type = getPropertyType(entityType, propertyName);
        if (type != null) {
            // propertyValue = coerce(type, propertyValue);
            try {
                propertyValue = mapper.convertValue(propertyValue, type);
            } catch (Exception e) {
                throw new PropertyTypeConversionException(entityType, propertyName, propertyValue, type, e);
            }
        }

        return propertyValue;
    }

    public Object validateEntitySetValue(String entityType, String dictionaryName, Object elementValue) {

        entityType = normalizeEntityType(entityType);

        if ((elementValue instanceof String) && elementValue.equals("")) {
            elementValue = null;
        }

        if (!hasDictionary(entityType, dictionaryName)) {
            return elementValue;
        }

        Class<?> type = getDictionaryKeyType(entityType, dictionaryName);
        if (type != null) {
            // elementValue = coerce(type, elementValue);
            elementValue = mapper.convertValue(elementValue, type);
        }

        return elementValue;
    }

    public Entity toEntity(Map<String, Object> map) {
        Class<? extends Entity> entityClass = DynamicEntity.class;
        String type = (String) map.get(PROPERTY_TYPE);
        if (type != null) {
            entityClass = getEntityClass(type);
        }
        if (entityClass == null) {
            entityClass = DynamicEntity.class;
        }
        return mapper.convertValue(map, entityClass);
    }

    /*
     * public Entity toEntity(Reader reader) { Entity entity =
     * mapper.convertValue(reader, Entity.class); return entity; }
     * 
     * public Entity toEntity(InputStream input) { Entity entity =
     * mapper.convertValue(input, Entity.class); return entity; }
     * 
     * public Entity toEntity(String string) { Entity entity =
     * mapper.convertValue(string, Entity.class); return entity; }
     */

    public Map<String, Object> toMap(Entity entity) {
        return mapper.convertValue(entity, new TypeReference<Map<String, Object>>() {
        });
    }

    public Object convertToPropertyType(Class<? extends Entity> entityClass, String property, Object value) {
        Class<?> cls = getPropertyType(getEntityType(entityClass), property);
        if (cls != null) {
            return mapper.convertValue(value, cls);
        }
        return value;
    }

    public Object convertToPropertyType(String type, String property, Object value) {
        Class<?> cls = getPropertyType(type, property);
        if (cls != null) {
            return mapper.convertValue(value, cls);
        }
        return value;
    }

    public PropertyDescriptor getDescriptorForEntityProperty(Class<? extends Entity> entityClass, String property) {
        Map<String, PropertyDescriptor> propertyDescriptors = entityClassPropertyToDescriptor.get(entityClass);
        if (propertyDescriptors == null) {
            return null;
        }
        return propertyDescriptors.get(property);
    }

    public void setEntityProperty(Entity entity, String property, Object value) {
        PropertyDescriptor descriptor = getDescriptorForEntityProperty(entity.getClass(), property);
        if (descriptor != null) {
            Class<?> cls = descriptor.getPropertyType();
            if (cls != null) {
                if ((value == null) || (cls.isAssignableFrom(value.getClass()))) {
                    try {
                        descriptor.getWriteMethod().invoke(entity, value);
                        return;
                    } catch (Exception e) {
                        logger.error("Unable to set entity property " + property, e);
                    }
                }
                try {
                    descriptor.getWriteMethod().invoke(entity, mapper.convertValue(value, cls));
                    return;
                } catch (Exception e) {
                    logger.error("Unable to set entity property " + property, e);
                }
            }
        }
        entity.setDynamicProperty(property, value);
    }

    public Object getEntityProperty(Entity entity, String property) {
        PropertyDescriptor descriptor = getDescriptorForEntityProperty(entity.getClass(), property);
        if (descriptor != null) {
            try {
                return descriptor.getReadMethod().invoke(entity);
            } catch (Exception e) {
                logger.error("Unable to get entity property " + property, e);
            }
            return null;
        }
        Map<String, Object> properties = entity.getDynamicProperties();
        if (properties != null) {
            return properties.get(property);
        }
        return null;
    }

    public Map<String, Object> getEntityProperties(Entity entity) {
        Map<String, Object> properties = new LinkedHashMap<String, Object>();
        Map<String, PropertyDescriptor> propertyDescriptors = entityClassPropertyToDescriptor
                .get(entity.getClass());

        if (propertyDescriptors == null) {
            registerEntity(entity.getClass());
            propertyDescriptors = entityClassPropertyToDescriptor.get(entity.getClass());
        }

        for (Entry<String, PropertyDescriptor> propertyEntry : propertyDescriptors.entrySet()) {
            String property = propertyEntry.getKey();
            PropertyDescriptor descriptor = propertyEntry.getValue();
            if (descriptor != null) {
                try {
                    Object value = descriptor.getReadMethod().invoke(entity);
                    if (value != null) {
                        properties.put(property, value);
                    }
                } catch (Exception e) {
                    logger.error("Unable to get entity property " + property, e);
                }
            }
        }
        Map<String, Object> dynamicProperties = entity.getDynamicProperties();
        if (dynamicProperties != null) {
            properties.putAll(dynamicProperties);
        }
        return properties;
    }

    public static Map<String, Object> deserializeEntityProperties(Row<UUID, String, ByteBuffer> row) {
        if (row == null) {
            return null;
        }
        ColumnSlice<String, ByteBuffer> slice = row.getColumnSlice();
        if (slice == null) {
            return null;
        }
        return deserializeEntityProperties(slice.getColumns(), true, false);
    }

    /** @return entity properties from columns as a map */
    public static Map<String, Object> deserializeEntityProperties(List<HColumn<String, ByteBuffer>> columns) {
        return deserializeEntityProperties(CassandraPersistenceUtils.asMap(columns), true, false);
    }

    public static Map<String, Object> deserializeEntityProperties(Map<String, ByteBuffer> columns) {
        return deserializeEntityProperties(columns, true, false);
    }

    public static Map<String, Object> deserializeEntityProperties(List<HColumn<String, ByteBuffer>> columns,
            boolean checkId, boolean checkRequired) {
        return deserializeEntityProperties(CassandraPersistenceUtils.asMap(columns), checkId, checkRequired);
    }

    /** @return entity properties from columns as a map */
    public static Map<String, Object> deserializeEntityProperties(Map<String, ByteBuffer> columns, boolean checkId,
            boolean checkRequired) {

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

        String entityType = string(columns.get(PROPERTY_TYPE));
        if (entityType == null) {
            logger.debug("deserializeEntityProperties(): No type for entity found, entity probably doesn't exist");
            return null;
        }
        if (checkId && !columns.containsKey(PROPERTY_UUID)) {
            logger.error("No id for entity ( {} ) found!", entityType);
            return null;
        }

        if (checkRequired) {
            Set<String> required_properties = Schema.getDefaultSchema().getRequiredProperties(entityType);
            if (required_properties != null) {
                for (String property_name : required_properties) {
                    if (!columns.containsKey(property_name)) {
                        logger.error("Entity (" + entityType + ") missing required property: " + property_name,
                                new Throwable());
                        return null;
                    }
                }
            }
        }

        Map<String, Object> properties_map = new TreeMap<String, Object>(String.CASE_INSENSITIVE_ORDER);
        for (Entry<String, ByteBuffer> column : columns.entrySet()) {
            String propertyName = column.getKey();
            Object propertyValue = deserializeEntityProperty(entityType, propertyName, column.getValue());
            properties_map.put(propertyName, propertyValue);
        }
        return properties_map;
    }

    /** @return object of correct type deserialize from column bytes */
    public static Object deserializeEntityProperty(String entityType, String propertyName, ByteBuffer bytes) {
        Object propertyValue = null;
        if (PROPERTY_UUID.equals(propertyName)) {
            propertyValue = uuid(bytes);
        } else if (PROPERTY_TYPE.equals(propertyName)) {
            propertyValue = string(bytes);
        } else {
            if (Schema.getDefaultSchema().isPropertyEncrypted(entityType, propertyName)) {
                bytes = decrypt(bytes);
            }
            propertyValue = Schema.deserializePropertyValueFromJsonBinary(bytes);
        }
        return propertyValue;
    }

    public static ByteBuffer serializeEntityProperty(String entityType, String propertyName, Object propertyValue) {
        ByteBuffer bytes = null;
        if (PROPERTY_UUID.equals(propertyName)) {
            bytes = bytebuffer(uuid(propertyValue));
        } else if (PROPERTY_TYPE.equals(propertyName)) {
            bytes = bytebuffer(string(propertyValue));
        } else {
            bytes = Schema.serializePropertyValueToJsonBinary(toJsonNode(propertyValue));
            if (Schema.getDefaultSchema().isPropertyEncrypted(entityType, propertyName)) {
                bytes.rewind();
                bytes = encrypt(bytes);
            }
        }
        return bytes;
    }

    public static ByteBuffer serializePropertyValueToJsonBinary(Object obj) {
        return JsonUtils.toByteBuffer(obj);
    }

    public static Object deserializePropertyValueFromJsonBinary(ByteBuffer bytes) {
        return JsonUtils.normalizeJsonTree(JsonUtils.fromByteBuffer(bytes));
    }

    public static Object deserializePropertyValueFromJsonBinary(ByteBuffer bytes, Class<?> classType) {
        return JsonUtils.normalizeJsonTree(JsonUtils.fromByteBuffer(bytes, classType));
    }

    public boolean isPropertyEncrypted(String entityType, String propertyName) {

        EntityInfo entity = getEntityInfo(entityType);
        if (entity == null) {
            return false;
        }

        PropertyInfo property = entity.getProperty(propertyName);
        return property != null && property.isEncrypted();

    }

    private static final byte[] DEFAULT_ENCRYPTION_SEED = "oWyWX?I2kZAhkKb_jQ8SZvjmgkiF4eGSjsfIkhnRetD4Dvtx2J"
            .getBytes();
    private static byte[] encryptionSeed = (System.getProperty("encryptionSeed") != null)
            ? System.getProperty("encryptionSeed").getBytes()
            : DEFAULT_ENCRYPTION_SEED;

    public static ByteBuffer encrypt(ByteBuffer clear) {
        if (clear == null || !clear.hasRemaining()) {
            return clear;
        }
        try {
            SecretKeySpec sKeySpec = new SecretKeySpec(getRawKey(encryptionSeed), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
            ByteBuffer encrypted = ByteBuffer.allocate(cipher.getOutputSize(clear.remaining()));
            cipher.doFinal(clear, encrypted);
            encrypted.rewind();
            return encrypted;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    public static ByteBuffer decrypt(ByteBuffer encrypted) {
        if (encrypted == null || !encrypted.hasRemaining()) {
            return encrypted;
        }
        try {
            SecretKeySpec sKeySpec = new SecretKeySpec(getRawKey(encryptionSeed), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
            ByteBuffer decrypted = ByteBuffer.allocate(cipher.getOutputSize(encrypted.remaining()));
            cipher.doFinal(encrypted, decrypted);
            decrypted.rewind();
            return decrypted;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        keyGenerator.init(128, sr); // 192 and 256 bits may not be available
        SecretKey secretKey = keyGenerator.generateKey();
        return secretKey.getEncoded();
    }
}