com.comcast.cqs.persistence.CQSMessagePartitionedCassandraPersistence.java Source code

Java tutorial

Introduction

Here is the source code for com.comcast.cqs.persistence.CQSMessagePartitionedCassandraPersistence.java

Source

/**
 * Copyright 2012 Comcast Corporation
 * 
 * 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.comcast.cqs.persistence;

import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONWriter;

import com.comcast.cmb.common.persistence.AbstractDurablePersistence;
import com.comcast.cmb.common.persistence.AbstractDurablePersistence.CMB_SERIALIZER;
import com.comcast.cmb.common.persistence.AbstractDurablePersistence.CmbColumn;
import com.comcast.cmb.common.persistence.AbstractDurablePersistence.CmbColumnSlice;
import com.comcast.cmb.common.persistence.AbstractDurablePersistence.CmbComposite;
import com.comcast.cmb.common.persistence.DurablePersistenceFactory;
import com.comcast.cmb.common.util.CMBErrorCodes;
import com.comcast.cmb.common.util.CMBException;
import com.comcast.cmb.common.util.CMBProperties;
import com.comcast.cmb.common.util.PersistenceException;
import com.comcast.cqs.controller.CQSCache;
import com.comcast.cqs.model.CQSMessage;
import com.comcast.cqs.model.CQSMessageAttribute;
import com.comcast.cqs.model.CQSQueue;
import com.comcast.cqs.util.CQSConstants;
import com.comcast.cqs.util.CQSErrorCodes;
import com.comcast.cqs.util.RandomNumberCollection;
import com.comcast.cqs.util.Util;
import com.eaio.uuid.UUIDGen;

/**
 * Cassandra persistence for CQS Message
 * @author aseem, vvenkatraman, bwolf
 *
 */
public class CQSMessagePartitionedCassandraPersistence implements ICQSMessagePersistence {

    private static final String COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES = "CQSPartitionedQueueMessages";
    private static final Random rand = new Random();

    private static Logger logger = Logger.getLogger(CQSMessagePartitionedCassandraPersistence.class);

    private static final AbstractDurablePersistence cassandraHandler = DurablePersistenceFactory.getInstance();

    public CQSMessagePartitionedCassandraPersistence() {
    }

    @Override
    public String sendMessage(CQSQueue queue, int shard, CQSMessage message) throws PersistenceException,
            IOException, InterruptedException, NoSuchAlgorithmException, JSONException {

        if (queue == null) {
            throw new PersistenceException(CQSErrorCodes.NonExistentQueue, "The supplied queue does not exist");
        }

        if (message == null) {
            throw new PersistenceException(CQSErrorCodes.InvalidMessageContents, "The supplied message is invalid");
        }

        int delaySeconds = 0;

        if (message.getAttributes().containsKey(CQSConstants.DELAY_SECONDS)) {
            delaySeconds = Integer.parseInt(message.getAttributes().get(CQSConstants.DELAY_SECONDS));
        }

        long ts = System.currentTimeMillis() + delaySeconds * 1000;
        CmbComposite columnName = cassandraHandler.getCmbComposite(AbstractDurablePersistence.newTime(ts, false),
                UUIDGen.getClockSeqAndNode());
        int ttl = queue.getMsgRetentionPeriod();
        int partition = rand.nextInt(queue.getNumberOfPartitions());
        String key = Util.hashQueueUrl(queue.getRelativeUrl()) + "_" + shard + "_" + partition;

        if (queue.isCompressed()) {
            message.setBody(Util.compress(message.getBody()));
        }

        message.setMessageId(key + ":" + columnName.get(0) + ":" + columnName.get(1));

        logger.debug("event=send_message ttl=" + ttl + " delay_sec=" + delaySeconds + " msg_id="
                + message.getMessageId() + " key=" + key + " col=" + columnName);

        cassandraHandler.update(AbstractDurablePersistence.CQS_KEYSPACE, COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES,
                key, columnName, getMessageJSON(message), CMB_SERIALIZER.STRING_SERIALIZER,
                CMB_SERIALIZER.COMPOSITE_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER, ttl);

        return message.getMessageId();
    }

