Package org.firebrandocm.dao.impl.hector

Source Code of org.firebrandocm.dao.impl.hector.HectorPersistenceFactory$Builder

/*
* 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.impl.hector;

import me.prettyprint.cassandra.model.CqlQuery;
import me.prettyprint.cassandra.model.CqlRows;
import me.prettyprint.cassandra.serializers.AbstractSerializer;
import me.prettyprint.cassandra.serializers.StringSerializer;
import me.prettyprint.cassandra.service.BatchSizeHint;
import me.prettyprint.cassandra.service.CassandraHostConfigurator;
import me.prettyprint.cassandra.service.ThriftKsDef;
import me.prettyprint.hector.api.Cluster;
import me.prettyprint.hector.api.Keyspace;
import me.prettyprint.hector.api.beans.ColumnSlice;
import me.prettyprint.hector.api.beans.HColumn;
import me.prettyprint.hector.api.beans.Row;
import me.prettyprint.hector.api.ddl.ColumnDefinition;
import me.prettyprint.hector.api.ddl.ColumnFamilyDefinition;
import me.prettyprint.hector.api.ddl.KeyspaceDefinition;
import me.prettyprint.hector.api.factory.HFactory;
import me.prettyprint.hector.api.mutation.Mutator;
import me.prettyprint.hector.api.query.QueryResult;
import me.prettyprint.hector.api.query.SliceQuery;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.KsDef;
import org.apache.commons.beanutils.NestedNullException;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.firebrandocm.dao.*;
import org.firebrandocm.dao.events.ColumnEventListener;
import org.firebrandocm.dao.events.EntityEventListener;
import org.firebrandocm.dao.events.Event;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.*;

import static me.prettyprint.hector.api.factory.HFactory.createColumn;
import static org.firebrandocm.dao.cql.QueryBuilder.*;

/**
* An Hector based impl for the Persistence Factory
*/
public class HectorPersistenceFactory extends AbstractPersistenceFactory {
    /* Fields */

    /**
     * the cluster name
     */
    private String clusterName;

    /**
     * The cluster credentials
     */
    private Map<String, String> credentials;

    /**
     * Whether to auto discover hosts
     */
    private boolean autoDiscoverHosts;

    /**
     * A cassandra host configurator instance.
     * If non provided a default one will be created on init
     */
    private CassandraHostConfigurator cassandraHostConfigurator;

    /**
     * The cluster instance
     */
    private Cluster cluster;

    /* Getters & Setters */

    /**
     * Sets the autoDiscoverHosts property
     *
     * @param autoDiscoverHosts whether to auto discover hosts
     */
    public void setAutoDiscoverHosts(boolean autoDiscoverHosts) {
        this.autoDiscoverHosts = autoDiscoverHosts;
    }

    /**
     * Sets a cassandra host configurator instance.
     * If non provided a default one will be created on init
     *
     * @param cassandraHostConfigurator the cassandra host configurator instance
     */
    public void setCassandraHostConfigurator(CassandraHostConfigurator cassandraHostConfigurator) {
        this.cassandraHostConfigurator = cassandraHostConfigurator;
    }

    /**
     * Sets the cluster name
     *
     * @param clusterName the cluster name
     */
    public void setClusterName(String clusterName) {
        this.clusterName = clusterName;
    }

    /**
     * Sets the cluster credentials
     *
     * @param credentials the credentials
     */
    public void setCredentials(Map<String, String> credentials) {
        this.credentials = credentials;
    }

    /* Interface Implementations */


// --------------------- Interface PersistenceFactory ---------------------


    /**
     * Deletes columns by name from column family
     *
     * @param colFamily the column family
     * @param key       the key
     * @param columns   the columns to be deleted by name
     */
    @Override
    public void deleteColumns(String colFamily, String key, String... columns) {
        Keyspace keyspace = HFactory.createKeyspace(getDefaultKeySpace(), cluster);
        Mutator<String> mutator = HFactory.createMutator(keyspace, StringSerializer.get(), new BatchSizeHint(1, columns.length));
        for (String property : columns) {
            fireColumnEvent(Event.Column.PRE_COLUMN_DELETION, null, property, key, colFamily, null);
            mutator.addDeletion(key, colFamily, property, StringSerializer.get());
            log.debug(String.format("\tD: %s", property));
            fireColumnEvent(Event.Column.POST_COLUMN_DELETION, null, property, key, colFamily, null);
        }
        mutator.execute();
    }

