org.firebrandocm.dao.ClassMetadata.java Source code

Java tutorial

Introduction

Here is the source code for org.firebrandocm.dao.ClassMetadata.java

Source

/*
 * Copyright (C) 2012 47 Degrees, LLC
 * http://47deg.com
 * hello@47deg.com
 *
 * 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.firebrandocm.dao;

import javassist.util.proxy.MethodFilter;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
import me.prettyprint.cassandra.serializers.StringSerializer;
import org.apache.cassandra.db.marshal.CounterColumnType;
import org.apache.cassandra.thrift.CfDef;
import org.apache.cassandra.thrift.ColumnDef;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.firebrandocm.dao.annotations.*;
import org.firebrandocm.dao.events.Event;
import org.firebrandocm.dao.utils.ObjectUtils;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Holds metadata information about a class, initialized at startup time and reused
 */
public class ClassMetadata<T> {
    /* Fields */

    private static final String CLASS_PROPERTY = "class";

    private static Map<String, String> namedQueries = new HashMap<String, String>();

    private Log log = LogFactory.getLog(getClass());

    /**
     * the keyspace this entity belongs to
     */
    private String keySpace;

    /**
     * a column family for this entity
     */
    private String columnFamily;

    /**
     * the entity's class
     */
    private Class<T> target;

    /**
     * the set of all class properties
     */
    private Set<String> mutationProperties = new HashSet<String>();

    /**
     * the set of all readable properties
     */
    private Set<String> selectionProperties = new HashSet<String>();

    /**
     * the set of all persistent properties that declared themselves as being part of secondary indexes
     */
    private Set<String> indexedProperties = new HashSet<String>();

    /**
     * the property that holds the key in this class
     */
    private String keyProperty;

    /**
     * a map from columns to types
     */
    private Map<String, Class<?>> propertiesTypesMap = new HashMap<String, Class<?>>();

    /**
     * set of properties that map to embedded entities
     */
    private Set<String> embeddedEntities = new HashSet<String>();

    /**
     * set of properties that map to mappedEntities
     */
    private Set<String> mappedEntities = new HashSet<String>();

    /**
     * set of properties that map to element collections
     */
    private Set<String> elementCollections = new HashSet<String>();

    /**
     * the column family definition
     */
    private CfDef columnFamilyDefinition;

    /**
     * set of properties that map to counter properties
     */
    private Set<String> counterProperties = new HashSet<String>();

    /**
     * set of properties that map to properties that increase counter values
     */
    private Map<String, String> counterPropertiesIncrease = new HashMap<String, String>();

    /**
     * set of properties that map to mapped properties
     */
    private Set<String> mappedProperties = new HashSet<String>();

    /**
     * set of properties that map to mapped collections
     */
    private Set<String> mappedCollections = new HashSet<String>();

    /**
     * map of methods and their properties that are lazy accessors
     */
    private Map<Method, String> lazyAccesors = new HashMap<Method, String>();

    /**
     * set of properties that map to properties that should be loaded on demand when their read method is invoked
     */
    private Set<String> lazyProperties = new HashSet<String>();

    /**
     * a proxy method handler
     */
    private MethodHandler proxyMethodHandler;

    /**
     * the proxy class
     */
    private Class<?> proxyClass;

    /**
     * true if this metadata object is associated with a class that represents a Counter style column family
     */
    private boolean counterColumnFamily;

    /**
     * map of properties and the container classes associated
     */
    private Map<String, Class<?>> propertyContainerMap = new HashMap<String, Class<?>>();

    /**
     * map of events and the methods that act as listeners of those events
     */
    private Map<Event.Entity, Set<Method>> entityEventListenersMap = new TreeMap<Event.Entity, Set<Method>>();

    /**
     * the keyspace consistency level
     */
    private ConsistencyLevel consistencyLevel;

    /* Static Methods */

    /**
     * Gets a named query by its name
     *
     * @param name the query name
     * @return the query value
     */
    public static String getNamedQuery(String name) {
        String query = getNullSafeNamedQuery(name);
        if (query == null)
            throw new IllegalArgumentException(String.format("named query not found for name: %s", name));
        return query;
    }