    public List<CQSMessage> extractMessagesFromColumnSlice(String queueUrl, int length, CmbComposite previousHandle,
            CmbComposite nextHandle, CmbColumnSlice<CmbComposite, String> columnSlice,
            boolean ignoreFirstLastColumn)
            throws NoSuchAlgorithmException, IOException, JSONException, PersistenceException {

        List<CQSMessage> messageList = new ArrayList<CQSMessage>();

        if (columnSlice != null && columnSlice.getColumns() != null) {

            boolean noMatch = true;

            for (CmbColumn<CmbComposite, String> column : columnSlice.getColumns()) {

                CmbComposite columnName = column.getName();

                if (ignoreFirstLastColumn && (previousHandle != null && columnName.compareTo(previousHandle) == 0)
                        || (nextHandle != null && columnName.compareTo(nextHandle) == 0)) {
                    noMatch = false;
                    continue;
                } else if (column.getValue() == null || column.getValue().length() == 0) {
                    continue;
                }

                CQSMessage message = extractMessageFromJSON(queueUrl, column);
                messageList.add(message);
            }

            if (noMatch && messageList.size() > length) {
                messageList.remove(messageList.size() - 1);
            }
        }

        return messageList;
    }

    private CQSMessage extractMessageFromJSON(String queueUrl, CmbColumn column)
            throws JSONException, IOException, PersistenceException {

        CQSQueue queue = null;
        CQSMessage m = new CQSMessage();

        try {
            queue = CQSCache.getCachedQueue(queueUrl);
        } catch (Exception ex) {
            throw new PersistenceException(ex);
        }

        if (queue == null) {
            throw new PersistenceException(CMBErrorCodes.InternalError, "Unknown queue " + queueUrl);
        }

        JSONObject json = new JSONObject((String) column.getValue());

        m.setMessageId(json.getString("MessageId"));
        m.setReceiptHandle(json.getString("MessageId"));
        m.setMD5OfBody(json.getString("MD5OfBody"));
        m.setBody(json.getString("Body"));

        if (m.getAttributes() == null) {
            m.setAttributes(new HashMap<String, String>());
        }

        if (json.has(CQSConstants.SENT_TIMESTAMP)) {
            m.getAttributes().put(CQSConstants.SENT_TIMESTAMP, json.getString(CQSConstants.SENT_TIMESTAMP));
        }

        if (json.has(CQSConstants.APPROXIMATE_RECEIVE_COUNT)) {
            m.getAttributes().put(CQSConstants.APPROXIMATE_RECEIVE_COUNT,
                    json.getString(CQSConstants.APPROXIMATE_RECEIVE_COUNT));
        }

        if (json.has(CQSConstants.SENDER_ID)) {
            m.getAttributes().put(CQSConstants.SENDER_ID, json.getString(CQSConstants.SENDER_ID));
        }

        if (json.has("MessageAttributes")) {
            m.setMD5OfMessageAttributes(json.getString("MD5OfMessageAttributes"));
            JSONObject messageAttributes = json.getJSONObject("MessageAttributes");
            Map<String, CQSMessageAttribute> ma = new HashMap<String, CQSMessageAttribute>();
            Iterator<String> iter = messageAttributes.keys();
            while (iter.hasNext()) {
                String key = iter.next();
                ma.put(key, new CQSMessageAttribute(messageAttributes.getJSONObject(key).getString("StringValue"),
                        messageAttributes.getJSONObject(key).getString("DataType")));
            }
            m.setMessageAttributes(ma);
        }

        m.setTimebasedId(column.getName());

        if (queue.isCompressed()) {
            m.setBody(Util.decompress(m.getBody()));
        }

        return m;
    }