    /**
     * Get an entity by id
     *
     * @param entityClass the class
     * @param key          the key
     * @param <T>         the entity type
     * @return the entity
     */
    public <T> T get(Class<T> entityClass, String key) {
        if (log.isDebugEnabled()) log.debug(String.format("get (start): %s, %s", entityClass, key));
        fireEntityEvent(Event.Entity.PRE_LOAD, entityClass, key);
        T result = getSingleResult(entityClass, Query.get(select(allColumns(), from(entityClass), where(key(key)))));
        fireEntityEvent(Event.Entity.POST_LOAD, result);
        if (log.isDebugEnabled()) log.debug(String.format("get (end): %s", result));
        return result;
    }

    /**
     * Fetch a map of columns and their values
     *
     * @param columnFamily the column family
     * @param key          the column family key
     * @param reversed     if the order should be reversed
     * @param columns      the column names
     * @return a map of columns and their values
     */
    @Override
    public Map<String, ByteBuffer> getColumns(String columnFamily, String key, boolean reversed, String... columns) {
        return getColumns(
                Query.get(select(reversed ? reversed() : null, columns(columns), from(columnFamily), where(key(key)))).getQuery()
        );
    }

    /**
     * Fetch a map of columns and their values
     *
     * @param columnFamily the column family
     * @param key          the column family key
     * @param limit        of columns
     * @param reversed     if the order should be reversed
     * @param fromColumn   from column
     * @param toColumn     to column
     * @return a map of columns and their values
     */
    @Override
    public Map<String, ByteBuffer> getColumns(String columnFamily, String key, int limit, boolean reversed, String fromColumn, String toColumn) {
        return getColumns(
                Query.get(select(reversed ? reversed() : null, first(limit), columnRange(fromColumn, toColumn), from(columnFamily), where(key(key)))).getQuery()
        );
    }

    /**
     * Get a list of entities given a query
     *
     * @param type  the type of objects to expect back
     * @param query the query
     * @param <T>   the result type
     * @return the list of entities
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> List<T> getResultList(Class<T> type, Query query) {
        if (log.isDebugEnabled()) log.debug(String.format("getResultList (start): %s", query.getQuery()));
        List<T> result = new ArrayList<T>();
        try {
            ClassMetadata metadata = getClassMetadata(type);
            if (metadata == null && !Long.class.isAssignableFrom(type))
                throw new IllegalArgumentException(String.format("type: %s not recognized as ColumnFamily or returnable value", type));
            CqlQuery<String, String, Object> indexedQuery = getCQLQuery(type, query.getQuery());
            CqlRows<String, String, Object> cqlRows = indexedQuery.execute().get();
            if (cqlRows != null) {
                T entity = null;
                if (metadata == null) {
                    entity = (T) Long.valueOf(cqlRows.getAsCount());
                    result.add(entity);
                } else {
                    for (Row<String, String, Object> orderedRow : cqlRows.getList()) {
                        ColumnSlice<String, Object> slice = orderedRow.getColumnSlice();
                        List<HColumn<String, Object>> columns = slice.getColumns();
                        if (columns.size() > 0) {
                            entity = serializeColumns(orderedRow.getKey(), metadata, type, columns, null, false);
                            result.add(entity);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (log.isDebugEnabled()) log.debug(String.format("getResultList (end): %s", result.size()));
        return result;
    }

    /**
     * Get a single result from a CQL query
     *
     * @param type  the type of objects to expect back
     * @param query the query
     * @param <T>   the entity type
     * @return the resulting entity
     */
    @Override
    public <T> T getSingleResult(Class<T> type, Query query) {
        List<T> results = getResultList(type, query);
        if (results.size() > 1) {
            throw new IllegalStateException(String.format("Expected a single result but found %d", results.size()));
        }
        return results.size() == 1 ? results.get(0) : null;
    }