    /**
     * Gets a name query by its name returning null if not found
     *
     * @param name the query name
     * @return the query value
     */
    public static String getNullSafeNamedQuery(String name) {
        return namedQueries.get(name);
    }

    /* Constructors */

    /**
     * Constructor.
     * Extracts and caches metadata for persistent entity classes
     *
     * @param target             the target class
     * @param persistenceFactory the persistence factory managing the entity
     */
    public ClassMetadata(Class<T> target, AbstractPersistenceFactory persistenceFactory)
            throws ClassNotFoundException, IntrospectionException, InstantiationException, IllegalAccessException {
        log.debug(String.format("Initializing class metadata for %s", target));
        this.target = target;
        //If this is a top level structure that holds a column family
        if (target.isAnnotationPresent(ColumnFamily.class)) {
            ColumnFamily columnFamilyAnnotation = target.getAnnotation(ColumnFamily.class);
            consistencyLevel = columnFamilyAnnotation.consistencyLevel();
            counterColumnFamily = columnFamilyAnnotation.defaultValidationClass() == CounterColumnType.class;
            keySpace = StringUtils.defaultIfEmpty(columnFamilyAnnotation.keySpace(),
                    persistenceFactory.getDefaultKeySpace());
            columnFamily = target.getSimpleName();
            initializeColumnFamilyDefinition();
            processFields(target, "");
            processMethods(target);
            addClassTypePropertyIfSupported();
            initializeProxyFactory(persistenceFactory);
            initializeNamedQueries(target);
        } else {
            throw new IllegalArgumentException(target + " is not annotated with " + ColumnFamily.class);
        }
    }

    /**
     * Private helper to initialize a column family definition
     */
    private void initializeColumnFamilyDefinition() throws ClassNotFoundException {
        columnFamilyDefinition = new CfDef();
        columnFamilyDefinition.setName(columnFamily);
        columnFamilyDefinition.setKeyspace(getKeySpace());
        ColumnFamily cfAnnotation = target.getAnnotation(ColumnFamily.class);
        String comparatorType = cfAnnotation.compareWith().getName();
        if (cfAnnotation.reversed()) {
            comparatorType = String.format("%s(reversed=true)", comparatorType);
        }
        columnFamilyDefinition.setComparator_type(comparatorType);
        columnFamilyDefinition.setKey_cache_size(cfAnnotation.keysCached());
        columnFamilyDefinition.setRow_cache_size(cfAnnotation.rowsCached());
        columnFamilyDefinition.setComment(StringUtils.defaultIfEmpty(cfAnnotation.comment(), null));
        columnFamilyDefinition.setComparator_type(cfAnnotation.compareWith().getName());
        columnFamilyDefinition.setRead_repair_chance(cfAnnotation.readRepairChance());
        columnFamilyDefinition.setGc_grace_seconds(cfAnnotation.gcGraceSeconds());
        columnFamilyDefinition.setDefault_validation_class(
                StringUtils.defaultIfEmpty(cfAnnotation.defaultValidationClass().getName(), null));
        columnFamilyDefinition.setKey_validation_class(
                StringUtils.defaultIfEmpty(cfAnnotation.defaultKeyValidationClass().getName(), null));
        columnFamilyDefinition.setMin_compaction_threshold(cfAnnotation.minCompactionThreshold());
        columnFamilyDefinition.setMax_compaction_threshold(cfAnnotation.maxCompactionThreshold());
        columnFamilyDefinition.setReplicate_on_write(cfAnnotation.replicateOnWrite());
    }

    /**
     * Processes the target fields searching for persistent annotations
     *
     * @param target the target class
     * @param prefix a potential prefix utilized for deep nested properties
     */
    private void processFields(Class<?> target, String prefix)
            throws IntrospectionException, ClassNotFoundException {
        if (StringUtils.isNotBlank(prefix)) {
            prefix += ".";
        }
        for (Field field : ObjectUtils.getAllFieldsInHierarchy(target)) {
            processElement(field, prefix + field.getName(), field.getType());
        }
    }