    private String getMessageJSON(CQSMessage message) throws JSONException {

        Writer writer = new StringWriter();
        JSONWriter jw = new JSONWriter(writer);

        jw = jw.object();
        jw.key("MessageId").value(message.getMessageId());
        jw.key("MD5OfBody").value(message.getMD5OfBody());
        jw.key("Body").value(message.getBody());

        if (message.getAttributes() == null) {
            message.setAttributes(new HashMap<String, String>());
        }

        if (!message.getAttributes().containsKey(CQSConstants.SENT_TIMESTAMP)) {
            message.getAttributes().put(CQSConstants.SENT_TIMESTAMP, "" + System.currentTimeMillis());
        }

        if (!message.getAttributes().containsKey(CQSConstants.APPROXIMATE_RECEIVE_COUNT)) {
            message.getAttributes().put(CQSConstants.APPROXIMATE_RECEIVE_COUNT, "0");
        }

        if (message.getAttributes() != null) {
            for (String key : message.getAttributes().keySet()) {
                String value = message.getAttributes().get(key);
                if (value == null || value.isEmpty()) {
                    value = "";
                }
                jw.key(key).value(value);
            }
        }

        if (message.getMessageAttributes() != null && message.getMessageAttributes().size() > 0) {
            jw.key("MD5OfMessageAttributes").value(message.getMD5OfMessageAttributes());
            jw.key("MessageAttributes");
            jw.object();
            for (String key : message.getMessageAttributes().keySet()) {
                jw.key(key);
                jw.object();
                CQSMessageAttribute messageAttribute = message.getMessageAttributes().get(key);
                if (messageAttribute.getStringValue() != null) {
                    jw.key("StringValue").value(messageAttribute.getStringValue());
                } else if (messageAttribute.getBinaryValue() != null) {
                    jw.key("BinaryValue").value(messageAttribute.getBinaryValue());
                }
                jw.key("DataType").value(messageAttribute.getDataType());
                jw.endObject();
            }
            jw.endObject();
        }

        jw.endObject();

        return writer.toString();
    }

    @Override
    public Map<String, String> sendMessageBatch(CQSQueue queue, int shard, List<CQSMessage> messages)
            throws PersistenceException, IOException, InterruptedException, NoSuchAlgorithmException,
            JSONException {

        if (queue == null) {
            throw new PersistenceException(CQSErrorCodes.NonExistentQueue, "The supplied queue doesn't exist");
        }

        if (messages == null || messages.size() == 0) {
            throw new PersistenceException(CQSErrorCodes.InvalidQueryParameter, "No messages are supplied.");
        }

        Map<CmbComposite, String> messageDataMap = new HashMap<CmbComposite, String>();
        Map<String, String> ret = new HashMap<String, String>();
        int ttl = queue.getMsgRetentionPeriod();
        String key = Util.hashQueueUrl(queue.getRelativeUrl()) + "_" + shard + "_"
                + rand.nextInt(queue.getNumberOfPartitions());

        for (CQSMessage message : messages) {

            if (message == null) {
                throw new PersistenceException(CQSErrorCodes.InvalidMessageContents,
                        "The supplied message is invalid");
            }

            if (queue.isCompressed()) {
                message.setBody(Util.compress(message.getBody()));
            }

            int delaySeconds = 0;

            if (message.getAttributes().containsKey(CQSConstants.DELAY_SECONDS)) {
                delaySeconds = Integer.parseInt(message.getAttributes().get(CQSConstants.DELAY_SECONDS));
            }

            long ts = System.currentTimeMillis() + delaySeconds * 1000;
            CmbComposite columnName = cassandraHandler
                    .getCmbComposite(AbstractDurablePersistence.newTime(ts, false), UUIDGen.getClockSeqAndNode());

            message.setMessageId(key + ":" + columnName.get(0) + ":" + columnName.get(1));

            logger.debug("event=send_message_batch msg_id=" + message.getMessageId() + " ttl=" + ttl + " delay_sec="
                    + delaySeconds + " key=" + key + " col=" + columnName);

            String messageJson = getMessageJSON(message);
            messageDataMap.put(columnName, messageJson);
            ret.put(message.getSuppliedMessageId(), message.getMessageId());
        }

        cassandraHandler.insertRow(AbstractDurablePersistence.CQS_KEYSPACE, key,
                COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, messageDataMap, CMB_SERIALIZER.STRING_SERIALIZER,
                CMB_SERIALIZER.COMPOSITE_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER, ttl);

        return ret;
    }

