org.wso2.andes.server.information.management.QueueManagementInformationMBean.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.andes.server.information.management.QueueManagementInformationMBean.java

Source

/*
*  Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you under the Apache License,
*  Version 2.0 (the "License"); you may not use this file except
*  in compliance with the License.
*  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/

package org.wso2.andes.server.information.management;

import com.gs.collections.impl.list.mutable.primitive.LongArrayList;
import com.gs.collections.impl.map.mutable.primitive.LongObjectHashMap;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.org.apache.mina.common.ByteBuffer;
import org.wso2.andes.AMQException;
import org.wso2.andes.amqp.AMQPUtils;
import org.wso2.andes.configuration.AndesConfigurationManager;
import org.wso2.andes.configuration.enums.AndesConfiguration;
import org.wso2.andes.framing.AMQShortString;
import org.wso2.andes.framing.BasicContentHeaderProperties;
import org.wso2.andes.kernel.Andes;
import org.wso2.andes.kernel.AndesChannel;
import org.wso2.andes.kernel.AndesContext;
import org.wso2.andes.kernel.AndesException;
import org.wso2.andes.kernel.AndesMessage;
import org.wso2.andes.kernel.AndesMessageMetadata;
import org.wso2.andes.kernel.AndesMessagePart;
import org.wso2.andes.kernel.AndesUtils;
import org.wso2.andes.kernel.DisablePubAckImpl;
import org.wso2.andes.kernel.FlowControlListener;
import org.wso2.andes.kernel.MessagingEngine;
import org.wso2.andes.kernel.ProtocolType;
import org.wso2.andes.kernel.disruptor.compression.LZ4CompressionHelper;
import org.wso2.andes.kernel.disruptor.inbound.InboundQueueEvent;
import org.wso2.andes.kernel.registry.StorageQueueRegistry;
import org.wso2.andes.kernel.router.AndesMessageRouter;
import org.wso2.andes.kernel.subscription.StorageQueue;
import org.wso2.andes.management.common.mbeans.QueueManagementInformation;
import org.wso2.andes.management.common.mbeans.annotations.MBeanOperationParameter;
import org.wso2.andes.server.management.AMQManagedObject;
import org.wso2.andes.server.message.AMQMessage;
import org.wso2.andes.server.queue.AMQQueue;
import org.wso2.andes.server.queue.DLCQueueUtils;
import org.wso2.andes.server.queue.QueueRegistry;
import org.wso2.andes.server.virtualhost.VirtualHost;
import org.wso2.andes.server.virtualhost.VirtualHostImpl;
import org.wso2.andes.transport.codec.BBDecoder;
import javax.jms.JMSException;
import javax.jms.MessageEOFException;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotReadableException;
import javax.management.JMException;
import javax.management.MBeanException;
import javax.management.NotCompliantMBeanException;
import javax.management.openmbean.ArrayType;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.OpenType;
import javax.management.openmbean.SimpleType;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Collections;

/**
 * This class contains all operations such as addition, deletion, purging, browsing, etc. that are invoked by the UI
 * console with relation to queues.
 */
public class QueueManagementInformationMBean extends AMQManagedObject implements QueueManagementInformation {

    public static final String MIME_TYPE_TEXT_PLAIN = "text/plain";
    public static final String MIMI_TYPE_TEXT_XML = "text/xml";
    public static final String MIME_TYPE_APPLICATION_JAVA_OBJECT_STREAM = "application/java-object-stream";
    public static final String MIME_TYPE_AMQP_MAP = "amqp/map";
    public static final String MIME_TYPE_JMS_MAP_MESSAGE = "jms/map-message";
    public static final String MIME_TYPE_JMS_STREAM_MESSAGE = "jms/stream-message";
    public static final String MIME_TYPE_APPLICATION_OCTET_STREAM = "application/octet-stream";
    private static Log log = LogFactory.getLog(QueueManagementInformationMBean.class);

    private final QueueRegistry queueRegistry;

    private final String PURGE_QUEUE_ERROR = "Error in purging queue : ";

    private final String MESSAGE_COUNT_RETRIEVE_ERROR = "Error while retrieving message count queue : ";

    // OpenMBean data types for viewMessageContent method
    private static CompositeType _msgContentType = null;
    private static OpenType[] _msgContentAttributeTypes = new OpenType[8];

    /**
     * Publisher Acknowledgements are disabled for this MBean
     * hence using DisablePubAckImpl to drop any pub ack request by Andes
     */
    private final DisablePubAckImpl disablePubAck;

    /**
     * The message restore flowcontrol blocking state.
     * If true message restore will be interrupted from dead letter channel.
     */
    boolean restoreBlockedByFlowControl = false;

    private static final int CHARACTERS_TO_SHOW = 15;

    /**
     * Maximum size a message will be displayed on UI
     */
    public static final Integer MESSAGE_DISPLAY_LENGTH_MAX = AndesConfigurationManager
            .readValue(AndesConfiguration.MANAGEMENT_CONSOLE_MAX_DISPLAY_LENGTH_FOR_MESSAGE_CONTENT);

    /**
     * Shown to user has a indication that the particular message has more content than shown in UI
     */
    public static final String DISPLAY_CONTINUATION = "...";

    /**
     * Message shown in UI if message content exceed the limit - Further enhancement,
     * these needs to read from a resource bundle
     */
    public static final String DISPLAY_LENGTH_EXCEEDED = "Message Content is too large to display.";

    protected static final byte BOOLEAN_TYPE = (byte) 1;

    protected static final byte BYTE_TYPE = (byte) 2;

    protected static final byte BYTEARRAY_TYPE = (byte) 3;

    protected static final byte SHORT_TYPE = (byte) 4;

    protected static final byte CHAR_TYPE = (byte) 5;

    protected static final byte INT_TYPE = (byte) 6;

    protected static final byte LONG_TYPE = (byte) 7;

    protected static final byte FLOAT_TYPE = (byte) 8;

    protected static final byte DOUBLE_TYPE = (byte) 9;

    protected static final byte STRING_TYPE = (byte) 10;

