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

com.impetus.client.cassandra.query.ResultIterator Maven / Gradle / Ivy

There is a newer version: 3.13
Show newest version
/**
 * Copyright 2013 Impetus Infotech.
 *
 * 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 com.impetus.client.cassandra.query;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;

import org.apache.cassandra.thrift.IndexClause;
import org.apache.cassandra.thrift.IndexExpression;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.impetus.client.cassandra.CassandraClientBase;
import com.impetus.client.cassandra.common.CassandraUtilities;
import com.impetus.client.cassandra.index.CassandraIndexHelper;
import com.impetus.client.cassandra.thrift.CQLTranslator;
import com.impetus.kundera.client.Client;
import com.impetus.kundera.client.ClientBase;
import com.impetus.kundera.client.EnhanceEntity;
import com.impetus.kundera.metadata.MetadataUtils;
import com.impetus.kundera.metadata.model.ApplicationMetadata;
import com.impetus.kundera.metadata.model.EntityMetadata;
import com.impetus.kundera.metadata.model.MetamodelImpl;
import com.impetus.kundera.metadata.model.attributes.AbstractAttribute;
import com.impetus.kundera.persistence.EntityManagerFactoryImpl.KunderaMetadata;
import com.impetus.kundera.persistence.EntityReader;
import com.impetus.kundera.property.PropertyAccessorHelper;
import com.impetus.kundera.query.IResultIterator;
import com.impetus.kundera.query.KunderaQuery.FilterClause;
import com.impetus.kundera.query.QueryImpl;
import com.impetus.kundera.utils.ReflectUtils;

/**
 * @author vivek.mishra .
 * 
 *         Implementation of Cassandra result iteration.
 * 
 *         TODO::: Need to add support for relational entities and a junit for
 *         Composite key test
 */
class ResultIterator implements IResultIterator
{
    private static Logger log = LoggerFactory.getLogger(ResultIterator.class);

    private final CassQuery query;

    private final EntityMetadata entityMetadata;

    private final Client client;

    private final EntityReader reader;

    private int maxResult = 1;

    private List results;

    private byte[] start;

    private static final String MIN_ = "min";

    private static final String MAX_ = "max";

    private int fetchSize;

    private int count;

    private boolean scrollComplete;

    private Map externalProperties;

    private E current;

    private final KunderaMetadata kunderaMetadata;

    /**
     * Constructor with parameters
     * 
     * @param query
     * @param m
     * @param client
     * @param reader
     * @param fetchSize
     */
    ResultIterator(final Query query, final EntityMetadata m, final Client client, final EntityReader reader,
            final int fetchSize, final KunderaMetadata kunderaMetadata)
    {
        this.client = client;
        this.query = (CassQuery) query;
        this.entityMetadata = m;
        this.reader = reader;
        this.scrollComplete = false;
        this.fetchSize = fetchSize;
        this.kunderaMetadata = kunderaMetadata;
    }

    @Override
    public boolean hasNext()
    {
        if (checkOnFetchSize())
        {
            onCheckRelation();
            if (!checkOnEmptyResult())
            {
                scrollComplete = true;
                return false;
            }
            return true;
        }
        return false;
    }

    @Override
    public E next()
    {
        if (current != null && checkOnEmptyResult() && current.equals(results.get(results.size() - 1)))
        {
            hasNext();
        }

        if (scrollComplete)
        {
            throw new NoSuchElementException("Nothing to scroll further for:" + entityMetadata.getEntityClazz());
        }

        E lastFetchedEntity = getEntity(results.get(results.size() - 1));
        start = lastFetchedEntity != null ? idValueInByteArr() : null;
        current = getEntity(results.get(results.size() - 1));

        return current;
    }

    @Override
    public void remove()
    {
        throw new UnsupportedOperationException("remove method is not supported over pagination");
    }

    @Override
    public List next(int chunkSize)
    {
        throw new UnsupportedOperationException("fetch in chunks is not yet supported");
    }

    /**
     * Check on fetch size. returns true, if count on fetched rows is less than
     * fetch size.
     * 
     * @return
     */
    private boolean checkOnFetchSize()
    {
        if (count++ < fetchSize)
        {
            return true;
        }
        count = 0;
        scrollComplete = true;
        return false;
    }