    @Override
    public void deleteMessage(String queueUrl, String receiptHandle) throws PersistenceException {

        if (receiptHandle == null) {
            logger.error("event=delete_message event=no_receipt_handle queue_url=" + queueUrl);
            return;
        }

        String[] receiptHandleParts = receiptHandle.split(":");

        if (receiptHandleParts.length != 3) {
            logger.error("event=delete_message event=invalid_receipt_handle queue_url=" + queueUrl
                    + " receipt_handle=" + receiptHandle);
            return;
        }

        CmbComposite columnName = cassandraHandler.getCmbComposite(
                Arrays.asList(Long.parseLong(receiptHandleParts[1]), Long.parseLong(receiptHandleParts[2])));

        if (columnName != null) {
            logger.debug("event=delete_message receipt_handle=" + receiptHandle + " col=" + columnName + " key="
                    + receiptHandleParts[0]);
            cassandraHandler.delete(AbstractDurablePersistence.CQS_KEYSPACE,
                    COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, receiptHandleParts[0], columnName,
                    CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.COMPOSITE_SERIALIZER);
        }
    }

    @Override
    public List<CQSMessage> receiveMessage(CQSQueue queue, Map<String, String> receiveAttributes)
            throws PersistenceException, IOException, NoSuchAlgorithmException, InterruptedException {
        throw new UnsupportedOperationException("ReceiveMessage is not supported, please call getMessages instead");
    }

    @Override
    public boolean changeMessageVisibility(CQSQueue queue, String receiptHandle, int visibilityTO)
            throws PersistenceException, IOException, NoSuchAlgorithmException, InterruptedException {
        throw new UnsupportedOperationException("ChangeMessageVisibility is not supported");
    }

