org.mule.util.queue.RandomAccessFileQueueStore.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.util.queue.RandomAccessFileQueueStore.java

Source

/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.util.queue;

import org.mule.api.MuleRuntimeException;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Basic queueing functionality with file storage.
 */
class RandomAccessFileQueueStore {

    private final Log logger = LogFactory.getLog(this.getClass());
    protected static final int CONTROL_DATA_SIZE = 5;
    private static final byte NOT_REMOVED = 0;
    private static final byte REMOVED = 1;
    private final QueueFileProvider queueFileProvider;

    private LinkedList<Long> orderedKeys = new LinkedList<Long>();
    private long fileTotalSpace = 0;

    public RandomAccessFileQueueStore(QueueFileProvider queueFileProvider) {
        this.queueFileProvider = queueFileProvider;
        initialise();
    }

    /**
     * @return the File where the content is stored.
     */
    public File getFile() {
        return this.queueFileProvider.getFile();
    }

    /**
     * Adds element at the end of the queue.
     *
     * @param element element to add
     */
    public synchronized void addLast(byte[] element) {
        long filePointer = writeData(element);
        orderedKeys.addLast(filePointer);
    }

    /**
     * Remove and returns data from the queue.
     *
     * @return data from the beginning of the queue.
     * @throws InterruptedException
     */
    public synchronized byte[] removeFirst() throws InterruptedException {
        try {
            if (orderedKeys.isEmpty()) {
                return null;
            }
            Long filePosition = orderedKeys.getFirst();
            queueFileProvider.getRandomAccessFile().seek(filePosition);
            queueFileProvider.getRandomAccessFile().writeByte(RandomAccessFileQueueStore.REMOVED);
            byte[] data = readDataInCurrentPosition();
            orderedKeys.removeFirst();
            return data;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Retrieves the first element from the queue without removing it.
     *
     * @return first element from the queue.
     * @throws InterruptedException
     */
    public synchronized byte[] getFirst() throws InterruptedException {
        return readFirstValue();
    }

    /**
     * Adds an element in the beginning of the queue.
     *
     * @param item element to add.
     * @throws InterruptedException
     */
    public synchronized void addFirst(byte[] item) throws InterruptedException {
        orderedKeys.addFirst(writeData(item));
    }

    /**
     * @return the size of the queue.
     */
    public int getSize() {
        return orderedKeys.size();
    }

    /**
     * removes all the elements from the queue.
     */
    public synchronized void clear() {
        try {
            queueFileProvider.getRandomAccessFile().close();
            orderedKeys.clear();
            fileTotalSpace = 0;
            queueFileProvider.recreate();
        } catch (IOException e) {
            throw new MuleRuntimeException(e);
        }
    }

    /**
     * Adds a collection of elements at the end of the queue.
     *
     * @param items collection of elements to add.
     * @return true if it were able to add them all, false otherwise.
     */
    public synchronized boolean addAll(Collection<? extends byte[]> items) {
        for (byte[] item : items) {
            addLast(item);
        }
        return true;
    }

    /**
     * Use this method carefully since it required bit amount of IO.
     *
     * @return all the elements from the queue.
     */
    public synchronized Collection<byte[]> allElements() {
        List<byte[]> elements = new LinkedList<byte[]>();
        try {
            queueFileProvider.getRandomAccessFile().seek(0);
            while (true) {
                boolean removed = queueFileProvider.getRandomAccessFile().readBoolean();
                if (!removed) {
                    elements.add(readDataInCurrentPosition());
                } else {
                    moveFilePointerToNextData();
                }
            }
        } catch (IOException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e);
            }
        }
        return elements;
    }

    /**
     * @return true if there's no elements in the queue, false otherwise
     */
    public boolean isEmpty() {
        return orderedKeys.isEmpty();
    }

    /**
     * Removes data from the queue according to a {@link RawDataSelector}
     * instance that determines if a certain element must be removed.
     *
     * @param rawDataSelector to determine if the element must be removed.
     * @return true if an element was removed
     */
    public synchronized boolean remove(RawDataSelector rawDataSelector) {
        try {
            queueFileProvider.getRandomAccessFile().seek(0);
            while (true) {
                long currentPosition = queueFileProvider.getRandomAccessFile().getFilePointer();
                byte removed = queueFileProvider.getRandomAccessFile().readByte();
                if (removed == 0) {
                    byte[] data = readDataInCurrentPosition();
                    if (rawDataSelector.isSelectedData(data)) {
                        queueFileProvider.getRandomAccessFile().seek(currentPosition);
                        queueFileProvider.getRandomAccessFile().writeByte(REMOVED);
                        orderedKeys.remove(currentPosition);
                        return true;
                    }
                }
            }
        } catch (EOFException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e);
            }
            return false;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Free all resources held for the queue.
     * <p/>
     * Do not removes elements from the queue.
     */
    public synchronized void close() {
        try {
            this.queueFileProvider.close();
        } catch (IOException e) {
            logAndIgnore(e);
        }
    }