    /**
     * Inserts columns based on a map representing keys with properties and their corresponding values
     *
     * @param colFamily     the column family
     * @param key           the column family key
     * @param keyValuePairs the map with keys and values
     */
    @Override
    public void insertColumns(String colFamily, String key, Map<String, Object> keyValuePairs) {
        Keyspace keyspace = HFactory.createKeyspace(getDefaultKeySpace(), cluster);
        Mutator<String> mutator = HFactory.createMutator(keyspace, StringSerializer.get(), new BatchSizeHint(1, keyValuePairs.size()));
        for (Map.Entry<String, Object> entry : keyValuePairs.entrySet()) {
            String property = entry.getKey();
            Object value = entry.getValue();
            HColumn<String, Object> column = createColumn(property, value, StringSerializer.get(), new TypeConverterSerializer<Object>(value));
            fireColumnEvent(Event.Column.PRE_COLUMN_MUTATION, null, property, key, colFamily, column);
            mutator.addInsertion(key, colFamily, column);
            log.debug(String.format("\tI: %s : %s ", property, value));
            fireColumnEvent(Event.Column.POST_COLUMN_MUTATION, null, property, key, colFamily, column);
        }
        mutator.execute();
    }

    /**
     * Entry point method to persist and arbitrary list of objects into the datastore
     *
     * @param entities the entities to be persisted
     */
    public void persist(Object... entities) {
        try {
            createKeysIfNeeded(entities);
            persistAll(entities);
            fireEntityEvent(Event.Entity.POST_COMMIT, entities);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Removes a list of entities from the data store
     *
     * @param entities the entities to be removed from the data store
     */
    public void remove(Object... entities) {
        log.debug(String.format("START remove(%s)", Arrays.toString(entities)));
        try {
            for (Object entity : entities) {
                ClassMetadata classMetadata = getClassMetadata(entity.getClass());
                Mutator<String> mutator = getMutator(classMetadata);
                fireEntityEvent(Event.Entity.PRE_DELETE, entity);
                String colFamily = classMetadata.getColumnFamily();
                String key = getKey(entity);
                if (key == null) {
                    log.warn(String.format("entity: %s had no value for key and was ignored", entity));
                } else {
                    mutator.addDeletion(key, colFamily);
                    mutator.execute();
                    fireEntityEvent(Event.Entity.POST_DELETE, entity);
                }
            }
        } catch (Throwable t) {
            throw new RuntimeException(t);
        }
        log.debug(String.format("END remove(%s)", Arrays.toString(entities)));
    }

    /* Misc */

    /**
     * Destroy method that shutdowns this factory
     *
     * @see org.firebrandocm.dao.AbstractPersistenceFactory#destroy()
     */
    public void destroy() {
        if (isDropOnDestroy()) {
            executeQuery(Void.class, Query.get(drop(keySpace(getDefaultKeySpace()))));
        }
        HFactory.shutdownCluster(cluster);
        super.destroy();
    }

    /**
     * Executes a query
     *
     * @param expectedResult the result expected from the query execution
     * @param query          the query
     * @param <T>            the result type
     * @return the result
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> T executeQuery(Class<T> expectedResult, Query query) {
        return (T) getCQLExecuteQuery(query.getQuery()).execute();
    }

    /**
     * Private helper that returns a CqlExecuteQuery
     * @param query the query
     * @param <T>  the entity type
     * @param <V> the value type
     * @return the CqlQuery
     */
    protected <T, V> CqlExecuteQuery<V> getCQLExecuteQuery(String query) {
        CqlExecuteQuery<V> cqlQuery = new CqlExecuteQuery<V>(getDefaultKeyspace());
        cqlQuery.setQuery(query);
        return cqlQuery;
    }

    /**
     * Private helper to obtain a CqlQuery given an entity class type and a cql query
     * @param type the class type
     * @param query the cql query
     * @param <T>  The type of entity
     * @param <V>  The type of value
     * @return a CqlQuery
     */
    protected <T, V> CqlQuery<String, String, V> getCQLQuery(Class<T> type, String query) {
        ClassMetadata classMetadata = getClassMetadata(type);
        Keyspace keyspace;
        if (classMetadata == null) { //this is not a managed class such as requesting a long for now as workaround the first keyspace will be selected //todo change in the future to be able to pass akeyspace
            keyspace = getDefaultKeyspace();
        } else {
            keyspace = getKeyspace(classMetadata);
        }
        CqlQuery<String, String, V> cqlQuery = new CqlQuery<String, String, V>(keyspace, StringSerializer.get(), StringSerializer.get(), new TypeConverterSerializer<V>());
        cqlQuery.setQuery(query);
        cqlQuery.setSuppressKeyInColumns(true);
        return cqlQuery;
    }

    /**
     * Fetch a map of columns and their values
     *
     * @param query a cql query
     * @return the resulting columns and their values
     */
    protected Map<String, ByteBuffer> getColumns(String query) {
        Map<String, ByteBuffer> resultMap = new LinkedHashMap<String, ByteBuffer>();
        CqlQuery<String, String, Object> cqlQuery = new CqlQuery<String, String, Object>(getDefaultKeyspace(), StringSerializer.get(), StringSerializer.get(), new TypeConverterSerializer<Object>());
        cqlQuery.setQuery(query);
        cqlQuery.setSuppressKeyInColumns(true);
        QueryResult<CqlRows<String, String, Object>> results = cqlQuery.execute();
        CqlRows<String, String, Object> rows = results.get();
        for (Row<String, String, Object> row : rows) {
            ColumnSlice<String, Object> slice = row.getColumnSlice();
            for (HColumn<String, Object> column : slice.getColumns()) {
                resultMap.put(column.getName(), column.getValueBytes());
            }
        }
        return resultMap;
    }

    /**
     *
     * @return the default keyspace
     */
    private Keyspace getDefaultKeyspace() {
        return HFactory.createKeyspace(getDefaultKeySpace(), cluster);
    }

    /**
     * Initializes the factory
     */
    public synchronized void init() throws Exception {
        super.init();
        log.debug("initializing factory");
        //favor an existing cassandraHostConfigurator
        if (cassandraHostConfigurator == null) {
            cassandraHostConfigurator = new CassandraHostConfigurator();
            cassandraHostConfigurator.setPort(getThriftPort());
            cassandraHostConfigurator.setHosts(StringUtils.join(getContactNodes(), ','));
            cassandraHostConfigurator.setAutoDiscoverHosts(autoDiscoverHosts);
        }
        cluster = HFactory.getOrCreateCluster(clusterName, cassandraHostConfigurator, credentials);
        initializeTypeConverters();
        initializeKeyspaceDefinitions();
        initializeSchema();
        log.debug("factory initialized");
    }

    /**
     * Private helper to initialize the schema
     */
    protected void initializeSchema() throws Exception {
        if (keyspaceDefinitions.size() == 0)
            throw new IllegalStateException("no keyspace definitions founds, maybe add some entities to the factory");
        for (KeyspaceDefinition keyspaceDefinition : ThriftKsDef.fromThriftList(new ArrayList<KsDef>(keyspaceDefinitions.values()))) {
            KeyspaceDefinition existingKeyspace = cluster.describeKeyspace(keyspaceDefinition.getName());
            if (existingKeyspace == null) {
                cluster.addKeyspace(keyspaceDefinition, true);
            } else {
                for (ColumnFamilyDefinition columnFamilyDefinition : keyspaceDefinition.getCfDefs()) {
                    if (keyspaceContainsColumnFamily(columnFamilyDefinition, existingKeyspace)) {
                        log.debug(String.format("found column family %s, updating schema", columnFamilyDefinition.getName()));
                        ColumnFamilyDefinition existingColumnFamilyDefinition = getColumnFamilyFromKeyspace(columnFamilyDefinition.getName(), existingKeyspace);
                        existingColumnFamilyDefinition.getColumnMetadata().clear();
                        for (ColumnDefinition columnDefinition : columnFamilyDefinition.getColumnMetadata()) {
                            existingColumnFamilyDefinition.addColumnDefinition(columnDefinition);
                        }
                        cluster.updateColumnFamily(existingColumnFamilyDefinition, true);
                    } else {
                        log.debug(String.format("not found column family %s, adding to schema", columnFamilyDefinition.getName()));
                        cluster.addColumnFamily(columnFamilyDefinition, true);
                    }
                }
            }
        }
    }

    /**
     * Private helper to determine if a column family has already been created in a keyspace
     *
     * @param columnFamilyDefinition the column family definition
     * @param keyspaceDefinition     the keyspace definition
     * @return true if the column family is already present in the keyspace
     */
    protected boolean keyspaceContainsColumnFamily(ColumnFamilyDefinition columnFamilyDefinition, KeyspaceDefinition keyspaceDefinition) {
        boolean contains = false;
        for (ColumnFamilyDefinition columnFamilyDefinitionEntry : keyspaceDefinition.getCfDefs()) {
            if (columnFamilyDefinitionEntry.getName().equals(columnFamilyDefinition.getName())) {
                contains = true;
                break;
            }
        }
        return contains;
    }

    /**
     * Private helper to obtain a column family from a keyspace by name
     *
     * @param name               the column family name
     * @param keyspaceDefinition the keyspace
     * @return the column family if found, null otherwise
     */
    protected ColumnFamilyDefinition getColumnFamilyFromKeyspace(String name, KeyspaceDefinition keyspaceDefinition) {
        ColumnFamilyDefinition definition = null;
        for (ColumnFamilyDefinition columnFamilyDefinitionEntry : keyspaceDefinition.getCfDefs()) {
            if (columnFamilyDefinitionEntry.getName().equals(name)) {
                definition = columnFamilyDefinitionEntry;
                break;
            }
        }
        return definition;
    }

    /**
     * Loads a lazy property's value
     *
     * @param metadata the entity metadata
     * @param self     the entity instance
     * @param proceed  the method being intercepted
     * @param m
     * @param args     the method arguments
     */
    @Override
    protected <T> void loadLazyPropertyIfNecessary(ClassMetadata<T> metadata, Object self, Method proceed, Method m, Object[] args) throws Exception {
        Object value = proceed.invoke(self, args);
        String key = getKey(self);
        if (key != null) { //key may be null if this is just a regular access to the property before the entity has been persisted and no key has been assigned
            SliceQuery<String, String, Object> query = getSliceQuery(metadata);
            query.setColumnFamily(metadata.getColumnFamily());
            query.setKey(key);
            String column = metadata.getLazyProperty(m);
            query.setColumnNames(column);
            List<HColumn<String, Object>> columns = query.execute().get().getColumns();
            HColumn<String, Object> mappedColumnValue = columns.size() == 1 ? columns.get(0) : null;
            if (mappedColumnValue != null && isEmptyContainerValue(value)) { //todo once a load attempt has been made we should not attempt again but we have no sessions...perhaps a weakreference map?
                Object propertyValue = loadProperty(metadata, column, mappedColumnValue);
                PropertyUtils.setProperty(self, column, propertyValue);
            }
        }
    }

    /**
     * Private helper to get a slice query given a class metadata
     * @param classMetadata the class metadata
     * @param <V> the value type
     * @return a slice query
     */
    private <V> SliceQuery<String, String, V> getSliceQuery(ClassMetadata<?> classMetadata) {
        return HFactory.createSliceQuery(getKeyspace(classMetadata), StringSerializer.get(), StringSerializer.get(), new TypeConverterSerializer<V>());
    }

    /**
     * Gets a keyspace associated with a class metadata
     * @param classMetadata the class metadata
     * @return the keyspace
     */
    private Keyspace getKeyspace(ClassMetadata<?> classMetadata) {
        String keySpace = classMetadata.getKeySpace();
        keySpace = keySpace != null ? keySpace : getDefaultKeySpace();
        Keyspace keyspace = HFactory.createKeyspace(keySpace, cluster, getColumnFamilyConsistencyLevel(classMetadata));
        return keyspace;
    }

    /**
     * Get the default consistency level for a keyspace given the class metadata
     * @param classMetadata the class metadata
     * @return the configured or default consistency level
     */
    private ColumnFamilyConsistencyLevel getColumnFamilyConsistencyLevel(ClassMetadata<?> classMetadata) {
        return new ColumnFamilyConsistencyLevel(classMetadata.getConsistencyLevel());
    }

    /**
     * Private helper to determine if a container is empty
     *
     * @param value the container
     * @return if the container is empty
     */
    private boolean isEmptyContainerValue(Object value) {
        boolean empty = value == null;
        if (!empty) {
            if (Collections.class.isAssignableFrom(value.getClass())) {
                empty = ((Collection) value).isEmpty();
            }
        }
        return empty;
    }

    /**
     * private helper to persist and arbitrary list of objects into the datastore
     *
     * @param entities
     */
    private void persistAll(Object... entities) {
        log.debug(String.format("persist(%s) { ", Arrays.toString(entities)));
        for (Object entity : entities) {
            persistEntity(entity);
        }
        log.debug(String.format("} (%s)", Arrays.toString(entities)));
    }

    /**
     * helper that persists a single an entity in the data store
     *
     * @param entity
     */
    protected void persistEntity(Object entity) {
        fireEntityEvent(Event.Entity.PRE_PERSIST, entity);
        ClassMetadata<?> classMetadata = getClassMetadata(entity.getClass());
        Mutator<String> mutator = getMutator(classMetadata);
        String key = getKey(entity);
        for (String property : classMetadata.getMutationProperties()) {
            persistPropertyIfNecessary(mutator, key, classMetadata, entity, property);
        }
        mutator.execute();
        fireEntityEvent(Event.Entity.POST_PERSIST, entity);
    }

    /**
     * Given a classMetadata obtain a mutator
     * @param classMetadata the class metadata
     * @return the mutator
     */
    private Mutator<String> getMutator(ClassMetadata classMetadata) {
        String keySpace = classMetadata.getKeySpace();
        keySpace = keySpace != null ? keySpace : getDefaultKeySpace();
        Keyspace keyspace = HFactory.createKeyspace(keySpace, cluster, getColumnFamilyConsistencyLevel(classMetadata));
        return HFactory.createMutator(keyspace, StringSerializer.get(), new BatchSizeHint(1, classMetadata.getMutationProperties().size()));
    }

    /**
     * helper that persist an entity property
     *
     * @param mutator       the current operation mutator
     * @param key           the column key
     * @param classMetadata the class metadata
     * @param entity        the entity
     * @param property      the property being persisted
     */
    protected void persistPropertyIfNecessary(Mutator<String> mutator, String key, ClassMetadata<?> classMetadata, Object entity, String property) {
        if (!property.equals(classMetadata.getKeyProperty())) {
            try {
                Object value;
                if (property.equals(CLASS_PROPERTY)) { //if this is a special case class property
                    value = classMetadata.getTarget().getName();
                } else { //otherwise proceed with serialization
                    value = PropertyUtils.getProperty(entity, property);
                    if (classMetadata.isMappedContainer(property)) { //we have a mapped property so we set its class:key as the value
                        value = getMappedPropertyTokenForPersistentValues(value);
                    } else if (classMetadata.isMappedCollection(property)) { //we have a mapped property so we set its key as the value
                        value = getMappedPropertyTokenForPersistentValues(value);
                    }
                }
                update(mutator, key, entity, property, value);
            } catch (NestedNullException nestedNullException) {
                String colFamily = classMetadata.getColumnFamily();
                if (key == null) {
                    log.warn(String.format("entity: %s had no value for key and was ignored", entity));
                } else {
                    fireColumnEvent(Event.Column.PRE_COLUMN_DELETION, entity, property, key, colFamily, null);
                    mutator.addDeletion(key, colFamily, property, StringSerializer.get());
                    log.debug(String.format("\tD:  %s ", property));
                    fireColumnEvent(Event.Column.POST_COLUMN_DELETION, entity, property, key, colFamily, null);
                }
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * Invoked when a setter is invoked in any entity changing a property value
     *
     * @param entity   the entity being invoked
     * @param property the property most likely to map to a column name
     * @param value    the new value for the property, null usually handled as deletions of that key column association
     */
    private void update(Mutator<String> mutator, String key, Object entity, String property, Object value) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException {
        ClassMetadata metadata = getClassMetadata(entity.getClass());
        if (!metadata.getKeyProperty().equals(property)) { //there is no point to create a column for the key
            updateWithMetadata(mutator, key, metadata, property, value, entity);
        }
    }

    /**
     * Private helper that delegates to updateSimpleColumn
     */
    private void updateWithMetadata(Mutator<String> mutator, String key, ClassMetadata metadata, String property, Object value, Object entity) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException {
        updateSimpleColumn(mutator, key, metadata, property, value, entity);
    }

    /**
     * Private helper that updates a simple column
     *
     * @param mutator  the operation mutator
     * @param key      the column key
     * @param metadata the class metadata associated to the entity
     * @param property the property represented by this column
     * @param value    the value
     * @param entity   the entity the property belongs to
     */
    protected void updateSimpleColumn(Mutator<String> mutator, String key, ClassMetadata metadata, String property, Object value, Object entity) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException {
        String colFamily = metadata.getColumnFamily();
        if (!metadata.isAssociationContainer(property)) { //association container properties are ignored since their embedded properties are flatten into the column family
            if (value == null) {
                if (!metadata.isContainer(property)) { //mapped properties are removed when loading a property with id that returns null
                    fireColumnEvent(Event.Column.PRE_COLUMN_DELETION, entity, property, key, colFamily, null);
                    mutator.addDeletion(key, colFamily, property, StringSerializer.get());
                    log.debug(String.format("\tD: %s", property));
                    fireColumnEvent(Event.Column.POST_COLUMN_DELETION, entity, property, key, colFamily, null);
                }
            } else {
                if (metadata.isCounterIncreaseProperty(property)) { //a counter property increase
                    Long counterIncreaseValue = (Long) value;
                    //if (counterIncreaseValue != 0) {
                    String targetCounterProperty = metadata.getTargetCounterProperty(property);
                    fireColumnEvent(Event.Column.PRE_COUNTER_MUTATION, entity, property, key, colFamily, null);
                    mutator.incrementCounter(key, colFamily, targetCounterProperty, counterIncreaseValue);
                    log.debug(String.format("C: %s increments to %d", targetCounterProperty, counterIncreaseValue));
                    //once applied the increase the counter increase value gets reset to 0
                    PropertyUtils.setProperty(entity, property, 0L);
                    fireColumnEvent(Event.Column.POST_COUNTER_MUTATION, entity, property, key, colFamily, null);
                    //}
                } else if (!metadata.isCounterProperty(property)) {  //a regular column update, counter are ignored since they're just serialized
                    HColumn<String, Object> column = createColumn(property, value, StringSerializer.get(), new TypeConverterSerializer<Object>(value));
                    fireColumnEvent(Event.Column.PRE_COLUMN_MUTATION, entity, property, key, colFamily, column);
                    mutator.addInsertion(key, colFamily, column);
                    log.debug(String.format("\tI: %s : %s ", property, value));
                    fireColumnEvent(Event.Column.POST_COLUMN_MUTATION, entity, property, key, colFamily, column);
                }
            }
            //log.debug(String.format("batched mutation for colFamily: %s, key: %s, col: %s, val: %s", colFamily, key, property, value));
        }
    }

    /**
     * Private helper that hidrates an entity from a list of columns in the datastore
     *
     * @param key              the key
     * @param metadata        the entity metadata
     * @param entityClass     the entity class
     * @param columns         the list of columns to set in the entity proeprties
     * @param instance        the entity instance
     * @param ignoreLazyFlags whether lazy flags in @Column annotations should be ignored for this operation
     * @param <T>             the entity type
     * @return the hidrated entity
     */
    private <T> T serializeColumns(String key, ClassMetadata<?> metadata, Class<T> entityClass, List<HColumn<String, Object>> columns, T instance, boolean ignoreLazyFlags) throws Exception {
        instance = getInstance(entityClass, instance);
        for (HColumn<String, Object> column : columns) {
            String name = column.getName();
            serializeColumn(metadata, instance, name, column, ignoreLazyFlags);
        }
        PropertyUtils.setProperty(instance, metadata.getKeyProperty(), key);
        return instance;
    }

    /**
     * Private helper that serializes a column to an entity property
     *
     * @param metadata        the class metadata
     * @param instance        the entity instance
     * @param name            the property name
     * @param column          the column object
     * @param ignoreLazyFlags whether lazy flags in @Column annotations should be ignored for this operation
     */
    protected void serializeColumn(ClassMetadata<?> metadata, Object instance, String name, HColumn<String, Object> column, boolean ignoreLazyFlags) throws Exception {
        if (!name.equals(CLASS_PROPERTY) && metadata.getSelectionProperties().contains(name)) { //ignore the class type property while deserializing
            if (ignoreLazyFlags || !metadata.isLazyProperty(name)) {
                if ("KEY".equals(name)) {
                    name = metadata.getKeyProperty();
                }
                Object value = loadProperty(metadata, name, column);
                try {
                    instantiateContainersIfNecessary(metadata, instance, name);
                    PropertyUtils.setProperty(instance, name, value);
                } catch (Throwable e) {
                    throw new UnsupportedOperationException(e);
                }
            }
        }
    }

    /* Inner Classes */

    /**
     * Private helper to convert from the data store type to java types and viceversa.
     * It delegates its operations to AbstractPersistenceFactory#convertRead and AbstractPersistenceFactory#convertWrite
     * @param <Type>
     */
    public final class TypeConverterSerializer<Type> extends AbstractSerializer<Type> {
        /**
         * The target class
         */
        private Class<?> target;

        /**
         * Default constructor
         */
        public TypeConverterSerializer() {
        }

        /**
         * Constructor based on a target object
         * @param targetObject the target object
         */
        public TypeConverterSerializer(Type targetObject) {
            this.target = targetObject != null ? targetObject.getClass() : null;
        }

        /**
         * Constructor based on target object class
         * @param target the target object class
         */
        public TypeConverterSerializer(Class<Type> target) {
            this.target = target;
        }

        /**
         * @see AbstractSerializer#toByteBuffer(Object)
         */
        @Override
        public ByteBuffer toByteBuffer(Object obj) {
            try {
                return convertWrite(obj);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * @see AbstractSerializer#fromByteBuffer(java.nio.ByteBuffer)
         */
        @Override
        @SuppressWarnings("unchecked")
        public Type fromByteBuffer(ByteBuffer byteBuffer) {
            try {
                return (Type) convertRead(target, byteBuffer);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * A builder for this factory impl
     */
    public static final class Builder {

        private HectorPersistenceFactory delegate;

        public Builder() {
            delegate = new HectorPersistenceFactory();
        }

        public Builder autoDiscoverHosts(boolean autoDiscoverHosts) {
            delegate.setAutoDiscoverHosts(autoDiscoverHosts);
            return this;
        }

        public Builder clusterName(String clusterName) {
            delegate.setClusterName(clusterName);
            return this;
        }

        public Builder credentials(Map<String, String> credentials) {
            delegate.setCredentials(credentials);
            return this;
        }

        public Builder contactNodes(String[] contactNodes) {
            delegate.setContactNodes(contactNodes);
            return this;
        }

        public Builder defaultConsistencyLevel(ConsistencyLevel defaultConsistencyLevel) {
            delegate.setDefaultConsistencyLevel(defaultConsistencyLevel);
            return this;
        }

        public Builder defaultKeySpace(String defaultKeySpace) {
            delegate.setDefaultKeySpace(defaultKeySpace);
            return this;
        }

        public Builder poolName(String poolName) {
            delegate.setPoolName(poolName);
            return this;
        }

        public Builder thriftPort(int thriftPort) {
            delegate.setThriftPort(thriftPort);
            return this;
        }

        public Builder typeConverters(Map<Class<?>, TypeConverter<?>> typeConverters) {
            delegate.setTypeConverters(typeConverters);
            return this;
        }

        public Builder debug(boolean debug) {
            delegate.setDebug(debug);
            return this;
        }

        public Builder deleteIfNull(boolean deleteIfNull) {
            delegate.setDeleteIfNull(deleteIfNull);
            return this;
        }

        public Builder dropOnDestroy(boolean dropOnDestroy) {
            delegate.setDropOnDestroy(dropOnDestroy);
            return this;
        }

        public Builder columnEventListenerMap(Map<Event.Column, List<ColumnEventListener>> columnEventListenerMap) {
            delegate.setColumnEventListenerMap(columnEventListenerMap);
            return this;
        }

        public Builder embeddedServerBaseDir(String embeddedServerBaseDir) {
            delegate.setEmbeddedServerBaseDir(embeddedServerBaseDir);
            return this;
        }

        public Builder entityEventListenerMap(Map<Event.Entity, List<EntityEventListener>> entityEventListenerMap) {
            delegate.setEntityEventListenerMap(entityEventListenerMap);
            return this;
        }

        public Builder replicationFactor(int replicationFactor) {
            delegate.setReplicationFactor(replicationFactor);
            return this;
        }

        public Builder startEmbeddedServer(boolean startEmbeddedServer) {
            delegate.setStartEmbeddedServer(startEmbeddedServer);
            return this;
        }

        public Builder entities(List<Class<?>> entities) {
            delegate.setEntities(entities);
            return this;
        }

        public Builder cassandraHostConfigurator(CassandraHostConfigurator cassandraHostConfigurator) {
            delegate.setCassandraHostConfigurator(cassandraHostConfigurator);
            return this;
        }

        public HectorPersistenceFactory build() throws Exception {
            delegate.init();
            return delegate;
        }

    }
}
TOP

Related Classes of org.firebrandocm.dao.impl.hector.HectorPersistenceFactory$Builder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.