    @Override
    public List<CQSMessage> peekQueue(String queueUrl, int shard, String previousReceiptHandle,
            String nextReceiptHandle, int length)
            throws IOException, NoSuchAlgorithmException, JSONException, PersistenceException {

        String queueHash = Util.hashQueueUrl(queueUrl);
        String key = queueHash + "_" + shard + "_0";
        String handle = null;
        List<CQSMessage> messageList = new ArrayList<CQSMessage>();
        CmbComposite previousHandle = null;
        CmbComposite nextHandle = null;

        int numberPartitions = getNumberOfPartitions(queueUrl);
        int numberShards = getNumberOfShards(queueUrl);

        logger.debug("event=peek_queue queue_url=" + queueUrl + " prev_receipt_handle=" + previousReceiptHandle
                + " next_receipt_handle=" + nextReceiptHandle + " length=" + length + " num_partitions="
                + numberPartitions);

        if (previousReceiptHandle != null) {

            handle = previousReceiptHandle;
            String[] handleParts = handle.split(":");

            if (handleParts.length != 3) {
                logger.error("event=peek_queue error_code=corrupt_receipt_handle receipt_handle=" + handle);
                throw new IllegalArgumentException("Corrupt receipt handle " + handle);
            }

            key = handleParts[0];
            previousHandle = cassandraHandler
                    .getCmbComposite(Arrays.asList(Long.parseLong(handleParts[1]), Long.parseLong(handleParts[2])));

        } else if (nextReceiptHandle != null) {

            handle = nextReceiptHandle;
            String[] handleParts = handle.split(":");

            if (handleParts.length != 3) {
                logger.error("action=peek_queue error_code=corrupt_receipt_handle receipt_handle=" + handle);
                throw new IllegalArgumentException("Corrupt receipt handle " + handle);
            }

            key = handleParts[0];
            nextHandle = cassandraHandler
                    .getCmbComposite(Arrays.asList(Long.parseLong(handleParts[1]), Long.parseLong(handleParts[2])));
        }

        String[] queueParts = key.split("_");

        if (queueParts.length != 3) {
            logger.error("event=peek_queue error_code=invalid_queue_key key=" + key);
            throw new IllegalArgumentException("Invalid queue key " + key);
        }

        int shardNumber = Integer.parseInt(queueParts[1]);
        int partitionNumber = Integer.parseInt(queueParts[2]);

        if (partitionNumber < 0 || partitionNumber > numberPartitions - 1) {
            logger.error(
                    "event=peek_queue error_code=invalid_partition_number partition_number=" + partitionNumber);
            throw new IllegalArgumentException("Invalid queue partition number " + partitionNumber);
        }

        if (shardNumber < 0 || shardNumber > numberShards - 1) {
            logger.error("event=peek_queue error_code=invalid_shard_number shard_number=" + shardNumber);
            throw new IllegalArgumentException("Invalid queue shard number " + shardNumber);
        }

        while (messageList.size() < length && -1 < partitionNumber && partitionNumber < numberPartitions) {

            key = queueHash + "_" + shardNumber + "_" + partitionNumber;

            CmbColumnSlice<CmbComposite, String> columnSlice = cassandraHandler.readColumnSlice(
                    AbstractDurablePersistence.CQS_KEYSPACE, COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, key,
                    previousHandle, nextHandle, length - messageList.size() + 1, CMB_SERIALIZER.STRING_SERIALIZER,
                    CMB_SERIALIZER.COMPOSITE_SERIALIZER, CMB_SERIALIZER.STRING_SERIALIZER);

            messageList.addAll(extractMessagesFromColumnSlice(queueUrl, length - messageList.size(), previousHandle,
                    nextHandle, columnSlice, true));

            if (messageList.size() < length && -1 < partitionNumber && partitionNumber < numberPartitions) {

                if (previousHandle != null) {

                    partitionNumber++;

                    if (partitionNumber > -1) {
                        previousHandle = cassandraHandler.getCmbComposite(Arrays.asList(
                                AbstractDurablePersistence.newTime(System.currentTimeMillis() - 1209600000, false),
                                UUIDGen.getClockSeqAndNode()));
                    }

                } else if (nextHandle != null) {

                    partitionNumber--;

                    if (partitionNumber < numberPartitions) {
                        nextHandle = cassandraHandler.getCmbComposite(Arrays.asList(
                                AbstractDurablePersistence.newTime(System.currentTimeMillis() + 1209600000, false),
                                UUIDGen.getClockSeqAndNode()));
                    }

                } else {
                    partitionNumber++;
                }
            }
        }

        return messageList;
    }

    @Override
    public void clearQueue(String queueUrl, int shard)
            throws PersistenceException, NoSuchAlgorithmException, UnsupportedEncodingException {

        int numberPartitions = getNumberOfPartitions(queueUrl);

        logger.debug("event=clear_queue queue_url=" + queueUrl + " num_partitions=" + numberPartitions);

        for (int i = 0; i < numberPartitions; i++) {
            String key = Util.hashQueueUrl(queueUrl) + "_" + shard + "_" + i;
            //cassandraHandler.deleteSuperColumn(COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, key, null, CMB_SERIALIZER.STRING_SERIALIZER, CompositeSerializer.get());
            cassandraHandler.delete(AbstractDurablePersistence.CQS_KEYSPACE,
                    COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, key, null, CMB_SERIALIZER.STRING_SERIALIZER,
                    CMB_SERIALIZER.STRING_SERIALIZER);
        }
    }