    /**
     * Processes a field searching for persistent annotations
     *
     * @param element      the annotated element
     * @param propertyName the element name
     * @param type         the property type
     */
    private void processElement(Field element, String propertyName, Class<?> type)
            throws ClassNotFoundException, IntrospectionException {
        if (valid(element, propertyName, type)) {
            if (element.isAnnotationPresent(Embedded.class)) {
                processEmbeddedEntity(type, propertyName);
            } else if (element.isAnnotationPresent(Mapped.class)) {
                processMappedEntity(type, element, propertyName);
            } else if (element.isAnnotationPresent(MappedCollection.class)) {
                processMappedCollection(type, element, propertyName);
            } else if (element.isAnnotationPresent(CounterIncrease.class)) {
                processCounterIncrease(type, element, propertyName,
                        element.getAnnotation(CounterIncrease.class).value());
            } else {
                if (element.isAnnotationPresent(Key.class)) {
                    keyProperty = propertyName;
                }
                processSimpleColumn(element, propertyName);
            }
        }
    }

    /**
     * Private helper.
     * Determines whether a field is valid for processing and persistence consideration
     *
     * @param element the field element
     * @param name    the name
     * @param type    the field type
     * @return if the field should be considered for persistence
     */
    private boolean valid(Field element, String name, Class<?> type) {
        return !name.equals(CLASS_PROPERTY) && !(element.isAnnotationPresent(Transient.class))
                && !name.equals(keyProperty) && !type.getName().equals(PersistenceFactory.class.getName())
                && !Modifier.isStatic(element.getModifiers());
    }

    /**
     * Processes metadata for a nested embedded association. Helper method
     *
     * @param type         the type
     * @param propertyName the property name
     */
    private void processEmbeddedEntity(Class<?> type, String propertyName)
            throws ClassNotFoundException, IntrospectionException {
        embeddedEntities.add(propertyName);
        propertiesTypesMap.put(propertyName, type);
        mutationProperties.add(propertyName);
        propertyContainerMap.put(propertyName, type);
        log.debug(String.format("added type %s and property %s", type.getName(), propertyName));
        processFields(type, propertyName);
    }

    /**
     * Processes metadata for a nested mapped association. Helper method
     *
     * @param type         the type
     * @param element
     * @param propertyName the property name
     */
    private void processMappedEntity(Class<?> type, Field element, String propertyName)
            throws ClassNotFoundException, IntrospectionException {
        Mapped mapped = element.getAnnotation(Mapped.class);
        mappedEntities.add(propertyName);
        propertiesTypesMap.put(propertyName, type);
        mutationProperties.add(propertyName);
        propertyContainerMap.put(propertyName, type);
        mappedProperties.add(propertyName);
        boolean lazy = mapped != null && mapped.lazy();
        addProperty(null, propertyName, element.getType(), true, lazy, false, false);
        log.debug(String.format("added mapped type %s and property %s", type.getName(), propertyName));
    }