    private void logAndIgnore(IOException e) {
        logger.warn(e.getMessage());
        if (logger.isDebugEnabled()) {
            logger.debug(e);
        }
    }

    /**
     * Deletes the files backing this queue. This method must only be invoked
     * after {@link #close()} has been executed on {@code this} instance
     */
    public synchronized void delete() {
        queueFileProvider.delete();
    }

    private byte[] readDataInCurrentPosition() throws IOException {
        int serializedValueSize = queueFileProvider.getRandomAccessFile().readInt();
        byte[] data = new byte[serializedValueSize];
        queueFileProvider.getRandomAccessFile().read(data, 0, serializedValueSize);
        return data;
    }

    private long writeData(byte[] data) {
        try {
            if (getSize() > 0) {
                queueFileProvider.getRandomAccessFile().seek(fileTotalSpace);
            }
            long filePointer = queueFileProvider.getRandomAccessFile().getFilePointer();
            int totalBytesRequired = CONTROL_DATA_SIZE + data.length;
            ByteBuffer byteBuffer = ByteBuffer.allocate(totalBytesRequired);
            byteBuffer.put(NOT_REMOVED);
            byteBuffer.putInt(data.length);
            byteBuffer.put(data);
            queueFileProvider.getRandomAccessFile().write(byteBuffer.array());
            fileTotalSpace += totalBytesRequired;
            return filePointer;
        } catch (IOException e) {
            throw new MuleRuntimeException(e);
        }
    }

    private void initialise() {
        try {
            queueFileProvider.getRandomAccessFile().seek(0);
            while (true) {
                long position = queueFileProvider.getRandomAccessFile().getFilePointer();
                byte removed = queueFileProvider.getRandomAccessFile().readByte();
                if (removed == NOT_REMOVED) {
                    orderedKeys.add(position);
                    moveFilePointerToNextData();
                } else {
                    moveFilePointerToNextData();
                }
            }
        } catch (EOFException e) {
            try {
                fileTotalSpace = queueFileProvider.getRandomAccessFile().length();
            } catch (IOException ioe) {
                throw new MuleRuntimeException(e);
            }
            if (logger.isDebugEnabled()) {
                logger.debug(e);
            }
        } catch (Exception e) {
            throw new MuleRuntimeException(e);
        }
    }

    private byte[] readFirstValue() {
        try {
            if (orderedKeys.isEmpty()) {
                return null;
            }
            Long filePointer = orderedKeys.getFirst();
            queueFileProvider.getRandomAccessFile().seek(filePointer);
            queueFileProvider.getRandomAccessFile().readByte(); //Always true since it's a key
            return readDataInCurrentPosition();
        } catch (IOException e) {
            throw new MuleRuntimeException(e);
        }
    }

    private void moveFilePointerToNextData() throws IOException {
        int serializedValueSize = queueFileProvider.getRandomAccessFile().readInt();
        queueFileProvider.getRandomAccessFile()
                .seek(queueFileProvider.getRandomAccessFile().getFilePointer() + serializedValueSize);
    }

    /**
     * @return the length of the file in bytes.
     */
    public long getLength() {
        return fileTotalSpace;
    }

    /**
     * Searches for data within the queue store using a {@link RawDataSelector}
     *
     * @param rawDataSelector to determine if the element is the one we are looking for
     * @return true if an element exists within the queue, false otherwise
     */
    public synchronized boolean contains(RawDataSelector rawDataSelector) {
        try {
            queueFileProvider.getRandomAccessFile().seek(0);
            while (true) {
                byte removed = queueFileProvider.getRandomAccessFile().readByte();
                if (removed == NOT_REMOVED) {
                    byte[] data = readDataInCurrentPosition();
                    if (rawDataSelector.isSelectedData(data)) {
                        return true;
                    }
                } else {
                    moveFilePointerToNextData();
                }
            }
        } catch (EOFException e) {
            if (logger.isDebugEnabled()) {
                logger.debug(e);
            }
            return false;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}