    /**
     * on check relation event, invokes populate entities and set relational
     * entities, in case relations are present.
     */
    private void onCheckRelation()
    {
        try
        {
            results = populateEntities(entityMetadata, client);

            if (entityMetadata.isRelationViaJoinTable()
                    || (entityMetadata.getRelationNames() != null && !(entityMetadata.getRelationNames().isEmpty())))
            {
                query.setRelationalEntities(results, client, entityMetadata);
            }
        }
        catch (Exception e)
        {
            throw new PersistenceException("Error while scrolling over results, Caused by :.", e);
        }
    }

    /**
     * Method parse provided JPQL query into: 1. CQL3 query, in case cql3 is
     * enabled or is a native query. 2. list of index clause, if cql2 is
     * enabled. Then executes query for given min & max values for scrolling
     * over results.
     * 
     * @param m
     *            entity metadata
     * @param client
     *            client
     * @return list of database values wrapped into entities.
     * @throws Exception
     *             throws exception, in case of run time error.
     */
    private List populateEntities(EntityMetadata m, Client client) throws Exception
    {
        if (log.isDebugEnabled())
        {
            log.debug("Populating entities for Cassandra query {}.", ((QueryImpl) query).getJPAQuery());
        }
        List result = new ArrayList();
        ApplicationMetadata appMetadata = kunderaMetadata.getApplicationMetadata();
        externalProperties = ((CassandraClientBase) client).getExternalProperties();

        // if id attribute is embeddable, it is meant for CQL translation.
        // make it independent of embedded stuff and allow even to add non
        // composite into where clause and let cassandra complain for it.

        MetamodelImpl metaModel = (MetamodelImpl) kunderaMetadata.getApplicationMetadata().getMetamodel(
                m.getPersistenceUnit());

        String queryString = appMetadata.getQuery(((QueryImpl) query).getJPAQuery());

        boolean isNative = ((CassQuery) query).isNative();

        if (((CassandraClientBase) client).isCql3Enabled(m))
        {
            String parsedQuery = query.onQueryOverCQL3(m, client, metaModel, null);

            parsedQuery = appendWhereClauseWithScroll(parsedQuery);
            results = parsedQuery != null ? ((CassandraClientBase) client).executeQuery(m.getEntityClazz(),
                    m.getRelationNames(), isNative, parsedQuery) : null;
        }
        else
        {
            // Index in Inverted Index table if applicable
            boolean useInvertedIndex = CassandraIndexHelper.isInvertedIndexingApplicable(m,
                    MetadataUtils.useSecondryIndex(((ClientBase) client).getClientMetadata()));
            Map> ixClause = query.prepareIndexClause(m, useInvertedIndex);
            if (useInvertedIndex && !((QueryImpl) query).getKunderaQuery().getFilterClauseQueue().isEmpty())
            {
                result = (List) ((CassandraEntityReader) this.reader).readFromIndexTable(m, client, ixClause);
            }
            else
            {
                boolean isRowKeyQuery = ixClause.keySet().iterator().next();

                List expressions = !ixClause.get(isRowKeyQuery).isEmpty() ? ixClause
                        .get(isRowKeyQuery).get(0).getExpressions() : null;

                Map rowKeys = ((CassandraEntityReader) this.reader).getRowKeyValue(expressions,
                        ((AbstractAttribute) m.getIdAttribute()).getJPAColumnName());

                byte[] minValue = start == null ? rowKeys.get(MIN_) : start;
                byte[] maxVal = rowKeys.get(MAX_);

                results = ((CassandraClientBase) client).findByRange(minValue, maxVal, m, m.getRelationNames() != null
                        && !m.getRelationNames().isEmpty(), m.getRelationNames(),
                        query.getColumnList(m, metaModel, ((QueryImpl) query).getKunderaQuery().getResult(), null),
                        expressions, maxResult);

                if (maxResult == 1)
                {
                    maxResult++;
                }
                else if (maxResult > 1 && checkOnEmptyResult() && maxResult != results.size())
                {
                    // means iterating over last record only, so need for
                    // database trip anymore!.
                    results = null;
                }
            }
        }
        return results;
    }