    @Override
    public Map<String, CQSMessage> getMessages(String queueUrl, List<String> ids)
            throws NoSuchAlgorithmException, IOException, JSONException, PersistenceException {

        Map<String, CQSMessage> messageMap = new HashMap<String, CQSMessage>();

        logger.debug("event=get_messages ids=" + ids);

        if (ids == null || ids.size() == 0) {
            return messageMap;
        } else if (ids.size() > 100) {
            return getMessagesBulk(queueUrl, ids);
        }

        for (String id : ids) {

            String[] idParts = id.split(":");

            if (idParts.length != 3) {
                logger.error("event=get_messages error_code=invalid_message_id id=" + id);
                throw new IllegalArgumentException("Invalid message id " + id);
            }

            CmbComposite columnName = cassandraHandler
                    .getCmbComposite(Arrays.asList(Long.parseLong(idParts[1]), Long.parseLong(idParts[2])));

            CmbColumn<CmbComposite, String> column = cassandraHandler.readColumn(
                    AbstractDurablePersistence.CQS_KEYSPACE, COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, idParts[0],
                    columnName, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.COMPOSITE_SERIALIZER,
                    CMB_SERIALIZER.STRING_SERIALIZER);

            CQSMessage message = null;

            if (column != null) {
                message = extractMessageFromJSON(queueUrl, column);
            }

            messageMap.put(id, message);
        }

        return messageMap;
    }

    private Map<String, CQSMessage> getMessagesBulk(String queueUrl, List<String> ids)
            throws NoSuchAlgorithmException, IOException, JSONException, NumberFormatException,
            PersistenceException {

        logger.debug("event=get_message_bulk ids=" + ids);

        Map<String, CQSMessage> messageMap = new HashMap<String, CQSMessage>();
        Set<String> messageIdSet = new HashSet<String>();
        Map<String, Map<String, String>> firstLastIdsForEachPartition = getFirstAndLastIdsForEachPartition(ids,
                messageIdSet);

        if (firstLastIdsForEachPartition.size() == 0) {
            return messageMap;
        }

        for (String queuePartition : firstLastIdsForEachPartition.keySet()) {

            int messageCount = 200;
            Map<String, String> firstLastForPartition = firstLastIdsForEachPartition.get(queuePartition);
            String firstParts[] = firstLastForPartition.get("First").split(":");
            String lastParts[] = firstLastForPartition.get("Last").split(":");

            CmbColumnSlice<CmbComposite, String> columnSlice = cassandraHandler.readColumnSlice(
                    AbstractDurablePersistence.CQS_KEYSPACE, COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES,
                    queuePartition,
                    cassandraHandler.getCmbComposite(
                            Arrays.asList(Long.parseLong(firstParts[0]), Long.parseLong(firstParts[1]))),
                    cassandraHandler.getCmbComposite(
                            Arrays.asList(Long.parseLong(lastParts[0]), Long.parseLong(lastParts[1]))),
                    messageCount, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.COMPOSITE_SERIALIZER,
                    CMB_SERIALIZER.STRING_SERIALIZER);

            List<CQSMessage> messageList = extractMessagesFromColumnSlice(queueUrl, messageCount, null, null,
                    columnSlice, false);

            for (CQSMessage message : messageList) {

                if (messageIdSet.contains(message.getMessageId())) {
                    messageMap.put(message.getMessageId(), message);
                    messageIdSet.remove(message.getMessageId());
                }
            }
        }

        for (String messageId : messageIdSet) {
            messageMap.put(messageId, null);
        }

        return messageMap;
    }

    private Map<String, Map<String, String>> getFirstAndLastIdsForEachPartition(List<String> ids,
            Set<String> messageIdSet) {

        Map<String, Map<String, String>> firstLastForEachPartitionMap = new HashMap<String, Map<String, String>>();

        if (ids == null || ids.size() == 0) {
            return firstLastForEachPartitionMap;
        }

        for (String id : ids) {

            messageIdSet.add(id);
            String[] idParts = id.split(":");

            if (idParts.length != 3) {
                logger.error("action=get_messages_bulk error_code=corrupt_receipt_handle receipt_handle=" + id);
                throw new IllegalArgumentException("Corrupt receipt handle " + id);
            }

            String queuePartition = idParts[0];
            final String messageId = idParts[1] + ":" + idParts[2];

            if (!firstLastForEachPartitionMap.containsKey(queuePartition)) {
                firstLastForEachPartitionMap.put(queuePartition, new HashMap<String, String>() {
                    {
                        put("First", messageId);
                        put("Last", messageId);
                    }
                });
            } else {
                Map<String, String> firstLastForPartition = firstLastForEachPartitionMap.get(queuePartition);
                if (firstLastForPartition.get("First").compareTo(messageId) > 0) {
                    firstLastForPartition.put("First", messageId);
                } else if (firstLastForPartition.get("Last").compareTo(messageId) < 0) {
                    firstLastForPartition.put("Last", messageId);
                }
            }
        }

        return firstLastForEachPartitionMap;
    }

