com.smartitengineering.dao.impl.hbase.CommonDao.java Source code

Java tutorial

Introduction

Here is the source code for com.smartitengineering.dao.impl.hbase.CommonDao.java

Source

/*
 * This is a common dao with basic CRUD operations and is not limited to any
 * persistent layer implementation
 *
 * Copyright (C) 2010  Imran M Yousuf (imyousuf@smartitengineering.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.smartitengineering.dao.impl.hbase;

import com.google.inject.Inject;
import com.google.inject.internal.Nullable;
import com.google.inject.name.Named;
import com.smartitengineering.dao.common.queryparam.BasicCompoundQueryParameter;
import com.smartitengineering.dao.common.queryparam.BiOperandQueryParameter;
import com.smartitengineering.dao.common.queryparam.CompositionQueryParameter;
import com.smartitengineering.dao.common.queryparam.MatchMode;
import com.smartitengineering.dao.common.queryparam.OperatorType;
import com.smartitengineering.dao.common.queryparam.ParameterType;
import com.smartitengineering.dao.common.queryparam.QueryParameter;
import com.smartitengineering.dao.common.queryparam.QueryParameterCastHelper;
import com.smartitengineering.dao.common.queryparam.QueryParameterWithOperator;
import com.smartitengineering.dao.common.queryparam.QueryParameterWithPropertyName;
import com.smartitengineering.dao.common.queryparam.QueryParameterWithValue;
import com.smartitengineering.dao.common.queryparam.ValueOnlyQueryParameter;
import com.smartitengineering.dao.impl.hbase.spi.AsyncExecutorService;
import com.smartitengineering.dao.impl.hbase.spi.Callback;
import com.smartitengineering.dao.impl.hbase.spi.FilterConfig;
import com.smartitengineering.dao.impl.hbase.spi.LockAttainer;
import com.smartitengineering.dao.impl.hbase.spi.LockType;
import com.smartitengineering.dao.impl.hbase.spi.MergeService;
import com.smartitengineering.dao.impl.hbase.spi.ObjectRowConverter;
import com.smartitengineering.dao.impl.hbase.spi.SchemaInfoProvider;
import com.smartitengineering.dao.impl.hbase.spi.impl.DiffBasedMergeService;
import com.smartitengineering.dao.impl.hbase.spi.impl.RangeComparator;
import com.smartitengineering.domain.PersistentDTO;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
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.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.BinaryPrefixComparator;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.FilterList.Operator;
import org.apache.hadoop.hbase.filter.QualifierFilter;
import org.apache.hadoop.hbase.filter.RegexStringComparator;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.filter.SkipFilter;
import org.apache.hadoop.hbase.filter.SubstringComparator;
import org.apache.hadoop.hbase.filter.WritableByteArrayComparable;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A common DAO implementation for HBase. Please note that all parameters for reading (i.e. Scan) assumes that the
 * toString() method returns the string representation of the value to be compared in byte[] form.
 * @author imyousuf
 */