    protected static final byte NULL_STRING_TYPE = (byte) 11;

    /**
     * This is set when reading a byte array. The readBytes(byte[]) method supports multiple calls to read
     * a byte array in multiple chunks, hence this is used to track how much is left to be read
     */
    private int byteArrayRemaining = -1;

    /**
     * Used to get configuration values related to compression and used to decompress message content
     */
    LZ4CompressionHelper lz4CompressionHelper;

    /**
     * AndesChannel for this dead letter channel restore which implements flow control.
     */
    AndesChannel andesChannel;

    /***
     * Virtual host information are needed in the constructor to evaluate user permissions for
     * queue management actions.(e.g. purge)
     *
     * @param vHostMBean Used to access the virtual host information
     * @throws NotCompliantMBeanException
     */
    public QueueManagementInformationMBean(VirtualHostImpl.VirtualHostMBean vHostMBean)
            throws NotCompliantMBeanException, OpenDataException {
        super(QueueManagementInformation.class, QueueManagementInformation.TYPE);

        andesChannel = Andes.getInstance().createChannel(new FlowControlListener() {
            @Override
            public void block() {
                restoreBlockedByFlowControl = true;
            }

            @Override
            public void unblock() {
                restoreBlockedByFlowControl = false;
            }

            @Override
            public void disconnect() {
                // Do nothing. since its not applicable.
            }
        });

        VirtualHost virtualHost = vHostMBean.getVirtualHost();

        queueRegistry = virtualHost.getQueueRegistry();
        disablePubAck = new DisablePubAckImpl();

        _msgContentAttributeTypes[0] = SimpleType.STRING; // For message properties
        _msgContentAttributeTypes[1] = SimpleType.STRING; // For content type
        _msgContentAttributeTypes[2] = new ArrayType(1, SimpleType.STRING); // For message content
        _msgContentAttributeTypes[3] = SimpleType.STRING; // For JMS message id
        _msgContentAttributeTypes[4] = SimpleType.BOOLEAN; // For redelivered
        _msgContentAttributeTypes[5] = SimpleType.LONG; // For JMS timeStamp
        _msgContentAttributeTypes[6] = SimpleType.STRING; // For dlc message destination
        _msgContentAttributeTypes[7] = SimpleType.LONG; // For andes message metadata id
        _msgContentType = new CompositeType("Message Content", "Message content for queue browse",
                VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC
                        .toArray(new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]),
                VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray(
                        new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]),
                _msgContentAttributeTypes);
        lz4CompressionHelper = new LZ4CompressionHelper();
    }

    public String getObjectInstanceName() {
        return QueueManagementInformation.TYPE;
    }

    /***
     * {@inheritDoc}
     *
     * @return
     */
    public synchronized String[] getAllQueueNames() {

        try {
            AndesMessageRouter queueMessageRouter = AndesContext.getInstance().getMessageRouterRegistry()
                    .getMessageRouter(AMQPUtils.DIRECT_EXCHANGE_NAME);
            List<String> queueNameList = queueMessageRouter.getNamesOfAllQueuesBound();
            String[] queues = new String[queueNameList.size()];
            queueNameList.toArray(queues);
            return queues;

        } catch (Exception e) {
            throw new RuntimeException("Error in accessing destination queues", e);
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map<String, Integer> getAllQueueCounts() {
        try {
            AndesMessageRouter queueMessageRouter = AndesContext.getInstance().getMessageRouterRegistry()
                    .getMessageRouter(AMQPUtils.DIRECT_EXCHANGE_NAME);
            List<String> queueNames = queueMessageRouter.getNamesOfAllQueuesBound();
            return Andes.getInstance().getMessageCountForAllQueues(queueNames);
        } catch (AndesException exception) {
            throw new RuntimeException("Error retrieving message count for all queues", exception);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CompositeData getMessageCountOfQueuesAsCompositeData() {
        CompositeDataSupport support;
        try {
            Map<String, java.lang.Integer> messageCounts = getAllQueueCounts();

            OpenType[] messageCountAttributeTypes = new OpenType[messageCounts.size()];
            String[] itemNames = messageCounts.keySet().toArray(new String[0]);
            String[] itemDescriptions = messageCounts.keySet().toArray(new String[0]);
            for (int count = 0; count < messageCounts.size(); count++) {
                messageCountAttributeTypes[count] = SimpleType.INTEGER;
            }
            CompositeType messageCountCompositeType = new CompositeType("Message Count of Queues",
                    "Message count of queues", itemNames, itemDescriptions, messageCountAttributeTypes);
            support = new CompositeDataSupport(messageCountCompositeType, messageCounts);
        } catch (OpenDataException e) {
            log.error("Error in accessing retrieving message count information", e);
            throw new RuntimeException("Error in accessing retrieving message count information", e);
        }
        return support;
    }

    /**
     * Get names of all durable queues created and registered in broker.
     * @return set of unique names of queues
     */
    @Override
    public Set<String> getNamesOfAllDurableQueues() {
        Set<String> namesOfDurableQueues = new HashSet<>();
        StorageQueueRegistry queueRegistry = AndesContext.getInstance().getStorageQueueRegistry();
        List<StorageQueue> queues = queueRegistry.getAllStorageQueues();
        for (StorageQueue queue : queues) {
            if (queue.isDurable()) {
                namesOfDurableQueues.add(queue.getName());
            }
        }
        return namesOfDurableQueues;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isQueueExists(String queueName) {
        try {
            List<String> queuesList = AndesContext.getInstance().getStorageQueueRegistry()
                    .getAllStorageQueueNames();
            return queuesList.contains(queueName);
        } catch (Exception e) {
            throw new RuntimeException("Error in accessing destination queues", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteAllMessagesInQueue(
            @MBeanOperationParameter(name = "queueName", description = "Name of the queue to delete messages from") String queueName,
            @MBeanOperationParameter(name = "ownerName", description = "Username of user that calls for "
                    + "purge") String ownerName)
            throws MBeanException {

        AMQQueue queue = queueRegistry.getQueue(new AMQShortString(queueName));

        try {
            if (queue == null) {
                throw new JMException("The Queue " + queueName + " is not a registered queue.");
            }

            queue.purge(0l); //This is to trigger the AMQChannel purge event so that the queue
            // state of qpid is updated. This method also validates the request owner and throws
            // an exception if permission is denied.

            InboundQueueEvent storageQueue = AMQPUtils.createInboundQueueEvent(queue);
            int purgedMessageCount = Andes.getInstance().purgeQueue(storageQueue);
            log.info("Total message count purged for queue (from store) : " + queueName + " : " + purgedMessageCount
                    + ". All in memory messages received before the purge call"
                    + " are abandoned from delivery phase. ");

        } catch (JMException jme) {
            if (jme.toString().contains("not a registered queue")) {
                throw new MBeanException(jme, "The Queue " + queueName + " is not a registered " + "queue.");
            } else {
                throw new MBeanException(jme, PURGE_QUEUE_ERROR + queueName);
            }
        } catch (AMQException | AndesException amqex) {
            throw new MBeanException(amqex, PURGE_QUEUE_ERROR + queueName);
        }
    }

    /**
     * Delete a selected message list from a given Dead Letter Queue of a tenant.
     *
     * @param andesMetadataIDs     The browser message Ids
     * @param destinationQueueName The Dead Letter Queue Name for the tenant
     */
    @Override
    public void deleteMessagesFromDeadLetterQueue(
            @MBeanOperationParameter(name = "andesMetadataIDs", description = "ID of the Messages to Be DELETED") long[] andesMetadataIDs,
            @MBeanOperationParameter(name = "destinationQueueName", description = "The Dead Letter Queue Name for the selected tenant") String destinationQueueName) {

        List<AndesMessageMetadata> messageMetadataList = new ArrayList<>(andesMetadataIDs.length);

        for (long andesMetadataID : andesMetadataIDs) {
            AndesMessageMetadata messageToRemove = new AndesMessageMetadata(andesMetadataID, null, false);
            messageToRemove.setStorageQueueName(destinationQueueName);
            messageToRemove.setDestination(destinationQueueName);
            messageMetadataList.add(messageToRemove);
        }

        // Deleting messages which are in the list.
        try {
            Andes.getInstance().deleteMessagesFromDLC(messageMetadataList);
        } catch (AndesException e) {
            throw new RuntimeException("Error deleting messages from Dead Letter Channel", e);
        }
    }

    /***
    * {@inheritDoc}
    */
    @Override
    public void restoreSelectedMessagesFromDeadLetterChannel(
            @MBeanOperationParameter(name = "andesMessageIds", description = "IDs of the Messages to Be restored") long[] andesMessageIds,
            @MBeanOperationParameter(name = "destinationQueueName", description = "Original destination queue of the messages") String destinationQueueName)
            throws MBeanException {

        if (null != andesMessageIds) {
            int movedMessageCount = -1;
            List<Long> andesMessageIdList = new ArrayList<>(andesMessageIds.length);
            Collections.addAll(andesMessageIdList, ArrayUtils.toObject(andesMessageIds));
            try {
                movedMessageCount = moveMessagesFromDLCToNewDestination(andesMessageIdList, destinationQueueName,
                        destinationQueueName, true);
            } catch (AndesException ex) {
                throw new MBeanException(ex, "Error occurred when restoring messages from DLC to original qeueue : "
                        + destinationQueueName + " movedMessageCount : " + movedMessageCount);
            }
        }
    }

    /***
     * {@inheritDoc}
     */
    @Override
    public void rerouteSelectedMessagesFromDeadLetterChannel(
            @MBeanOperationParameter(name = "andesMessageIds", description = "IDs of the Messages to Be Restored") long[] andesMessageIds,
            @MBeanOperationParameter(name = "sourceQueue", description = "The  original queue name of the messages") String sourceQueue,
            @MBeanOperationParameter(name = "targetQueue", description = "New destination queue for the messages") String targetQueue)
            throws MBeanException {

        if (null != andesMessageIds) {
            int movedMessageCount = -1;
            List<Long> andesMessageIdList = new ArrayList<>(andesMessageIds.length);
            Collections.addAll(andesMessageIdList, ArrayUtils.toObject(andesMessageIds));
            try {
                movedMessageCount = moveMessagesFromDLCToNewDestination(andesMessageIdList, sourceQueue,
                        targetQueue, false);
            } catch (AndesException ex) {
                throw new MBeanException(ex,
                        "Error occurred when moving messages destined to sourceQueue : " + sourceQueue
                                + " from DLC to targetQueue : " + targetQueue + ". movedMessageCount : "
                                + movedMessageCount);
            }
        }
    }

    /**
     * Create new andes message and update the chunk details.
     *
     * @param messageMetadata message metadata
     * @param messageParts message parts list
     * @return andesMessage
     */
    private AndesMessage createMessage(AndesMessageMetadata messageMetadata, List<AndesMessagePart> messageParts) {
        AndesMessageMetadata clonedMetadata = messageMetadata.shallowCopy(messageMetadata.getMessageID());
        AndesMessage andesMessage = new AndesMessage(clonedMetadata);
        // Update Andes message with all the chunk details
        for (AndesMessagePart messagePart : messageParts) {
            andesMessage.addMessagePart(messagePart);
        }
        return andesMessage;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CompositeData[] browseQueue(
            @MBeanOperationParameter(name = "queueName", description = "Name of queue to browse "
                    + "messages") String queueName,
            @MBeanOperationParameter(name = "lastMsgId", description = "Browse message this message id "
                    + "onwards") long nextMsgId,
            @MBeanOperationParameter(name = "maxMsgCount", description = "Maximum message count per "
                    + "request") int maxMsgCount)
            throws MBeanException {
        List<CompositeData> compositeDataList = new ArrayList<>();
        try {

            List<AndesMessageMetadata> nextNMessageMetadataFromQueue;
            if (!DLCQueueUtils.isDeadLetterQueue(queueName)) {
                nextNMessageMetadataFromQueue = Andes.getInstance().getNextNMessageMetadataFromQueue(queueName,
                        nextMsgId, maxMsgCount);
            } else {
                nextNMessageMetadataFromQueue = Andes.getInstance().getNextNMessageMetadataFromDLC(queueName,
                        nextMsgId, maxMsgCount);
            }

            return getDisplayableMetaData(nextNMessageMetadataFromQueue, true);

        } catch (AndesException e) {
            throw new MBeanException(e, "Error occurred in browse queue.");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public long getNumberOfMessagesInDLCForQueue(String queueName) throws MBeanException {
        try {
            return Andes.getInstance().getMessageCountInDLCForQueue(queueName,
                    DLCQueueUtils.identifyTenantInformationAndGenerateDLCString(queueName));
        } catch (AndesException e) {
            throw new MBeanException(e, "Error restoring messages from dead letter channel for:" + queueName);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CompositeData[] getMessagesInDLCForQueue(
            @MBeanOperationParameter(name = "queueName", description = "Name of queue to browse "
                    + "messages") String queueName,
            @MBeanOperationParameter(name = "lastMsgId", description = "Browse message this "
                    + "onwards") long nextMsgId,
            @MBeanOperationParameter(name = "maxMsgCount", description = "Maximum message count "
                    + "per request") int maxMessageCount)
            throws MBeanException {

        try {

            List<AndesMessageMetadata> nextNMessageMetadataFromQueue;
            if (!DLCQueueUtils.isDeadLetterQueue(queueName)) {
                nextNMessageMetadataFromQueue = Andes.getInstance().getNextNMessageMetadataInDLCForQueue(queueName,
                        DLCQueueUtils.identifyTenantInformationAndGenerateDLCString(queueName), nextMsgId,
                        maxMessageCount);
            } else {
                nextNMessageMetadataFromQueue = Andes.getInstance().getNextNMessageMetadataFromDLC(
                        DLCQueueUtils.identifyTenantInformationAndGenerateDLCString(queueName), nextMsgId,
                        maxMessageCount);
            }
            return getDisplayableMetaData(nextNMessageMetadataFromQueue, true);
        } catch (AndesException e) {
            throw new MBeanException(e, "Error occurred in browse queue.");
        }

    }

    /**
     * Extract MapMessage content from ByteBuffer
     *
     * @param wrapMsgContent ByteBuffer which contains data
     * @return extracted content as text
     */
    private String extractMapMessageContent(ByteBuffer wrapMsgContent) {
        wrapMsgContent.rewind();
        BBDecoder decoder = new BBDecoder();
        decoder.init(wrapMsgContent.buf());
        Map<String, Object> mapMassage = decoder.readMap();
        String wholeMsg = "";
        for (Map.Entry<String, Object> entry : mapMassage.entrySet()) {
            String mapName = entry.getKey();
            String mapVal = entry.getValue().toString();
            StringBuilder messageContentBuilder = new StringBuilder();
            wholeMsg = StringEscapeUtils.escapeHtml(
                    messageContentBuilder.append(mapName).append(": ").append(mapVal).append(", ").toString())
                    .trim();
        }
        return wholeMsg;
    }

    /**
     * Extract StreamMessage from ByteBuffer
     *
     * @param wrapMsgContent ByteBuffer which contains data
     * @param encoding       message encoding
     * @return extracted content as text
     * @throws CharacterCodingException
     */
    private String extractStreamMessageContent(ByteBuffer wrapMsgContent, String encoding)
            throws CharacterCodingException {
        String wholeMsg;
        boolean eofReached = false;
        StringBuilder messageContentBuilder = new StringBuilder();

        while (!eofReached) {

            try {
                Object obj = readObject(wrapMsgContent, encoding);
                // obj could be null if the wire type is AbstractBytesTypedMessage.NULL_STRING_TYPE
                if (null != obj) {
                    messageContentBuilder.append(obj.toString()).append(", ");
                }
            } catch (MessageEOFException ex) {
                eofReached = true;
            } catch (MessageNotReadableException e) {
                eofReached = true;
            } catch (MessageFormatException e) {
                eofReached = true;
            } catch (JMSException e) {
                eofReached = true;
            }
        }

        wholeMsg = StringEscapeUtils.escapeHtml(messageContentBuilder.toString());
        return wholeMsg;
    }

    /**
     * Extract TextMessage from ByteBuffer
     *
     * @param wrapMsgContent ByteBuffer which contains data
     * @param encoding       message encoding
     * @return extracted content as text
     * @throws CharacterCodingException
     */
    private String extractTextMessageContent(ByteBuffer wrapMsgContent, String encoding)
            throws CharacterCodingException {
        String wholeMsg;
        wholeMsg = wrapMsgContent.getString(Charset.forName(encoding).newDecoder());
        return wholeMsg;
    }

    /**
     * Read object from StreamMessage ByteBuffer content
     *
     * @param wrapMsgContent ByteBuffer which contains data
     * @param encoding       message encoding
     * @return Object extracted from ByteBuffer
     * @throws JMSException
     * @throws CharacterCodingException
     */
    private Object readObject(ByteBuffer wrapMsgContent, String encoding)
            throws JMSException, CharacterCodingException {
        int position = wrapMsgContent.position();
        checkAvailable(1, wrapMsgContent);
        byte wireType = wrapMsgContent.get();
        Object result = null;
        try {
            switch (wireType) {
            case BOOLEAN_TYPE:
                checkAvailable(1, wrapMsgContent);
                result = wrapMsgContent.get() != 0;
                break;
            case BYTE_TYPE:
                checkAvailable(1, wrapMsgContent);
                result = wrapMsgContent.get();
                break;
            case BYTEARRAY_TYPE:
                checkAvailable(4, wrapMsgContent);
                int size = wrapMsgContent.getInt();
                if (size == -1) {
                    result = null;
                } else {
                    byteArrayRemaining = size;
                    byte[] bytesResult = new byte[size];
                    readBytesImpl(wrapMsgContent, bytesResult);
                    result = bytesResult;
                }
                break;
            case SHORT_TYPE:
                checkAvailable(2, wrapMsgContent);
                result = wrapMsgContent.getShort();
                break;
            case CHAR_TYPE:
                checkAvailable(2, wrapMsgContent);
                result = wrapMsgContent.getChar();
                break;
            case INT_TYPE:
                checkAvailable(4, wrapMsgContent);
                result = wrapMsgContent.getInt();
                break;
            case LONG_TYPE:
                checkAvailable(8, wrapMsgContent);
                result = wrapMsgContent.getLong();
                break;
            case FLOAT_TYPE:
                checkAvailable(4, wrapMsgContent);
                result = wrapMsgContent.getFloat();
                break;
            case DOUBLE_TYPE:
                checkAvailable(8, wrapMsgContent);
                result = wrapMsgContent.getDouble();
                break;
            case NULL_STRING_TYPE:
                result = null;
                break;
            case STRING_TYPE:
                checkAvailable(1, wrapMsgContent);
                result = wrapMsgContent.getString(Charset.forName(encoding).newDecoder());
                break;
            }
            return result;
        } catch (RuntimeException e) {
            wrapMsgContent.position(position);
            throw e;
        }
    }

    /**
     * Read byte[] array object and return length
     *
     * @param wrapMsgContent ByteBuffer which contains data
     * @param bytes          byte[] object
     * @return length of byte[] array
     */
    private int readBytesImpl(ByteBuffer wrapMsgContent, byte[] bytes) {
        int count = (byteArrayRemaining >= bytes.length ? bytes.length : byteArrayRemaining);
        byteArrayRemaining -= count;
        if (count == 0) {
            return 0;
        } else {
            wrapMsgContent.get(bytes, 0, count);
            return count;
        }
    }

    /**
     * Check that there is at least a certain number of bytes available to read
     *
     * @param length the number of bytes
     * @throws javax.jms.MessageEOFException if there are less than len bytes available to read
     */
    private void checkAvailable(int length, ByteBuffer byteBuffer) throws MessageEOFException {
        if (byteBuffer.remaining() < length) {
            throw new MessageEOFException("Unable to read " + length + " bytes");
        }
    }

    /**
     * Return readable name for ContentType of message
     *
     * @param contentType content type of message
     * @return readable name
     */
    private String getReadableNameForMessageContentType(String contentType) {
        if (StringUtils.isNotBlank(contentType)) {
            if (contentType.equals(MIME_TYPE_TEXT_PLAIN) || contentType.equals(MIMI_TYPE_TEXT_XML)) {
                contentType = "Text";
            } else if (contentType.equals(MIME_TYPE_APPLICATION_JAVA_OBJECT_STREAM)) {
                contentType = "Object";
            } else if (contentType.equals(MIME_TYPE_AMQP_MAP) || contentType.equals(MIME_TYPE_JMS_MAP_MESSAGE)) {
                contentType = "Map";
            } else if (contentType.equals(MIME_TYPE_JMS_STREAM_MESSAGE)) {
                contentType = "Stream";
            } else if (contentType.equals(MIME_TYPE_APPLICATION_OCTET_STREAM)) {
                contentType = "Byte";
            }
        }
        return contentType;
    }

    /**
     * Retrieve a valid andes messageId list from a given browser message Id list.
     *
     * @param browserMessageIdList List of browser messageIds.
     * @return Valid Andes MessageId list
     */
    private LongArrayList getValidAndesMessageIdList(String[] browserMessageIdList) {
        LongArrayList andesMessageIdList = new LongArrayList(browserMessageIdList.length);

        for (String browserMessageId : browserMessageIdList) {
            Long andesMessageId = AndesUtils.getAndesMessageId(browserMessageId);

            if (andesMessageId > 0) {
                andesMessageIdList.add(andesMessageId);
            } else {
                log.warn("A valid message could not be found for the message Id : " + browserMessageId);
            }
        }

        return andesMessageIdList;
    }

    /**
     * We are returning message count to the UI from this method.
     * When it has received Acks from the clients more than the message actual
     * message in the  queue,( This can happen when a copy of a message get
     * delivered to the consumer while the ACK for the previouse message was
     * on the way back to server), Message count is becoming minus.
     * <p>
     * So from now on , we ll not provide minus values to the front end since
     * it is not acceptable
     */
    public long getMessageCount(String queueName, String msgPattern) throws MBeanException {

        if (log.isDebugEnabled()) {
            log.debug("Counting at queue : " + queueName);
        }

        long messageCount = 0;
        try {
            if (!DLCQueueUtils.isDeadLetterQueue(queueName)) {
                if ("queue".equals(msgPattern)) {
                    messageCount = Andes.getInstance().getMessageCountOfQueue(queueName);
                }
            } else {
                messageCount = Andes.getInstance().getMessageCountInDLC(queueName);
            }

        } catch (AndesException e) {
            log.error(MESSAGE_COUNT_RETRIEVE_ERROR + queueName, e);
            throw new MBeanException(e, MESSAGE_COUNT_RETRIEVE_ERROR + queueName);
        }

        return messageCount;
    }

    /**
     * Get DLC queue by name specified if already created
     *
     * @param queueName name of the DLc queue (including tenant information)
     * @return a Map with <DLCQueueName,MessageCount> entry
     */
    public Map<String, Long> getDLCQueueInformation(String queueName) throws MBeanException {

        Map<String, Long> DLCQueueInformation;

        try {
            DLCQueueInformation = new HashMap<>(1);

            AndesMessageRouter DLCMessageRouter = AndesContext.getInstance().getMessageRouterRegistry()
                    .getMessageRouter(AMQPUtils.DLC_EXCHANGE_NAME);

            List<StorageQueue> DLCQueues = DLCMessageRouter.getAllBoundQueues();

            for (StorageQueue dlcQueue : DLCQueues) {
                if (queueName.equals(dlcQueue.getName())) {
                    DLCQueueInformation.put(dlcQueue.getName(), dlcQueue.getMessageCount());
                    break;
                }
            }
        } catch (AndesException e) {
            throw new MBeanException(e, "Error while receiving DLC queue Information from MBeans");
        }

        return DLCQueueInformation;
    }

    /***
     * {@inheritDoc}
     */
    public int getSubscriptionCount(String queueName) {
        try {
            return AndesContext.getInstance().getAndesSubscriptionManager()
                    .numberOfSubscriptionsInCluster(queueName, ProtocolType.AMQP);
        } catch (Exception e) {
            throw new RuntimeException("Error in getting subscriber count", e);
        }
    }

    /**
     * Method to display a list of messages when browsed.
     *
     * @param metadataList the list of message metadata
     * @return Composite data array of properties of all messages
     * @throws MBeanException if an OpenDataException occurs while mapping JMS headers for the Message.
     */
    private CompositeData[] getDisplayableMetaData(List<AndesMessageMetadata> metadataList, boolean includeContent)
            throws MBeanException {
        List<CompositeData> compositeDataList = new ArrayList<>();
        try {
            for (AndesMessageMetadata andesMessageMetadata : metadataList) {
                Object[] itemValues = getItemValues(andesMessageMetadata, includeContent);
                if (null != itemValues) {
                    CompositeDataSupport support = new CompositeDataSupport(_msgContentType,
                            VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.toArray(
                                    new String[VIEW_MSG_CONTENT_COMPOSITE_ITEM_NAMES_DESC.size()]),
                            itemValues);
                    compositeDataList.add(support);
                }
            }
        } catch (OpenDataException exception) {
            throw new MBeanException(exception, "Error occurred when formatting message in queue.");
        }
        return compositeDataList.toArray(new CompositeData[compositeDataList.size()]);
    }

    /**
     * Method to get an array of properties of a single message.
     *
     * @param andesMessageMetadata andes message metadata to be parsed
     * @return an array of properties of the message
     * @throws MBeanException if an AMQException occurs while reading Message Content headers.
     */
    private Object[] getItemValues(AndesMessageMetadata andesMessageMetadata, boolean includeContent)
            throws MBeanException {
        try {

            Object[] itemValues = null;
            //get AMQMessage from AndesMessageMetadata
            AMQMessage amqMessage = AMQPUtils.getAMQMessageFromAndesMetaData(andesMessageMetadata);
            //header properties from AMQMessage
            BasicContentHeaderProperties properties = (BasicContentHeaderProperties) amqMessage
                    .getContentHeaderBody().getProperties();
            //get custom header properties of AMQMessage
            StringBuilder stringBuilder = new StringBuilder();
            for (String headerKey : properties.getHeaders().keys()) {
                stringBuilder.append(headerKey).append(" = ").append(properties.getHeaders().get(headerKey));
                stringBuilder.append(", ");
            }
            if (null != properties.getCorrelationId()) {
                stringBuilder.append("JMSCorrelationID").append(" = ").append(properties.getCorrelationId())
                        .append(", ");
            }
            if (null != properties.getReplyTo()) {
                stringBuilder.append("JMSReplyTo").append(" = ").append(properties.getReplyTo()).append(", ");
            }
            if (null != properties.getType()) {
                stringBuilder.append("JMSType").append(" = ").append(properties.getType()).append(", ");
            }
            String msgProperties = stringBuilder.toString();
            //get content type
            String contentType = properties.getContentTypeAsString();
            //get message id
            String messageId = properties.getMessageIdAsString();
            //get redelivered
            boolean redelivered = false;
            //get timestamp
            long timeStamp = properties.getTimestamp();
            //get destination
            String destination = andesMessageMetadata.getDestination();
            //get AndesMessageMetadata id
            long andesMessageMetadataId = andesMessageMetadata.getMessageID();

            //content is constructing
            final int bodySize = (int) amqMessage.getSize();

            if (includeContent) {
                AndesMessagePart constructedContent = constructContent(bodySize, amqMessage);
                byte[] messageContent = constructedContent.getData();
                int position = constructedContent.getOffset();
                //if position did not proceed, there is an error receiving content. If not, decode content
                if (!((bodySize != 0) && (position == 0))) {
                    String[] content = decodeContent(amqMessage, messageContent);
                    //set content type of message to readable name
                    contentType = getReadableNameForMessageContentType(contentType);
                    //set CompositeData of message
                    itemValues = new Object[] { msgProperties, contentType, content, messageId, redelivered,
                            timeStamp, destination, andesMessageMetadataId };
                } else if (bodySize == 0) { //empty message
                    itemValues = new Object[] { msgProperties, contentType, "", messageId, redelivered, timeStamp,
                            destination, andesMessageMetadataId };
                }
            } else {
                itemValues = new Object[] { msgProperties, contentType, new String[] {}, messageId, redelivered,
                        timeStamp, destination, andesMessageMetadataId };
            }
            return itemValues;
        } catch (AMQException exception) {
            throw new MBeanException(exception,
                    "Error occurred when formatting message with Id : " + andesMessageMetadata.getMessageID()
                            + " assigned to queue : " + andesMessageMetadata.getDestination());
        }
    }

    /**
     * Method to construct message body of a single message.
     *
     * @param bodySize   Original content size of the message
     * @param amqMessage AMQMessage
     * @return Message content and last position of written data as an AndesMessagePart
     * @throws MBeanException
     */
    private AndesMessagePart constructContent(int bodySize, AMQMessage amqMessage) throws MBeanException {

        AndesMessagePart andesMessagePart;

        if (amqMessage.getMessageMetaData().isCompressed()) {
            /* If the current message was compressed by the server, decompress the message content and, get it as an
             * AndesMessagePart
             */
            long messageID = amqMessage.getMessageId();
            LongArrayList messList = new LongArrayList();
            messList.add(messageID);

            try {
                LongObjectHashMap<List<AndesMessagePart>> contentListMap = MessagingEngine.getInstance()
                        .getContent(messList);
                List<AndesMessagePart> contentList = contentListMap.get(messageID);

                andesMessagePart = lz4CompressionHelper.getDecompressedMessage(contentList, bodySize);

            } catch (AndesException e) {
                throw new MBeanException(e, "Error occurred while construct the message content. Message ID:"
                        + amqMessage.getMessageId());
            }
        } else {
            byte[] messageContent = new byte[bodySize];

            //Getting a buffer, to write data into the byte array and to the buffer at the same time
            java.nio.ByteBuffer buffer = java.nio.ByteBuffer.wrap(messageContent);

            int position = 0;

            while (position < bodySize) {
                position = position + amqMessage.getContent(buffer, position);

                //If position did not proceed, there is an error receiving content
                if ((0 != bodySize) && (0 == position)) {
                    break;
                }

                //The limit is setting to the current position and then the position of the buffer is setting to zero
                buffer.flip();

                //The position of the buffer is setting to zero, the limit is setting to the capacity
                buffer.clear();
            }

            andesMessagePart = new AndesMessagePart();
            andesMessagePart.setData(messageContent);
            andesMessagePart.setOffSet(position);
        }

        return andesMessagePart;
    }

    /**
     * Method to decode content of a single message into text
     *
     * @param amqMessage     the message of which content need to be decoded
     * @param messageContent the byte array of message content to be decoded
     * @return A string array representing the decoded message content
     * @throws MBeanException
     */
    private String[] decodeContent(AMQMessage amqMessage, byte[] messageContent) throws MBeanException {

        try {
            //get encoding
            String encoding = amqMessage.getMessageHeader().getEncoding();
            if (encoding == null) {
                encoding = "UTF-8";
            }
            //get mime type of message
            String mimeType = amqMessage.getMessageHeader().getMimeType();
            // setting default mime type
            if (StringUtils.isBlank(mimeType)) {
                mimeType = MIME_TYPE_TEXT_PLAIN;
            }
            //create message content to readable text from ByteBuffer
            ByteBuffer wrapMsgContent = ByteBuffer.wrap(messageContent);
            String content[] = new String[2];
            String summaryMsg = "";
            String wholeMsg = "";

            //get TextMessage content to display
            if (mimeType.equals(MIME_TYPE_TEXT_PLAIN) || mimeType.equals(MIMI_TYPE_TEXT_XML)) {
                wholeMsg = extractTextMessageContent(wrapMsgContent, encoding);
                //get ByteMessage content to display
            } else if (mimeType.equals(MIME_TYPE_APPLICATION_JAVA_OBJECT_STREAM)
                    || mimeType.equals(MIME_TYPE_APPLICATION_OCTET_STREAM)) {
                wholeMsg = "This Operation is Not Supported!";
                //get StreamMessage content to display
            } else if (mimeType.equals(MIME_TYPE_JMS_STREAM_MESSAGE)) {
                wholeMsg = extractStreamMessageContent(wrapMsgContent, encoding);
                //get MapMessage content to display
            } else if (mimeType.equals(MIME_TYPE_AMQP_MAP) || mimeType.equals(MIME_TYPE_JMS_MAP_MESSAGE)) {
                wholeMsg = extractMapMessageContent(wrapMsgContent);
            }
            //trim content to summary and whole message
            if (wholeMsg.length() >= CHARACTERS_TO_SHOW) {
                summaryMsg = wholeMsg.substring(0, CHARACTERS_TO_SHOW);
            } else {
                summaryMsg = wholeMsg;
            }
            if (wholeMsg.length() > MESSAGE_DISPLAY_LENGTH_MAX) {
                wholeMsg = wholeMsg.substring(0, MESSAGE_DISPLAY_LENGTH_MAX - 3) + DISPLAY_CONTINUATION
                        + DISPLAY_LENGTH_EXCEEDED;
            }
            content[0] = summaryMsg;
            content[1] = wholeMsg;
            return content;
        } catch (CharacterCodingException exception) {
            throw new MBeanException(exception, "Error occurred in browse queue.");
        }
    }

    /**
     * Common method to restore a list of messages based on Id to its original queue or a different queue.
     *
     * @param messageIds             list of messages to be restored
     * @param sourceQueue            original destination queue of the messages.
     * @param targetQueue            new target destination of the messages.
     * @param restoreToOriginalQueue true if the messages need to be restored to their original
     *                               queues instead of a single target queue.
     * @return int Number of messages that were successfully restored.
     * @throws AndesException if the database calls to read/delete the messages/content fails.
     */
    private int moveMessagesFromDLCToNewDestination(List<Long> messageIds, String sourceQueue, String targetQueue,
            boolean restoreToOriginalQueue) throws AndesException {

        List<AndesMessageMetadata> messagesToRemove = new ArrayList<>(messageIds.size());

        LongArrayList messageIdCollection = new LongArrayList();
        for (Long messageId : messageIds) {
            messageIdCollection.add(messageId);
        }

        int movedMessageCount = 0;
        LongObjectHashMap<List<AndesMessagePart>> messageContent = Andes.getInstance()
                .getContent(messageIdCollection);
        boolean interruptedByFlowControl = false;

        for (Long messageId : messageIds) {
            if (restoreBlockedByFlowControl) {
                interruptedByFlowControl = true;
                break;
            }
            AndesMessageMetadata metadata = Andes.getInstance().getMessageMetaData(messageId);
            if (!restoreToOriginalQueue) {
                // Set the new destination queue
                StorageQueue newStorageQueue = AndesContext.getInstance().getStorageQueueRegistry()
                        .getStorageQueue(targetQueue);
                metadata.setDestination(targetQueue);
                metadata.setStorageQueueName(targetQueue);
                metadata.setMessageRouterName(newStorageQueue.getMessageRouter().getName());
                //update metadata with new queue, router and publish time
                metadata.updateMetadata(targetQueue, newStorageQueue.getMessageRouter().getName(),
                        System.currentTimeMillis());
            } else {
                //update metadata with new publish time
                metadata.updateMetadata(metadata.getDestination(), metadata.getMessageRouterName(),
                        System.currentTimeMillis());
            }

            //set new expiration time. This will be time now + original TTL
            long now = System.currentTimeMillis();
            metadata.setExpirationTime(now + (metadata.getExpirationTime() - metadata.getArrivalTime()));

            AndesMessageMetadata clonedMetadata = metadata.shallowCopy(metadata.getMessageID());
            AndesMessage andesMessage = new AndesMessage(clonedMetadata);

            messagesToRemove.add(metadata);
            // Update Andes message with all the chunk details
            if (!messageContent.isEmpty()) {
                List<AndesMessagePart> messageParts = messageContent.get(messageId);
                if (messageParts != null) {
                    for (AndesMessagePart messagePart : messageParts) {
                        andesMessage.addMessagePart(messagePart);
                    }
                }
            }

            // Handover message to Andes. This will generate a new message ID and store it
            Andes.getInstance().messageReceived(andesMessage, andesChannel, disablePubAck);

            movedMessageCount++;
        }

        if (interruptedByFlowControl) {
            // Throw this out so UI will show this to the user as an error message.
            // Messages can be duplicated
            throw new AndesException("Message restore from dead letter queue has been interrupted by flow "
                    + "control. Messages in the DLC for sourceQueue : " + sourceQueue + " may be duplicated due to "
                    + "this situation. Please try again later. movedMessageCount : " + movedMessageCount);
        }

        // Delete old messages
        Andes.getInstance().deleteMessagesFromDLC(messagesToRemove);

        return movedMessageCount;
    }

    /***
     * {@inheritDoc}
     */
    @Override
    public CompositeData[] getMessageMetadataInDeadLetterChannel(
            @MBeanOperationParameter(name = "targetQueue", description = "Name of destination queue ") String targetQueue,
            @MBeanOperationParameter(name = "startMessageId", description = "Message Id to start the resultset with.") long startMessageId,
            @MBeanOperationParameter(name = "pageLimit", description = "Maximum message count required in a single response") int pageLimit)
            throws MBeanException {

        try {
            List<AndesMessageMetadata> nextNMessageMetadataFromQueue;

            if (!DLCQueueUtils.isDeadLetterQueue(targetQueue)) {
                nextNMessageMetadataFromQueue = Andes.getInstance().getNextNMessageMetadataInDLCForQueue(
                        targetQueue, DLCQueueUtils.identifyTenantInformationAndGenerateDLCString(targetQueue),
                        startMessageId, pageLimit);
            } else {
                nextNMessageMetadataFromQueue = Andes.getInstance().getNextNMessageMetadataFromDLC(
                        DLCQueueUtils.identifyTenantInformationAndGenerateDLCString(targetQueue), startMessageId,
                        pageLimit);
            }
            return getDisplayableMetaData(nextNMessageMetadataFromQueue, false);

        } catch (AndesException e) {
            throw new MBeanException(e, "Error occurred when listing metadata in DLC for queue : " + targetQueue
                    + " from message Id : " + startMessageId + " onwards.");
        }
    }

    /***
     * {@inheritDoc}
     */
    @Override
    public int rerouteAllMessagesInDeadLetterChannelForQueue(
            @MBeanOperationParameter(name = "sourceQueue", description = "Name of the source queue") String sourceQueue,
            @MBeanOperationParameter(name = "targetQueue", description = "Name of the target queue") String targetQueue,
            @MBeanOperationParameter(name = "internalBatchSize", description = "Number of messages processed in a "
                    + "single database call.") int internalBatchSize)
            throws MBeanException {

        List<Long> currentMessageIdList;
        Long lastMessageId = 0L;
        int movedMessageCount = 0;

        // Get full name of Dead Letter Channel
        String dlcQueueName = DLCQueueUtils.identifyTenantInformationAndGenerateDLCString(sourceQueue);

        try {
            if (DLCQueueUtils.isDeadLetterQueue(sourceQueue)) {
                currentMessageIdList = Andes.getInstance().getNextNMessageIdsInDLC(dlcQueueName, lastMessageId,
                        internalBatchSize);

                while (currentMessageIdList.size() > 0) {
                    int movedMessageCountInThisBatch = moveMessagesFromDLCToNewDestination(currentMessageIdList,
                            sourceQueue, targetQueue, false);

                    if (log.isDebugEnabled()) {
                        log.debug("Successfully restored messages from DLC to targetQueue: " + targetQueue
                                + " movedMessageCountInThisBatch : " + movedMessageCountInThisBatch);
                    }

                    movedMessageCount = movedMessageCount + movedMessageCountInThisBatch;
                    lastMessageId = currentMessageIdList.get(currentMessageIdList.size() - 1);

                    currentMessageIdList = Andes.getInstance().getNextNMessageIdsInDLC(dlcQueueName, lastMessageId,
                            internalBatchSize);
                }
            } else {
                currentMessageIdList = Andes.getInstance().getNextNMessageIdsInDLCForQueue(sourceQueue,
                        dlcQueueName, lastMessageId, internalBatchSize);

                while (currentMessageIdList.size() > 0) {
                    int movedMessageCountInThisBatch = moveMessagesFromDLCToNewDestination(currentMessageIdList,
                            sourceQueue, targetQueue, false);

                    if (log.isDebugEnabled()) {
                        log.debug("Successfully restored messages from sourceQueue : " + sourceQueue
                                + " to targetQueue : " + targetQueue + " movedMessageCountInThisBatch : "
                                + movedMessageCountInThisBatch);
                    }

                    movedMessageCount = movedMessageCount + movedMessageCountInThisBatch;
                    lastMessageId = currentMessageIdList.get(currentMessageIdList.size() - 1);

                    currentMessageIdList = Andes.getInstance().getNextNMessageIdsInDLCForQueue(sourceQueue,
                            dlcQueueName, lastMessageId, internalBatchSize);
                }
            }
        } catch (AndesException ex) {
            throw new MBeanException(ex,
                    "Error occurred when moving metadata destined to sourceQueue : " + sourceQueue
                            + " from DLC to targetQueue : " + targetQueue + ". movedMessageCount : "
                            + movedMessageCount);
        }

        return movedMessageCount;
    }

}