    private int getNumberOfPartitions(String queueUrl) {

        int numberPartitions = CMBProperties.getInstance().getCQSNumberOfQueuePartitions();

        try {

            CQSQueue queue = CQSCache.getCachedQueue(queueUrl);

            if (queue != null) {
                numberPartitions = queue.getNumberOfPartitions();
            }

        } catch (Exception ex) {
            logger.warn("event=queue_cache_failure queue_url=" + queueUrl, ex);
        }

        return numberPartitions;
    }

    private int getNumberOfShards(String queueUrl) {

        int numberShards = 1;

        try {

            CQSQueue queue = CQSCache.getCachedQueue(queueUrl);

            if (queue != null) {
                numberShards = queue.getNumberOfShards();
            }

        } catch (Exception ex) {
            logger.warn("event=queue_cache_failure queue_url=" + queueUrl, ex);
        }

        return numberShards;
    }

    @Override
    public List<CQSMessage> peekQueueRandom(String queueUrl, int shard, int length)
            throws IOException, NoSuchAlgorithmException, JSONException, PersistenceException {

        String queueHash = Util.hashQueueUrl(queueUrl);

        int numberPartitions = getNumberOfPartitions(queueUrl);

        logger.debug("event=peek_queue_random queue_url=" + queueUrl + " shard=" + shard + " queue_hash="
                + queueHash + " num_partitions=" + numberPartitions);

        List<CQSMessage> messageList = new ArrayList<CQSMessage>();

        if (length > numberPartitions) {

            // no randomness, get from all rows, subsequent calls will return the same result

            return peekQueue(queueUrl, shard, null, null, length);

        } else {

            // get from random set of rows
            // note: as a simplification we may return less messages than length if not all rows contain messages

            RandomNumberCollection rc = new RandomNumberCollection(numberPartitions);
            int numFound = 0;

            for (int i = 0; i < numberPartitions && numFound < length; i++) {

                int partition = rc.getNext();
                String key = queueHash + "_" + shard + "_" + partition;

                CmbColumnSlice<CmbComposite, String> columnSlice = cassandraHandler.readColumnSlice(
                        AbstractDurablePersistence.CQS_KEYSPACE, COLUMN_FAMILY_PARTITIONED_QUEUE_MESSAGES, key,
                        null, null, 1, CMB_SERIALIZER.STRING_SERIALIZER, CMB_SERIALIZER.COMPOSITE_SERIALIZER,
                        CMB_SERIALIZER.STRING_SERIALIZER);

                List<CQSMessage> messages = extractMessagesFromColumnSlice(queueUrl, 1, null, null, columnSlice,
                        false);
                numFound += messages.size();
                messageList.addAll(messages);
            }

            return messageList;
        }
    }

    @Override
    public List<String> getIdsFromHead(String queueUrl, int shard, int num) throws PersistenceException {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getQueueMessageCount(String queueUrl) {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public boolean checkCacheConsistency(String queueUrl, int shard, boolean trueOnFiller) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public long getQueueMessageCount(String queueUrl, boolean processHiddenIds) throws Exception {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public int getNumConnections() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public long getCacheQueueMessageCount(String queueUrl) throws Exception {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public long getQueueNotVisibleMessageCount(String queueUrl, boolean visibilityProcessFlag) throws Exception {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public long getQueueDelayedMessageCount(String queueUrl, boolean visibilityProcessFlag) throws Exception {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void flushAll() {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean isAlive() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public int getNumberOfRedisShards() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void shutdown() {
        // TODO Auto-generated method stub

    }

    @Override
    public List<Map<String, String>> getInfo() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public long getMemQueueMessageCreatedTS(String memId) {
        // TODO Auto-generated method stub
        return 0;
    }
}