public class CommonDao<Template extends PersistentDTO<? extends PersistentDTO, ? extends Comparable, ? extends Long>, IdType extends Serializable>
        implements com.smartitengineering.dao.common.CommonDao<Template, IdType> {

    public static final int DEFAULT_MAX_ROWS = 1000;
    @Inject
    private ObjectRowConverter<Template> converter;
    @Inject
    private SchemaInfoProvider<Template, IdType> infoProvider;
    @Inject
    private AsyncExecutorService executorService;
    @Inject
    private ExecutorService resultExecutorService;
    @Inject
    @Named("mergeEnabled")
    private Boolean mergeEnabled = false;
    @Inject
    @Nullable
    private MergeService<Template, IdType> mergeService;
    @Inject
    private LockAttainer<Template, IdType> lockAttainer;
    @Inject(optional = true)
    private LockType lockType;
    private final String errorMessageFormat = "Operation of row %s from table %s has failed optimistically!";
    private int maxRows = -1;
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    public AsyncExecutorService getExecutorService() {
        return executorService;
    }

    public void setExecutorService(AsyncExecutorService executorService) {
        this.executorService = executorService;
    }

    public int getMaxRows() {
        return maxRows;
    }

    @Inject
    public void setMaxRows(@Named("maxRows") Integer maxRows) {
        this.maxRows = maxRows;
    }

    public ObjectRowConverter<Template> getConverter() {
        return converter;
    }

    public void setConverter(ObjectRowConverter<Template> converter) {
        this.converter = converter;
    }

    public SchemaInfoProvider<Template, IdType> getInfoProvider() {
        return infoProvider;
    }

    public void setInfoProvider(SchemaInfoProvider<Template, IdType> infoProvider) {
        this.infoProvider = infoProvider;
    }

    protected String getDefaultTableName() {
        return getInfoProvider().getMainTableName();
    }

    protected int getMaxScanRows() {
        return getMaxRows() > 0 ? getMaxRows() : DEFAULT_MAX_ROWS;
    }

    protected int getMaxScanRows(List<QueryParameter> params) {
        if (params != null && !params.isEmpty()) {
            for (QueryParameter param : params) {
                if (ParameterType.PARAMETER_TYPE_MAX_RESULT.equals(param.getParameterType())) {
                    ValueOnlyQueryParameter<Integer> queryParameter = QueryParameterCastHelper.VALUE_PARAM_HELPER
                            .cast(param);
                    return queryParameter.getValue();
                }
            }
        }
        return getMaxScanRows();
    }

    /*
     * READ OPERATIONS
     */

    /*
     * Unsupported read operations
     */
    @Override
    public Set<Template> getAll() {
        logger.info("Get ALL Executed!");
        return executorService.execute(getDefaultTableName(), new Callback<Set<Template>>() {

            @Override
            public Set<Template> call(HTableInterface tableInterface) throws Exception {
                final Scan scan = new Scan();
                RowFilter rowFilter = new RowFilter(CompareOp.EQUAL, new BinaryPrefixComparator(new byte[0]));
                scan.setFilter(rowFilter);
                final int maxRows = getMaxScanRows();
                return new LinkedHashSet<Template>(CommonDao.this.scanList(tableInterface, scan, maxRows));
            }
        });
    }

    @Override
    public <OtherTemplate extends Object> OtherTemplate getOther(final List<QueryParameter> query) {
        return executorService.execute(getDefaultTableName(), new Callback<OtherTemplate>() {

            @Override
            public OtherTemplate call(HTableInterface tableInterface) throws Exception {
                ResultScanner scanner = tableInterface.getScanner(formScan(query));
                try {
                    Result result = scanner.next();
                    if (result == null || result.isEmpty()) {
                        return null;
                    } else {
                        return (OtherTemplate) result.getNoVersionMap();
                    }
                } finally {
                    if (scanner != null) {
                        scanner.close();
                    }
                }
            }
        });
    }

    @Override
    public <OtherTemplate> List<OtherTemplate> getOtherList(final List<QueryParameter> query) {
        return executorService.execute(getDefaultTableName(), new Callback<List<OtherTemplate>>() {

            @Override
            public List<OtherTemplate> call(HTableInterface tableInterface) throws Exception {
                ResultScanner scanner = tableInterface.getScanner(formScan(query));
                try {
                    Result[] results = scanner.next(getMaxScanRows(query));
                    if (results == null) {
                        return Collections.emptyList();
                    } else {
                        final ArrayList<OtherTemplate> templates = new ArrayList<OtherTemplate>(results.length);
                        for (final Result result : results) {
                            if (result == null || result.isEmpty()) {
                                continue;
                            } else {
                                templates.add((OtherTemplate) result.getNoVersionMap());
                            }
                        }
                        return templates;
                    }
                } finally {
                    if (scanner != null) {
                        scanner.close();
                    }
                }
            }
        });
    }

    /*
     * Supported read operations
     */
    @Override
    public Set<Template> getByIds(List<IdType> ids) {
        LinkedHashSet<Future<Template>> set = new LinkedHashSet<Future<Template>>(ids.size());
        LinkedHashSet<Template> resultSet = new LinkedHashSet<Template>(ids.size());
        for (IdType id : ids) {
            set.add(executorService.executeAsynchronously(getDefaultTableName(), getByIdCallback(id)));
        }
        for (Future<Template> future : set) {
            try {
                final Template get = future.get();
                if (get != null) {
                    resultSet.add(get);
                }
            } catch (Exception ex) {
                logger.warn("Could not retrieve from Future...", ex);
            }
        }
        return resultSet;
    }

    @Override
    public Template getById(final IdType id) {
        return executorService.execute(getDefaultTableName(), getByIdCallback(id));
    }

    protected Callback<Template> getByIdCallback(final IdType id) {
        return new Callback<Template>() {

            @Override
            public Template call(HTableInterface tableInterface) throws Exception {
                final byte[] rowId = getInfoProvider().getRowIdFromId(id);
                Get get = new Get(rowId);
                Result result = tableInterface.get(get);
                if (result == null || result.isEmpty()) {
                    return null;
                } else {
                    return getConverter().rowsToObject(result, executorService);
                }
            }
        };
    }

    @Override
    public Template getSingle(final List<QueryParameter> query) {
        return executorService.execute(getDefaultTableName(), new Callback<Template>() {

            @Override
            public Template call(HTableInterface tableInterface) throws Exception {
                ResultScanner scanner = tableInterface.getScanner(formScan(query));
                try {
                    Result result = scanner.next();
                    if (result == null || result.isEmpty()) {
                        return null;
                    } else {
                        return getConverter().rowsToObject(result, executorService);
                    }
                } finally {
                    if (scanner != null) {
                        scanner.close();
                    }
                }
            }
        });
    }

    @Override
    public List<Template> getList(final List<QueryParameter> query) {
        return executorService.execute(getDefaultTableName(), new Callback<List<Template>>() {

            @Override
            public List<Template> call(HTableInterface tableInterface) throws Exception {
                final Scan scan = formScan(query);
                final int maxRows = getMaxScanRows(query);
                return CommonDao.this.scanList(tableInterface, scan, maxRows);
            }
        });
    }

    protected LockType getLockType() {
        if (lockType == null) {
            return LockType.getDefault();
        }
        return lockType;
    }

    protected List<Template> scanList(HTableInterface tableInterface, Scan scan, int maxRows)
            throws IOException, InterruptedException, ExecutionException {
        logger.info("Scanning a list");
        if (logger.isDebugEnabled()) {
            logger.debug("Scanning for rows " + maxRows);
        }
        ResultScanner scanner = tableInterface.getScanner(scan);
        try {
            Result[] results = scanner.next(maxRows);
            if (logger.isDebugEnabled()) {
                logger.debug("ResultScanner " + scanner);
                logger.debug("Results " + results);
                if (results != null) {
                    logger.debug("Results " + results.length);
                }
            }
            if (results == null) {
                return Collections.emptyList();
            } else {
                final ArrayList<Template> templates = new ArrayList<Template>(results.length);
                ArrayList<Future<Template>> futureTemplates = new ArrayList<Future<Template>>(results.length);
                for (final Result result : results) {
                    if (result == null || result.isEmpty()) {
                        logger.debug("Result is null or empty " + result + " "
                                + (result != null ? result.isEmpty() : "TRUE"));
                        continue;
                    } else {
                        futureTemplates.add(resultExecutorService.submit(new Callable<Template>() {

                            @Override
                            public Template call() throws Exception {
                                logger.debug("Converting row to object " + result);
                                return getConverter().rowsToObject(result, executorService);
                            }
                        }));
                    }
                }
                for (Future<Template> future : futureTemplates) {
                    Template template = future.get();
                    if (template != null) {
                        templates.add(template);
                    }
                }
                return templates;
            }
        } catch (IOException ex) {
            logger.warn(ex.getMessage(), ex);
            throw ex;
        } catch (InterruptedException ex) {
            logger.warn(ex.getMessage(), ex);
            throw ex;
        } catch (ExecutionException ex) {
            logger.warn(ex.getMessage(), ex);
            throw ex;
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
    }

    protected Scan formScan(List<QueryParameter> query) {
        Scan scan = new Scan();
        final Filter filter = getFilter(query, scan);
        if (filter != null) {
            scan.setFilter(filter);
        }
        scan.setCaching(getMaxScanRows(query));
        return scan;
    }

    protected Filter getFilter(Collection<QueryParameter> queryParams, Scan scan) {
        return getFilter(queryParams, scan, Operator.MUST_PASS_ALL);
    }

    protected Filter getFilter(Collection<QueryParameter> queryParams, Scan scan, Operator operator) {
        return getFilter("", queryParams, scan, operator);
    }

    protected Filter getFilter(String namePrefix, Collection<QueryParameter> queryParams, Scan scan,
            Operator operator) {
        final Filter filter;
        if (queryParams != null && !queryParams.isEmpty()) {
            List<Filter> filters = new ArrayList<Filter>(queryParams.size());
            for (QueryParameter param : queryParams) {
                switch (param.getParameterType()) {
                case PARAMETER_TYPE_CONJUNCTION: {
                    BasicCompoundQueryParameter queryParameter = QueryParameterCastHelper.BASIC_COMPOUND_PARAM_HELPER
                            .cast(param);
                    Collection<QueryParameter> nestedParameters = queryParameter.getNestedParameters();
                    filters.add(getFilter(nestedParameters, scan, Operator.MUST_PASS_ALL));
                    break;
                }
                case PARAMETER_TYPE_NESTED_PROPERTY: {
                    CompositionQueryParameter queryParameter = QueryParameterCastHelper.COMPOSITION_PARAM_FOR_NESTED_TYPE
                            .cast(param);
                    Collection<QueryParameter> nestedParameters = queryParameter.getNestedParameters();
                    filters.add(getFilter(getPropertyName(namePrefix, param), nestedParameters, scan,
                            Operator.MUST_PASS_ALL));
                    break;
                }
                case PARAMETER_TYPE_DISJUNCTION: {
                    BasicCompoundQueryParameter queryParameter = QueryParameterCastHelper.BASIC_COMPOUND_PARAM_HELPER
                            .cast(param);
                    Collection<QueryParameter> nestedParameters = queryParameter.getNestedParameters();
                    filters.add(getFilter(nestedParameters, scan, Operator.MUST_PASS_ONE));
                    break;
                }
                case PARAMETER_TYPE_PROPERTY: {
                    handlePropertyParam(namePrefix, param, filters);
                    break;
                }
                case PARAMETER_TYPE_FIRST_RESULT: {
                    Object value = getValue(param);
                    scan.setStartRow(Bytes.toBytes(value.toString()));
                    break;
                }
                case PARAMETER_TYPE_UNIT_PROP: {
                    final String propertyName = getPropertyName(namePrefix, param);
                    final String configPropertyName;
                    final int indexOfColon = propertyName.indexOf(":");
                    final String dynaPropName;
                    if (indexOfColon > -1) {
                        configPropertyName = propertyName.substring(0, indexOfColon);
                        dynaPropName = propertyName.substring(indexOfColon + 1);
                    } else {
                        configPropertyName = propertyName;
                        dynaPropName = null;
                    }
                    FilterConfig config = getInfoProvider().getFilterConfig(configPropertyName);
                    if (config != null) {
                        if (StringUtils.isNotBlank(dynaPropName)) {
                            scan.addColumn(config.getColumnFamily(), Bytes.toBytes(dynaPropName));
                        } else if (config.getColumnQualifier() != null && config.getColumnQualifier().length > 0) {
                            scan.addColumn(config.getColumnFamily(), config.getColumnQualifier());
                        } else {
                            scan.addFamily(config.getColumnFamily());
                        }
                    }
                    break;
                }
                default:
                    //Do nothing
                }
            }
            if (!filters.isEmpty()) {
                FilterList filterList = new FilterList(operator, filters);
                filter = filterList;
            } else {
                filter = null;
            }
        } else {
            filter = new RowFilter(CompareOp.EQUAL, new BinaryPrefixComparator(new byte[0]));
        }
        return filter;
    }

    protected void handlePropertyParam(String namePrefix, QueryParameter queryParameter, List<Filter> filters) {
        OperatorType operator = getOperator(queryParameter);
        Object parameter = getValue(queryParameter);
        final boolean byteArray;
        final boolean isFilter;
        final byte[] paramAsArray;
        if (logger.isInfoEnabled()) {
            logger.info("Class of parameter is " + parameter.getClass());
        }
        if (parameter instanceof byte[]) {
            paramAsArray = (byte[]) parameter;
            byteArray = true;
        } else {
            paramAsArray = null;
            byteArray = false;
        }
        FilterConfig filterConfig = getInfoProvider().getFilterConfig(getPropertyName(namePrefix, queryParameter));
        switch (operator) {
        case OPERATOR_EQUAL: {
            filters.add(getCellFilter(filterConfig, CompareOp.EQUAL,
                    !byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray, parameter));
            return;
        }
        case OPERATOR_LESSER: {
            logger.info("Lesser operator. Is with byte array - " + byteArray);
            filters.add(getCellFilter(filterConfig, CompareOp.LESS,
                    !byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray, parameter));
            return;
        }
        case OPERATOR_LESSER_EQUAL: {
            logger.info("Lesser than equal to operator. Is with byte array - " + byteArray);
            filters.add(getCellFilter(filterConfig, CompareOp.LESS_OR_EQUAL,
                    !byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray, parameter));
            return;
        }
        case OPERATOR_GREATER: {
            logger.info("Greater operator. Is with byte array - " + byteArray);
            filters.add(getCellFilter(filterConfig, CompareOp.GREATER,
                    !byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray, parameter));
            return;
        }
        case OPERATOR_GREATER_EQUAL: {
            logger.info("Greater than equal to operator. Is with byte array - " + byteArray);
            filters.add(getCellFilter(filterConfig, CompareOp.GREATER_OR_EQUAL,
                    !byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray, parameter));
            return;
        }
        case OPERATOR_NOT_EQUAL: {
            filters.add(getCellFilter(filterConfig, CompareOp.NOT_EQUAL,
                    !byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray, parameter));
            return;
        }
        case OPERATOR_IS_EMPTY:
        case OPERATOR_IS_NULL: {
            final Filter cellFilter = getCellFilter(filterConfig, CompareOp.EQUAL, Bytes.toBytes(""), parameter);
            if (cellFilter instanceof SingleColumnValueFilter) {
                ((SingleColumnValueFilter) cellFilter).setFilterIfMissing(false);
            }
            filters.add(cellFilter);
            return;
        }
        case OPERATOR_IS_NOT_EMPTY:
        case OPERATOR_IS_NOT_NULL: {
            final Filter cellFilter = getCellFilter(filterConfig, CompareOp.NOT_EQUAL, Bytes.toBytes(""),
                    parameter);
            if (cellFilter instanceof SingleColumnValueFilter) {
                ((SingleColumnValueFilter) cellFilter).setFilterIfMissing(true);
            }
            filters.add(cellFilter);
            return;
        }
        case OPERATOR_STRING_LIKE: {
            MatchMode matchMode = getMatchMode(queryParameter);
            if (matchMode == null) {
                matchMode = MatchMode.EXACT;
            }
            switch (matchMode) {
            case END:
                logger.info("String like end operator. Is with byte array - " + byteArray);
                filters.add(getCellFilter(filterConfig, CompareOp.EQUAL,
                        new RegexStringComparator(!byteArray ? parameter.toString() : new String(paramAsArray)),
                        parameter));
                break;
            case EXACT:
                filters.add(getCellFilter(filterConfig, CompareOp.EQUAL,
                        new BinaryComparator(!byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray),
                        parameter));
                break;
            case START:
                logger.info("String like start operator. Is with byte array - " + byteArray);
                filters.add(getCellFilter(filterConfig, CompareOp.EQUAL,
                        new BinaryPrefixComparator(!byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray),
                        parameter));
                break;
            default:
            case ANYWHERE:
                filters.add(getCellFilter(filterConfig, CompareOp.EQUAL,
                        new SubstringComparator(parameter.toString()), parameter));
                break;
            }
            return;
        }
        case OPERATOR_BETWEEN: {
            parameter = getFirstParameter(queryParameter);
            Object parameter2 = getSecondParameter(queryParameter);
            final boolean byteArray2;
            final byte[] paramAsArray2;
            if (parameter2 instanceof byte[]) {
                paramAsArray2 = (byte[]) parameter2;
                byteArray2 = true;
            } else {
                paramAsArray2 = null;
                byteArray2 = false;
            }
            filters.add(getCellFilter(filterConfig, CompareOp.EQUAL,
                    new RangeComparator(!byteArray ? Bytes.toBytes(parameter.toString()) : paramAsArray,
                            byteArray2 ? Bytes.toBytes(parameter2.toString()) : paramAsArray2),
                    null));
            return;
        }
        case OPERATOR_IS_IN: {
            Collection inCollectin = QueryParameterCastHelper.MULTI_OPERAND_PARAM_HELPER.cast(queryParameter)
                    .getValues();
            Filter filterList = getInFilter(inCollectin, filterConfig);
            filters.add(filterList);
            return;
        }
        case OPERATOR_IS_NOT_IN: {
            Collection notInCollectin = QueryParameterCastHelper.MULTI_OPERAND_PARAM_HELPER.cast(queryParameter)
                    .getValues();
            Filter filterList = getInFilter(notInCollectin, filterConfig);
            filters.add(new SkipFilter(filterList));
            return;
        }
        }
        return;
    }

    protected Filter getCellFilter(FilterConfig filterConfig, CompareOp op, WritableByteArrayComparable comparator,
            Object originalParam) {
        if (originalParam instanceof Filter) {
            logger.info("Found Filter from client so using that!");
            return (Filter) originalParam;
        }
        if (filterConfig.isFilterOnRowId()) {
            logger.info("Filtering on row id!");
            RowFilter rowFilter = new RowFilter(op, comparator);
            return rowFilter;
        } else if (filterConfig.isQualifierARangePrefix()) {
            logger.info("Filtering on range prefix qualifier!");
            QualifierFilter filter = new QualifierFilter(op, comparator);
            return filter;
        } else {
            logger.info("Filtering on a cell!");
            final SingleColumnValueFilter valueFilter;
            valueFilter = new SingleColumnValueFilter(filterConfig.getColumnFamily(),
                    filterConfig.getColumnQualifier(), op, comparator);
            valueFilter.setFilterIfMissing(filterConfig.isFilterOnIfMissing());
            valueFilter.setLatestVersionOnly(filterConfig.isFilterOnLatestVersionOnly());
            return valueFilter;
        }
    }

    protected Filter getCellFilter(FilterConfig filterConfig, CompareOp op, byte[] value, Object originalParam) {
        return getCellFilter(filterConfig, op, new BinaryComparator(value), originalParam);
    }

    protected Filter getInFilter(Collection inCollectin, FilterConfig config) {
        FilterList filterList = new FilterList(Operator.MUST_PASS_ONE);
        for (Object inObj : inCollectin) {
            final boolean byteArray;
            final byte[] paramAsArray;
            if (inObj instanceof byte[]) {
                paramAsArray = (byte[]) inObj;
                byteArray = true;
            } else {
                paramAsArray = null;
                byteArray = false;
            }
            filterList.addFilter(getCellFilter(config, CompareOp.EQUAL,
                    new BinaryComparator(!byteArray ? Bytes.toBytes(inObj.toString()) : paramAsArray), null));
        }
        return filterList;
    }

    protected String getPropertyName(String prefix, QueryParameter param) {
        final StringBuilder propertyName = new StringBuilder("");
        if (StringUtils.isNotBlank(prefix)) {
            propertyName.append(prefix).append('.');
        }
        if (param instanceof QueryParameterWithPropertyName) {
            propertyName.append(((QueryParameterWithPropertyName) param).getPropertyName());
        }
        return propertyName.toString();
    }

    protected Object getSecondParameter(QueryParameter queryParamemter) {
        if (queryParamemter instanceof BiOperandQueryParameter) {
            return QueryParameterCastHelper.BI_OPERAND_PARAM_HELPER.cast(queryParamemter).getSecondValue();
        } else {
            return "";
        }
    }

    protected Object getFirstParameter(QueryParameter queryParamemter) {
        if (queryParamemter instanceof BiOperandQueryParameter) {
            return QueryParameterCastHelper.BI_OPERAND_PARAM_HELPER.cast(queryParamemter).getFirstValue();
        } else {
            return "";
        }
    }

    protected MatchMode getMatchMode(QueryParameter queryParamemter) {
        return QueryParameterCastHelper.STRING_PARAM_HELPER.cast(queryParamemter).getMatchMode();
    }

    protected Object getValue(QueryParameter queryParamemter) {
        Object value;
        if (queryParamemter instanceof QueryParameterWithValue) {
            value = ((QueryParameterWithValue) queryParamemter).getValue();
        } else {
            value = null;
        }
        if (value == null) {
            value = "";
        }
        return value;
    }

    protected OperatorType getOperator(QueryParameter queryParamemter) {
        if (QueryParameterCastHelper.BI_OPERAND_PARAM_HELPER.isWithOperator(queryParamemter)) {
            QueryParameterWithOperator parameterWithOperator = QueryParameterCastHelper.BI_OPERAND_PARAM_HELPER
                    .castToOperatorParam(queryParamemter);
            return parameterWithOperator.getOperatorType();
        } else {
            return null;
        }
    }

    @Override
    public Template getSingle(QueryParameter... query) {
        return getSingle(Arrays.asList(query));
    }

    @Override
    public List<Template> getList(QueryParameter... query) {
        return getList(Arrays.asList(query));
    }

    @Override
    public <OtherTemplate> OtherTemplate getOther(QueryParameter... query) {
        return this.<OtherTemplate>getOther(Arrays.asList(query));
    }

    @Override
    public <OtherTemplate> List<OtherTemplate> getOtherList(QueryParameter... query) {
        return this.<OtherTemplate>getOtherList(Arrays.asList(query));
    }

    /*
     * WRITE OPERATIONS
     */
    @Override
    public void save(Template... states) {
        verifyAllEntitiesExists(false, states);
        put(states, false);
    }

    @Override
    public void update(Template... states) {
        verifyAllEntitiesExists(true, states);
        put(states, true);
    }

    protected void throwIfErrors(Collection<Future<String>> probableFutureErrors) throws IllegalStateException {
        Collection<String> probableErrors = new ArrayList<String>(probableFutureErrors.size());
        for (Future<String> delErr : probableFutureErrors) {
            String str;
            try {
                str = delErr.get();
            } catch (Exception ex) {
                logger.warn("Could not wait to complete deletion!", ex);
                str = "Could not complete deletion!";
            }
            if (StringUtils.isNotBlank(str)) {
                probableErrors.add(str);
            }
        }
        if (!probableErrors.isEmpty()) {
            throw new IllegalStateException(
                    Arrays.toString(probableErrors.toArray(new String[probableErrors.size()])));
        }
        logger.info("No error messages!");
    }

    protected void verifyAllEntitiesExists(boolean existenceExpected, Template... states) {
        List<Future<Boolean>> gets = new ArrayList<Future<Boolean>>(states.length);
        int i = 0;
        for (Template t : states) {
            try {
                final Comparable id = t.getId();
                if (logger.isInfoEnabled()) {
                    logger.info(new StringBuilder("Adding ").append(id).append(" to index ").append(i).toString());
                }
                final Get get = new Get(infoProvider.getRowIdFromId((IdType) id));
                final int index = i;
                gets.add(executorService.executeAsynchronously(infoProvider.getMainTableName(),
                        new Callback<Boolean>() {

                            @Override
                            public Boolean call(HTableInterface tableInterface) throws Exception {
                                final boolean exists = tableInterface.exists(get);
                                if (logger.isDebugEnabled()) {
                                    logger.debug(new StringBuilder("Id ").append(id.toString()).append(" exists ")
                                            .append(index).toString());
                                }
                                return exists;
                            }
                        }));
                i++;
            } catch (Exception ex) {
                logger.warn("Exception testing row existense...", ex);
            }
        }
        i = 0;
        for (Future<Boolean> future : gets) {
            Boolean exists = true;
            try {
                if (logger.isInfoEnabled()) {
                    logger.info(new StringBuilder("Checking index ").append(i++).toString());
                }
                exists = future.get();
            } catch (Exception ex) {
                logger.warn("Exception testing row existense...", ex);
            }
            if (!existenceExpected && exists) {
                throw new IllegalArgumentException(
                        "Some of the entities are already saved, so did not procced with any of them");
            }
            if (existenceExpected && !exists) {
                throw new IllegalArgumentException(
                        "Some of the entities are not saved, so did not procced with any of them");
            }

        }
    }

    protected void put(Template[] states, final boolean merge) throws IllegalStateException {
        LinkedHashMap<String, List<Put>> allPuts = new LinkedHashMap<String, List<Put>>();
        for (Template state : states) {
            if (!state.isValid()) {
                throw new IllegalStateException("Entity not in valid state!");
            }
            final LinkedHashMap<String, Put> puts;
            puts = getConverter().objectToRows(state, executorService, getLockType().equals(LockType.PESSIMISTIC));
            for (Map.Entry<String, Put> put : puts.entrySet()) {
                final List<Put> putList;
                if (allPuts.containsKey(put.getKey())) {
                    putList = allPuts.get(put.getKey());
                } else {
                    putList = new ArrayList<Put>();
                    allPuts.put(put.getKey(), putList);
                }
                putList.add(put.getValue());
            }
        }
        for (Map.Entry<String, List<Put>> puts : allPuts.entrySet()) {
            if (LockType.OPTIMISTIC.equals(getLockType()) && infoProvider.getVersionColumnFamily() != null
                    && infoProvider.getVersionColumnQualifier() != null) {
                putOptimistically(puts, merge, states);
            } else {
                putNonOptimistically(puts, merge, states);
            }
        }
    }

    protected void putOptimistically(Entry<String, List<Put>> puts, final boolean merge, Template[] states) {
        final List<Put> value = puts.getValue();
        //Merge first respecting lock type
        executorService.execute(puts.getKey(), new Callback<Void>() {

            @Override
            public Void call(HTableInterface tableInterface) throws Exception {
                if (merge && mergeEnabled && mergeService != null) {
                    mergeService.merge(tableInterface, value, getLockType());
                }
                return null;
            }
        });
        Collection<Future<String>> pFutures = new ArrayList<Future<String>>();
        final byte[] family = infoProvider.getVersionColumnFamily();
        final byte[] qualifier = infoProvider.getVersionColumnQualifier();
        for (final Put put : puts.getValue()) {
            final List<KeyValue> kVal = put.get(family, qualifier);
            final byte[] versionValue;
            final byte[] nextVersion;
            if (kVal != null && !kVal.isEmpty()) {
                versionValue = DiffBasedMergeService.getLatestValue(kVal).getValue();
                nextVersion = Bytes.toBytes(Bytes.toLong(versionValue) + 1);
            } else {
                versionValue = null;
                nextVersion = Bytes.toBytes(1l);
            }
            put.add(family, qualifier, nextVersion);
            pFutures.add(executorService.executeAsynchronously(puts.getKey(), new Callback<String>() {

                @Override
                public String call(HTableInterface tableInterface) throws Exception {
                    boolean puted = tableInterface.checkAndPut(put.getRow(), family, qualifier, versionValue, put);
                    if (!puted) {
                        return String.format(errorMessageFormat,
                                infoProvider.getIdFromRowId(put.getRow()).toString(),
                                Bytes.toString(tableInterface.getTableName()));
                    }
                    return null;
                }
            }));
        }
        throwIfErrors(pFutures);
    }

    protected void putNonOptimistically(Entry<String, List<Put>> puts, final boolean merge, Template[] states) {
        try {
            final List<Put> value = puts.getValue();
            executorService.execute(puts.getKey(), new Callback<Void>() {

                @Override
                public Void call(HTableInterface tableInterface) throws Exception {
                    if (merge && mergeEnabled && mergeService != null) {
                        mergeService.merge(tableInterface, value, getLockType());
                    }
                    final ArrayList<Put> valueCopy = new ArrayList<Put>(value);
                    tableInterface.put(valueCopy);
                    return null;
                }
            });
        } finally {
            for (Template state : states) {
                lockAttainer.unlockAndEvictFromCache(state);
            }
        }
    }

    @Override
    public void delete(Template... states) {
        verifyAllEntitiesExists(true, states);
        final byte[] family = infoProvider.getVersionColumnFamily();
        final byte[] qualifier = infoProvider.getVersionColumnQualifier();
        if (LockType.OPTIMISTIC.equals(getLockType()) && family != null && qualifier != null) {
            deleteOptimistically(states);
        } else {
            deleteNonOptimistically(states);
        }
    }

    protected void deleteOptimistically(Template[] states) throws IllegalStateException {
        final byte[] family = infoProvider.getVersionColumnFamily();
        final byte[] qualifier = infoProvider.getVersionColumnQualifier();
        Collection<Future<String>> deletes = new ArrayList<Future<String>>();
        for (final Template state : states) {
            if (!state.isValid()) {
                throw new IllegalStateException("Entity not in valid state!");
            }
            LinkedHashMap<String, Delete> dels = getConverter().objectToDeleteableRows(state, executorService,
                    false);
            final byte[] version = state.getVersion() != null ? Bytes.toBytes(state.getVersion()) : null;
            if (logger.isInfoEnabled() && version != null) {
                logger.info("Version to check on delete optimistically is " + Bytes.toLong(version));
            } else if (logger.isInfoEnabled()) {
                logger.info("Version is null");
            }
            for (final Map.Entry<String, Delete> del : dels.entrySet()) {
                deletes.add(executorService.executeAsynchronously(del.getKey(), new Callback<String>() {

                    @Override
                    public String call(HTableInterface tableInterface) throws Exception {
                        final Delete delVal = del.getValue();
                        boolean deleted = tableInterface.checkAndDelete(delVal.getRow(), family, qualifier, version,
                                delVal);
                        if (logger.isInfoEnabled()) {
                            logger.info("Deleted row? " + deleted);
                        }
                        if (!deleted) {
                            return String.format(errorMessageFormat,
                                    infoProvider.getIdFromRowId(delVal.getRow()).toString(),
                                    Bytes.toString(tableInterface.getTableName()));
                        }
                        return null;
                    }
                }));
            }
        }
        throwIfErrors(deletes);
    }

    protected void deleteNonOptimistically(Template[] states) throws IllegalStateException {
        LinkedHashMap<String, List<Delete>> allDels = new LinkedHashMap<String, List<Delete>>();
        for (Template state : states) {
            if (!state.isValid()) {
                throw new IllegalStateException("Entity not in valid state!");
            }
            LinkedHashMap<String, Delete> dels = getConverter().objectToDeleteableRows(state, executorService,
                    getLockType().equals(LockType.PESSIMISTIC));
            for (Map.Entry<String, Delete> del : dels.entrySet()) {
                final List<Delete> putList;
                if (allDels.containsKey(del.getKey())) {
                    putList = allDels.get(del.getKey());
                } else {
                    putList = new ArrayList<Delete>();
                    allDels.put(del.getKey(), putList);
                }
                putList.add(del.getValue());
            }
        }
        for (final Map.Entry<String, List<Delete>> dels : allDels.entrySet()) {
            try {
                executorService.execute(dels.getKey(), new Callback<Void>() {

                    @Override
                    public Void call(HTableInterface tableInterface) throws Exception {
                        final List<Delete> value = dels.getValue();
                        if (logger.isInfoEnabled()) {
                            logger.info("Attempting to DELETE " + value);
                        }
                        final ArrayList<Delete> list = new ArrayList<Delete>(value);
                        tableInterface.delete(list);
                        return null;
                    }
                });
            } finally {
                for (Template state : states) {
                    lockAttainer.unlockAndEvictFromCache(state);
                }
            }
        }
    }
}