    /**
     * Private helper that caches information for a property for further persistence consideration
     *
     * @param colAnnotation   the column annotation
     * @param propertyName    the property name
     * @param type            the property type
     * @param indexed         whether the property should be indexed in the data store
     * @param lazy            if access to this property should be loaded on demand
     * @param counter         if this property represents a counter
     * @param counterIncrease if this property represents a value for a counter arithmetic operation
     */
    private void addProperty(Column colAnnotation, String propertyName, Class<?> type, boolean indexed,
            boolean lazy, boolean counter, boolean counterIncrease)
            throws ClassNotFoundException, IntrospectionException {
        propertiesTypesMap.put(propertyName, type);
        mutationProperties.add(propertyName);
        if (indexed) {
            indexedProperties.add(propertyName);
            log.debug(String.format("added indexed property %s", propertyName));
        }
        if (lazy) {
            PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, target);
            lazyAccesors.put(descriptor.getReadMethod(), propertyName);
            lazyProperties.add(propertyName);
        }
        if (counter) {
            counterProperties.add(propertyName);
        }
        if (!counterIncrease) {
            selectionProperties.add(propertyName);
            addColumnToColumnFamilyDefinition(colAnnotation, propertyName, indexed);
        }
        log.debug(String.format("added property %s", propertyName));
    }

    /**
     * Private helper that adds a c olumn to a column family definition
     *
     * @param colAnnotation the column annotation
     * @param property      the property
     * @param indexed       if this property should be indexed in the datastore
     */
    private void addColumnToColumnFamilyDefinition(Column colAnnotation, String property, boolean indexed)
            throws ClassNotFoundException {
        if (!property.equals(keyProperty)) {
            boolean defaults = colAnnotation == null;
            ColumnDef columnDef = new ColumnDef();
            columnDef.setName(StringSerializer.get().toByteBuffer(property));
            columnDef.setValidation_class(
                    defaults ? org.firebrandocm.dao.annotations.Column.DEFAULTS.VALIDATION_CLASS.getName()
                            : colAnnotation.validationClass().getName());
            indexed = indexed || isMappedContainer(property);
            if (!indexed) {
                indexed = defaults ? org.firebrandocm.dao.annotations.Column.DEFAULTS.INDEXED
                        : colAnnotation.indexed();
            }
            if (indexed) {
                columnDef.setIndex_name(String.format("%s_%s_%s", keySpace, columnFamily, property));
                columnDef.setIndex_type(defaults ? org.firebrandocm.dao.annotations.Column.DEFAULTS.INDEX_TYPE
                        : colAnnotation.indexType());
            }
            columnFamilyDefinition.addToColumn_metadata(columnDef);
        }
    }

    /**
     * Whether this property should be considered as a mapped container which data belongs in some other row
     *
     * @param property the property
     * @return if this property is a mapped container
     */
    public boolean isMappedContainer(String property) {
        return mappedEntities.contains(property);
    }

    /**
     * Processes metadata for a nested mapped collection. Helper method
     *
     * @param type
     * @param element
     * @param propertyName
     * @throws ClassNotFoundException
     * @throws IntrospectionException
     */
    private void processMappedCollection(Class<?> type, Field element, String propertyName)
            throws ClassNotFoundException, IntrospectionException {
        MappedCollection mappedCollection = element.getAnnotation(MappedCollection.class);
        mappedCollections.add(propertyName);
        propertiesTypesMap.put(propertyName, type);
        mutationProperties.add(propertyName);
        propertyContainerMap.put(propertyName, type);
        mappedProperties.add(propertyName);
        boolean lazy = mappedCollection != null && mappedCollection.lazy();
        addProperty(null, propertyName, element.getType(), true, lazy, false, false);
        log.debug(String.format("added mapped type %s and property %s", type.getName(), propertyName));
    }

    /**
     * Processes metadata for counter increase property
     *
     * @param type
     * @param element
     * @param propertyName
     * @throws ClassNotFoundException
     * @throws IntrospectionException
     */
    private void processCounterIncrease(Class<?> type, Field element, String propertyName, String targetCounter)
            throws ClassNotFoundException, IntrospectionException {
        counterPropertiesIncrease.put(propertyName, targetCounter);
        addProperty(null, propertyName, element.getType(), true, false, false, true);
        log.debug(String.format("added processCounterIncrease type %s and property %s", type.getName(),
                propertyName));
    }

    /**
     * Processes metadata for a simple column. Helper method
     *
     * @param element      the type
     * @param propertyName the property name
     */
    private void processSimpleColumn(Field element, String propertyName)
            throws ClassNotFoundException, IntrospectionException {
        propertiesTypesMap.put(propertyName, element.getType());
        mutationProperties.add(propertyName);
        propertyContainerMap.put(propertyName, element.getDeclaringClass());
        boolean indexed = false;
        if (element.isAnnotationPresent(Column.class)) {
            Column columnAnnotation = element.getAnnotation(Column.class);
            indexed = columnAnnotation != null && columnAnnotation.indexed();
        }
        org.firebrandocm.dao.annotations.Column colAnnotation = element
                .getAnnotation(org.firebrandocm.dao.annotations.Column.class);
        boolean lazy = colAnnotation != null && colAnnotation.lazy();
        boolean counter = colAnnotation != null && colAnnotation.counter();
        addProperty(colAnnotation, propertyName, element.getType(), indexed, lazy, counter, false);
        log.debug(String.format("added property %s", propertyName));
    }

    /**
     * Private Helper.
     * Processes all methods in hierarchy for the annotated entities scanning for persistence annotations
     *
     * @param target the target class
     */
    private void processMethods(Class<T> target) {
        for (Method method : ObjectUtils.getAllMethodsInHierarchy(target)) {
            processMethod(method);
        }
    }

    /**
     * Private Helper.
     * Processes a method scanning for persistence annotations
     *
     * @param method the method
     */
    private void processMethod(Method method) {
        if (method.isAnnotationPresent(OnEvent.class)) {
            OnEvent onEvent = method.getAnnotation(OnEvent.class);
            Set<Method> methods = entityEventListenersMap.get(onEvent.value());
            if (methods == null) {
                methods = new LinkedHashSet<Method>();
                entityEventListenersMap.put(onEvent.value(), methods);
            }
            methods.add(method);
        }
    }

    /**
     * Private Helper.
     * Adds an internal class property to obtain class information from each inserted row
     */
    private void addClassTypePropertyIfSupported() throws ClassNotFoundException, IntrospectionException {
        if (!counterColumnFamily) {
            addProperty(null, PersistenceFactory.CLASS_PROPERTY, String.class, true, false, false, false);
        }
    }

    /**
     * Initializes a class proxy factory that enhances instances wrapping calls to lazy and other methods that need to be
     * audited around invokations
     *
     * @param persistenceFactory the persistence factory associated with this context
     */
    private void initializeProxyFactory(final AbstractPersistenceFactory persistenceFactory)
            throws IllegalAccessException, InstantiationException {
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setSuperclass(target);
        proxyFactory.setFilter(new MethodFilter() {
            public boolean isHandled(Method m) {
                return isLazyAccessor(m);
            }
        });
        proxyClass = proxyFactory.createClass();
        proxyMethodHandler = new MethodHandler() {
            public Object invoke(Object self, Method m, Method proceed, Object[] args) throws Throwable {
                log.debug("lazy loading: " + m.getName());
                persistenceFactory.loadLazyPropertyIfNecessary(ClassMetadata.this, self, proceed, m, args);
                return proceed.invoke(self, args); // execute the original method.
            }
        };
    }

    /**
     * Informs whether a method corresponds with a lazy accessor
     *
     * @param method the method
     * @return if it's the accessor of a lazy property
     */
    public boolean isLazyAccessor(Method method) {
        return lazyAccesors.containsKey(method);
    }

    /**
     * Private Helper. Scans for named query annotations an initializes the named queries associated with this persistence factory
     *
     * @param target the target class
     */
    private void initializeNamedQueries(Class<T> target) {
        List<NamedQuery> queries = new ArrayList<NamedQuery>();
        if (target.isAnnotationPresent(NamedQueries.class)) {
            NamedQuery[] value = target.getAnnotation(NamedQueries.class).value();
            for (int i = 0, valueLength = value.length; i < valueLength; i++) {
                NamedQuery namedQuery = value[i];
                queries.add(namedQuery);
            }
        }
        if (target.isAnnotationPresent(NamedQuery.class)) {
            queries.add(target.getAnnotation(NamedQuery.class));
        }
        for (NamedQuery query : queries) {
            if (namedQueries.containsKey(query.name())) {
                throw new IllegalStateException(String.format("Duplicated named query name: %s", query.name()));
            }
            namedQueries.put(query.name(), query.query());
        }
    }

    /* Getters & Setters */

    /**
     * @return the column family
     */
    public String getColumnFamily() {
        return columnFamily;
    }

    /**
     * @return the column family definition
     */
    public CfDef getColumnFamilyDefinition() {
        return columnFamilyDefinition;
    }

    /**
     * @return the set of properties that declares themselves as being part of secondary indexes
     */
    public Set<String> getIndexedProperties() {
        return indexedProperties;
    }

    /**
     * @return the key property that holds the key for this class / columnfamily
     */
    public String getKeyProperty() {
        return keyProperty;
    }

    /**
     * @return the keyspace for this column family
     */
    public String getKeySpace() {
        return keySpace;
    }

    /**
     * @return the mapped properties
     */
    public Set<String> getMappedProperties() {
        return mappedProperties;
    }

    /**
     * @return the set of all persistent properties
     */
    public Set<String> getMutationProperties() {
        return mutationProperties;
    }

    /**
     * @return the map of properties and their types
     */
    public Map<String, Class<?>> getPropertiesTypesMap() {
        return propertiesTypesMap;
    }

    /**
     * @return the map of property containers and their type
     */
    public Map<String, Class<?>> getPropertyContainerMap() {
        return propertyContainerMap;
    }

    /**
     * @return the properties that will be considered on selection operation
     */
    public Set<String> getSelectionProperties() {
        return selectionProperties;
    }

    /**
     * @return the target
     */
    public Class<T> getTarget() {
        return target;
    }

    /**
     * @return true if the metadata is associated with a counter column family
     */
    public boolean isCounterColumnFamily() {
        return counterColumnFamily;
    }

    /* Canonical Methods */

    /**
     * @see Object#toString()
     */
    @Override
    public String toString() {
        return "ClassMetadata{" + "target=" + target.getName() + '}';
    }

    /* Misc */

    /**
     * Creates a proxy instance for the class represented in this metadata
     *
     * @return the proxy class
     */
    @SuppressWarnings("unchecked")
    public T createProxy() {
        T instance;
        try {
            instance = (T) proxyClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        ((ProxyObject) instance).setHandler(proxyMethodHandler);
        return instance;
    }

    /**
     * destroys and frees any resources retained by this metadata
     */
    public void destroy() {
        namedQueries.clear();
    }

    /**
     * @param name the column name
     * @return the java type associated to the column
     */
    public Class<?> getColumnClass(String name) {
        return propertiesTypesMap.get(name);
    }

    /**
     * Gets a lazy property associated to a given a method
     *
     * @param method the method
     * @return the lazy property if found, null otherwise
     */
    public String getLazyProperty(Method method) {
        return lazyAccesors.get(method);
    }

    /**
     * Gets a method associated to a given event
     *
     * @param event the event
     * @return the method if found, null otherwise
     */
    public Set<Method> getListenersForEvent(Event.Entity event) {
        return entityEventListenersMap.get(event);
    }

    /**
     * Gets a counter property associated to an increase counter property
     *
     * @param increaseCounterProperty the increase counter property
     * @return the counter property if found, null otherwise
     */
    public String getTargetCounterProperty(String increaseCounterProperty) {
        return counterPropertiesIncrease.get(increaseCounterProperty);
    }

    /**
     * Checks if a property is a container (embedded, mapped or collection)
     *
     * @param property the property
     * @return true if the property is a container
     */
    public boolean isContainer(String property) {
        return isAssociationContainer(property) || isMappedContainer(property) || isMappedCollection(property);
    }

    /**
     * Checks if the property is a embedded container
     *
     * @param property the property
     * @return true if the property is an embedded container
     */
    public boolean isAssociationContainer(String property) {
        return embeddedEntities.contains(property);
    }

    /**
     * Checks if the property is a mapped collection
     *
     * @param property the property
     * @return true if the property is a mapped collection
     */
    public boolean isMappedCollection(String property) {
        return mappedCollections.contains(property);
    }

    /**
     * Checks if the property is a counter increase property.
     * A property that is used to increase counter columns
     *
     * @param property the property
     * @return true if the property is a counter increase property
     */
    public boolean isCounterIncreaseProperty(String property) {
        return counterPropertiesIncrease.containsKey(property);
    }

    /**
     * Checks if the property is a counter property
     * @param property the property
     * @return true if the property is a counter property
     */
    public boolean isCounterProperty(String property) {
        return counterProperties.contains(property);
    }

    /**
     * Checks if the property is an element collection e.g. List<? not an entity>
     * @param property the property
     * @return true if the property is an element collection
     */
    public boolean isElementCollection(String property) {
        return elementCollections.contains(property);
    }

    /**
     * Check if the property is flagged as lazy and should be loaded when it's getter is invoked
     * @param property the property
     * @return true if the property is flagged as a lazy property
     */
    public boolean isLazyProperty(String property) {
        return lazyProperties.contains(property);
    }

    /**
     *
     * @return the keyspace consistency level
     */
    public ConsistencyLevel getConsistencyLevel() {
        return consistencyLevel;
    }
}