    /**
     * Appends where claues and prepare for next fetch. Method to be called in
     * case cql3 enabled.
     * 
     * @param parsedQuery
     *            parsed query.
     * 
     * @return cql3 query to be executed.
     */
    private String appendWhereClauseWithScroll(String parsedQuery)
    {

        String queryWithoutLimit = parsedQuery.replaceAll(
                parsedQuery.substring(parsedQuery.lastIndexOf(CQLTranslator.LIMIT), parsedQuery.length()), "");

        CQLTranslator translator = new CQLTranslator();

        final String tokenCondition = prepareNext(translator, queryWithoutLimit);

        StringBuilder builder = new StringBuilder(queryWithoutLimit);

        if (tokenCondition != null)
        {
            if (query.getKunderaQuery().getFilterClauseQueue().isEmpty())
            {
                builder.append(CQLTranslator.ADD_WHERE_CLAUSE);
            }
            else
            {
                builder.append(CQLTranslator.AND_CLAUSE);
            }
            builder.append(tokenCondition);
        }

        String replaceQuery = replaceAndAppendLimit(builder.toString());
        builder.replace(0, builder.toString().length(), replaceQuery);
        translator.buildFilteringClause(builder);

        // in case of fetch by ID, token condition will be null and results will
        // not be empty.
        return checkOnEmptyResult() && tokenCondition == null ? null : builder.toString();
    }

    /**
     * Replace and append limit.
     * 
     * @param parsedQuery
     *            parsed cql3 query.
     * 
     * @return cql3 query appended with limit clause.
     */
    private String replaceAndAppendLimit(String parsedQuery)
    {
        StringBuilder builder = new StringBuilder(parsedQuery);
        onLimit(builder);
        parsedQuery = builder.toString();
        return parsedQuery;
    }

    /**
     * Append limit to sql3 query.
     * 
     * @param builder
     *            builder instance.
     */
    private void onLimit(StringBuilder builder)
    {
        builder.append(CQLTranslator.LIMIT);
        builder.append(this.maxResult);
    }

    /**
     * Parse and append cql3 token function for iter.next() call.
     * 
     * @param translator
     *            cql translator.
     * 
     * @return parsed/append cql3 query.
     */
    private String prepareNext(CQLTranslator translator, String query)
    {
        if (checkOnEmptyResult())
        {
            String idName = ((AbstractAttribute) entityMetadata.getIdAttribute()).getJPAColumnName();
            Map filterOnId = getConditionOnIdColumn(idName);

            if (filterOnId.get(true) != null)
            {
                String condition = filterOnId.get(true);
                // means id clause present in query.
                // now if id attribute is embeddable then fetch partition key
                // for token
                // if condition is with equals then no need to go for another
                // fetch.

                if (condition.equals("="))
                {
                    // no need to fetch another record, as there will be only
                    // one
                    return null;
                }
                else if (condition.endsWith(">") || condition.equals(">="))
                {
                    query = replaceAppliedToken(query);
                    return query;
                }
            }

            // Means there is an previous entity.
            Object entity = results.get(results.size() - 1);
            Class idClazz = ((AbstractAttribute) entityMetadata.getIdAttribute()).getBindableJavaType();
            Object id = PropertyAccessorHelper.getId(entity, entityMetadata);
            StringBuilder builder = new StringBuilder(CQLTranslator.TOKEN);
            MetamodelImpl metaModel = (MetamodelImpl) kunderaMetadata.getApplicationMetadata().getMetamodel(
                    entityMetadata.getPersistenceUnit());

            EmbeddableType keyObj = null;
            // Bytes bytes = null;
            String columnName;
            if (metaModel.isEmbeddable(entityMetadata.getIdAttribute().getBindableJavaType()))
            {
                keyObj = metaModel.embeddable(entityMetadata.getIdAttribute().getBindableJavaType());
                Field embeddedField = getPartitionKeyField();
                Attribute partitionKey = keyObj.getAttribute(embeddedField.getName());
                Object partitionKeyValue = PropertyAccessorHelper.getObject(id, (Field) partitionKey.getJavaMember());
                columnName = ((AbstractAttribute) partitionKey).getJPAColumnName();
                id = partitionKeyValue;
                idClazz = ((AbstractAttribute) partitionKey).getBindableJavaType();

            }
            else
            {
                columnName = CassandraUtilities.getIdColumnName(kunderaMetadata, entityMetadata, externalProperties,
                        ((CassandraClientBase) client).isCql3Enabled(entityMetadata));
            }

            translator.appendColumnName(builder, columnName);
            builder.append(CQLTranslator.CLOSE_BRACKET);
            builder.append(" > ");
            builder.append(CQLTranslator.TOKEN);
            translator.appendValue(builder, idClazz, id, false, false);
            builder.append(CQLTranslator.CLOSE_BRACKET);
            return builder.toString();
        }
        return null;
    }

    /**
     * 
     * @param idColumn
     * @return
     */
    private Map getConditionOnIdColumn(String idColumn)
    {

        Map filterIdResult = new HashMap();

        MetamodelImpl metaModel = (MetamodelImpl) kunderaMetadata.getApplicationMetadata().getMetamodel(
                entityMetadata.getPersistenceUnit());

        EmbeddableType keyObj = null;
        if (metaModel.isEmbeddable(entityMetadata.getIdAttribute().getBindableJavaType()))
        {
            keyObj = metaModel.embeddable(entityMetadata.getIdAttribute().getBindableJavaType());
        }

        for (Object o : query.getKunderaQuery().getFilterClauseQueue())
        {
            if (o instanceof FilterClause)
            {
                FilterClause clause = ((FilterClause) o);
                String fieldName = clause.getProperty();
                String condition = clause.getCondition();

                if (keyObj != null && fieldName.equals(idColumn)
                        || (keyObj != null && StringUtils.contains(fieldName, '.')) || (idColumn.equals(fieldName)))
                {
                    filterIdResult.put(true, condition);
                    break;
                }
            }
        }
        return filterIdResult;
    }

    private byte[] idValueInByteArr()
    {
        Object entity = results.get(results.size() - 1);
        Object id = PropertyAccessorHelper.getId(entity, entityMetadata);
        String idName = ((AbstractAttribute) entityMetadata.getIdAttribute()).getJPAColumnName();
        Class idClazz = ((AbstractAttribute) entityMetadata.getIdAttribute()).getBindableJavaType();
        MetamodelImpl metaModel = (MetamodelImpl) kunderaMetadata.getApplicationMetadata().getMetamodel(
                entityMetadata.getPersistenceUnit());

        EmbeddableType keyObj = null;
        ByteBuffer bytes = null;

        // if the key attribute is composite
        if (metaModel.isEmbeddable(entityMetadata.getIdAttribute().getBindableJavaType()))
        {
            keyObj = metaModel.embeddable(entityMetadata.getIdAttribute().getBindableJavaType());
            Field embeddedField = getPartitionKeyField();
            Attribute partitionKey = keyObj.getAttribute(embeddedField.getName());
            Object partitionKeyValue = PropertyAccessorHelper.getObject(id, (Field) partitionKey.getJavaMember());
            bytes = CassandraUtilities.toBytes(partitionKeyValue, (Field) partitionKey.getJavaMember());
        }
        else
        {
            bytes = query.getBytesValue(idName, entityMetadata, id);
        }
        return bytes.array();
    }

    /**
     * Will return partition key part of composite id.
     * 
     * @return
     */
    private Field getPartitionKeyField()
    {
        Field[] embeddedFields = entityMetadata.getIdAttribute().getBindableJavaType().getDeclaredFields();

        Field field = null;
        for (Field embeddedField : embeddedFields)
        {
            if (!ReflectUtils.isTransientOrStatic(embeddedField))
            {
                field = embeddedField;
                break;
            }
        }
        return field;
    }

    /**
     * check if result list is null or empty. Returns true, if it is not empty
     * or null.
     * 
     * @return boolean value (true/false).
     * 
     */
    private boolean checkOnEmptyResult()
    {
        return results != null && !results.isEmpty();
    }

    /**
     * Extract wrapped entity object from enhanced entity.
     * 
     * @param entity
     *            enhanced entity.
     * 
     * @return returns extracted instance of E.
     */
    private E getEntity(Object entity)
    {
        return (E) (entity.getClass().isAssignableFrom(EnhanceEntity.class) ? ((EnhanceEntity) entity).getEntity()
                : entity);
    }

    private String replaceAppliedToken(String query)
    {
        final String tokenRegex = "\\btoken\\(";
        final String pattern = "#TOKENKUNDERA#"; // need to replace with this as
                                                 // pattern matcher was
                                                 // returning false.
        query = query.replaceAll(tokenRegex, pattern);

        if (query.indexOf(pattern) > -1) // Means token( has been present and
                                         // replaced.
        {
            CQLTranslator translator = new CQLTranslator();

            int closingIndex = query.indexOf(CQLTranslator.CLOSE_BRACKET, query.lastIndexOf(pattern));

            String object = query.substring(query.lastIndexOf(pattern) + pattern.length(), closingIndex);

            Object entity = results.get(results.size() - 1);
            Class idClazz = ((AbstractAttribute) entityMetadata.getIdAttribute()).getBindableJavaType();
            Object id = PropertyAccessorHelper.getId(entity, entityMetadata);
            StringBuilder builder = new StringBuilder();

            translator.appendValue(builder, idClazz, id, false, false);

            query = query.replaceAll(pattern + object, pattern + builder.toString());
            query = query.replaceAll(pattern, CQLTranslator.TOKEN);
        }
        return query;
    }
    
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy