Package com.cloudhopper.mq.queue.impl

Source Code of com.cloudhopper.mq.queue.impl.DefaultQueueManager

package com.cloudhopper.mq.queue.impl;

/*
* #%L
* ch-mq
* %%
* Copyright (C) 2012 Cloudhopper by Twitter
* %%
* 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.
* #L%
*/

import com.cloudhopper.datastore.DataStore;
import com.cloudhopper.datastore.DataStoreFatalException;
import com.cloudhopper.datastore.DataStoreManager;
import com.cloudhopper.mq.message.PriorityMQMessage;
import com.cloudhopper.mq.queue.*;
import com.cloudhopper.mq.transcoder.ISO88591StringTranscoder;
import com.cloudhopper.mq.transcoder.Transcoder;
import com.cloudhopper.mq.util.CompositeKeyUtil;
import com.cloudhopper.mq.util.QueueNameValidator;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import javax.management.ObjectName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Manages creating and destroying queues based on a configuration.  Will load
* persistent queues at startup.
*
* @author joelauer
*/
public class DefaultQueueManager implements QueueManager, DefaultQueueManagerMBean {
    private static Logger logger = LoggerFactory.getLogger(DefaultQueueManager.class);

    /** all possible states of a QueueManager */
    private final static int STATE_STOPPED = 0;
    private final static int STATE_STOPPING = 1;
    private final static int STATE_STARTING = 2;
    private final static int STATE_STARTED = 3;
    private final static int STATE_DELETING = 4;

    // listeners watching for events
    private final CopyOnWriteArrayList<QueueManagerListener> listeners;
    // atomic check if DataStore is opening, open, or closing
    private final AtomicInteger state;
    // the configuration to use when starting the QueueManager
    private final QueueConfiguration configuration;
    // map of the name of a queue to a queue instance
    // concurrent hashmap does not block on retrievals
    private final ConcurrentHashMap<String,Queue> queueMap;
    // if a queue needs to be created/deleted/modified, this is the lock to the
    // modification atomic
    private final ReentrantLock lock;
    // map of the queueId to its QueueInfo (queueName, elementType, etc.)
    // non-synchronized instance since only threads with exclusive control can query/modify it
    private TreeMap<Integer,QueueInfo> queueInfoMap;
    // the datastore for persistent queues
    private DataStore datastore;
    // factory for creating queues
    private QueueFactory queueFactory;

    // flag for if we're not first-run, and failed our health check test on startup
    private boolean failedRestartHealthCheckTest = false;
   
    private static final ISO88591StringTranscoder HEALTH_CHECK_TRANSCODER = new ISO88591StringTranscoder();
   
    private final static String HEALTH_CHECK_ENTRY = "HEALTH CHECK ENTRY";
    private final static String HEALTH_CHECK_SHUTDOWN_QUEUE = ".SHUTDOWN";
    private final static String HEALTH_CHECK_TEST_QUEUE = ".TEST";
   
    public DefaultQueueManager(QueueConfiguration configuration) {
        // save the configuration
        this.configuration = configuration;
        this.listeners = new CopyOnWriteArrayList<QueueManagerListener>();
        this.state = new AtomicInteger(STATE_STOPPED);
        // map of queue names to queue instance
        this.queueMap = new ConcurrentHashMap<String,Queue>();
        // only used for adding and removing queues (uncommon cases)
        this.lock = new ReentrantLock();
        // queue info map is ONLY initialized when started via the map file
        this.queueInfoMap = null;
        registerQueueManagerMBean();
    }

    public void addListener(QueueManagerListener listener) {
        this.listeners.addIfAbsent(listener);
    }

    public void removeListener(QueueManagerListener listener) {
        this.listeners.remove(listener);
    }

    public QueueConfiguration getConfiguration() {
        return this.configuration;
    }
   
    /**
     * do a put/get test on the test queue
     */
    public boolean verifyQueuePutTakeSuccess() {
  boolean pass = false;
  try {
      Queue<String> queue = createQueue(configuration.getHealthCheckQueueName() + HEALTH_CHECK_TEST_QUEUE, String.class, this.HEALTH_CHECK_TRANSCODER);
      queue.getTemporarySession().put(this.HEALTH_CHECK_ENTRY, 10000);
     
      String value = (String) queue.getTemporarySession().take(1000l);
      if (value == null || !HEALTH_CHECK_ENTRY.equals(value)) {
    throw new Exception("No entry in Health Check test queue");
      }
      pass = true;
      logger.trace("Successful queue put/take test");
           
  } catch (Exception e) {
      logger.error("Problem doing health check put/take test: ", e);
  }
  return pass;
    }
   
    public boolean getFailedRestartHealthCheckTest() {
  return failedRestartHealthCheckTest;
    }

    private void registerQueueManagerMBean() {
        if (configuration == null) {
            return;
        }
        if (configuration.isJmxEnabled()) {
            // register the this queue manager as an mbean
            try {
                ObjectName name = new ObjectName(configuration.getJmxDomain() + ":name=QueueManager");
                configuration.getMBeanServer().registerMBean(this, name);
            } catch (Exception e) {
                // log the error, but don't throw an exception for this datasource
                logger.warn("Error while attempting to register QueueManager [{}] as an MBean: {}", configuration.getName(), e.getMessage());
            }
        }
    }

    private void registerQueueMBean(Queue queue) {
        if (configuration.isJmxEnabled()) {
            // register the this queue manager as an mbean
            try {
                ObjectName name = new ObjectName(configuration.getJmxDomain() + ":type=Queues,name=" + queue.getName());
                configuration.getMBeanServer().registerMBean(queue, name);
            } catch (Exception e) {
                // log the error, but don't throw an exception for this datasource
                logger.warn("Error while attempting to register Queue [{}] as an MBean: {}", queue.getName(), e.getMessage());
            }
        }
    }

    private void unregisterQueueMBean(Queue queue) {
        if (configuration.isJmxEnabled()) {
            // register the this queue manager as an mbean
            try {
                ObjectName name = new ObjectName(configuration.getJmxDomain() + ":type=Queues,name=" + queue.getName());
                // check if this is registered first
                if (configuration.getMBeanServer().isRegistered(name)) {
                    configuration.getMBeanServer().unregisterMBean(name);
                }
            } catch (Exception e) {
                // log the error, but don't throw an exception for this datasource
                logger.warn("Error while attempting to unregister Queue [{}] as an MBean: {}", queue.getName(), e.getMessage());
            }
        }
    }

    /**
     * Internally called whenever a new queue has been created.
     * @param queue The queue that was created
     */
    private void triggerQueueCreated(Queue queue) {
        // register the queue mbean
        registerQueueMBean(queue);

        // propagate event to all listeners
        for (QueueManagerListener listener : listeners) {
            try {
                listener.notifyQueueCreated(queue);
            } catch (Throwable t) {
                logger.error("Unable to cleanly propagate Queue [{}] was created", queue.getName());
            }
        }
    }

    private void triggerQueueDestroyed(Queue queue) {
        // register the queue mbean
        unregisterQueueMBean(queue);

        // propagate event to all listeners
        for (QueueManagerListener listener : listeners) {
            try {
                listener.notifyQueueDestroyed(queue);
            } catch (Throwable t) {
                logger.error("Unable to cleanly propagate Queue [{}] was destroyed", queue.getName());
            }
        }
    }

    protected void triggerQueueConsumerStarted(Queue queue, int oldCount, int newCount) {
        // we will mostly ignore this event, but need to trigger an event
        // if the num of consumers changed where the old value was zero
        if (oldCount <= 0) {
            // propagate event to all listeners
            for (QueueManagerListener listener : listeners) {
                try {
                    listener.notifyAtLeastOneQueueConsumerStarted(queue);
                } catch (Throwable t) {
                    logger.error("Unable to cleanly propagate that at least one consumer started on Queue [{}]", queue.getName());
                }
            }
        }
    }

    protected void triggerQueueConsumerStopped(Queue queue, int oldCount, int newCount) {
        // we will mostly ignore this event, but need to trigger an event
        // if the num of consumers changed where the newCount is zero
        if (newCount <= 0) {
            // propagate event to all listeners
            for (QueueManagerListener listener : listeners) {
                try {
                    listener.notifyAllQueueConsumersStopped(queue);
                } catch (Throwable t) {
                    logger.error("Unable to cleanly propagate that all consumers stopped on Queue [{}]", queue.getName());
                }
            }
        }
    }

    public boolean isStarted() {
        return (this.state.get() == STATE_STARTED);
    }

    public boolean isStopped() {
        return (this.state.get() == STATE_STOPPED);
    }

    public String getStateName() {
        return getStateName(this.state.get());
    }

    static private String getStateName(int state) {
        switch (state) {
            case STATE_STOPPED:
                return "STOPPED";
            case STATE_STOPPING:
                return "STOPPING";
            case STATE_STARTING:
                return "STARTING";
            case STATE_STARTED:
                return "STARTED";
            case STATE_DELETING:
                return "DELETING";
            default:
                return "UKNOWN";
        }
    }

    /**
     * Gets the "runtime" file associated with this QueueManager.  This file
     * contains information about the last time the QueueManager ran.  Used
     * to check if anything changed in the configuration that would cause issues
     * with this particular run.
     * @return The runtime File
     */
    public File getRunFile() {
        return new File(this.configuration.getDirectory(), this.configuration.getName() + ".run");
    }

    /**
     * Gets the "map" file containing information about each queue this QueueManager
     * is responsible for tracking.  The queue in the persistent store must
     * exist in this file, otherwise a QueueManager will fail on startup.
     * @return The map File
     */
    public File getMapFile() {
        return new File(this.configuration.getDirectory(), this.configuration.getName() + ".map");
    }

    /**
     * Gets the current number (size) of queues created.
     * @return The current number (size) of queues created.
     */
    public int getQueueCount() {
        return this.queueMap.size();
    }


    /**
     * Checks if the queue name matches the "local only" list.
     * @param queueName The name of the queue to match
     * @return True if matches, false otherwise.
     */
    protected boolean isLocalOnly(String queueName) {
        if (this.configuration == null) {
            logger.warn("Configuration object for DefaultQueueManager was null");
            return false;
        }
        return this.configuration.getLocalOnlyQueues().matches(queueName);
    }


    public void delete() throws QueueInvalidStateException, QueueFatalException, DataStoreFatalException {
        // NOTE: opportunistic locking
        // try to set the state to "deleting" only if the current state is "stopped"
        if (!state.compareAndSet(STATE_STOPPED, STATE_DELETING)) {
            int currentState = state.get();
            throw new QueueInvalidStateException("Unable to delete " + toString() + " since its not currently stopped [current state is " + getStateName(currentState) + "]");
        }

        try {
            datastore = createDataStoreFromCurrentConfiguration();
            // try delete everything
            getMapFile().delete();
            getRunFile().delete();
            datastore.delete();
        } finally {
            // always set state to stopped
            state.set(STATE_STOPPED);
        }
    }

    protected DataStore createDataStoreFromCurrentConfiguration() throws DataStoreFatalException {
        //
        // the DataStore has a nice method of verifying the directory exists
        // or creating it if it doesn't exist, let's try that first
        //
        DataStore ds = DataStoreManager.create(configuration.getDataStoreUrl());

        // replace datastore configuration with current config
        ds.setName(configuration.getName());
        ds.setDirectory(new File(configuration.getDirectory()));

        return ds;
    }

    public void start() throws QueueInvalidStateException, QueueFatalException, DataStoreFatalException, IOException {
        if (configuration == null) {
            throw new QueueFatalException("Unable to start - configuration is null or was not set");
        }

        // NOTE: opportunistic locking
        // try to set the state to "starting" only if the current state is "stopped"
        if (!state.compareAndSet(STATE_STOPPED, STATE_STARTING)) {
            int currentState = state.get();
            throw new QueueInvalidStateException("Unable to start " + toString() + " since its not currently stopped [current state is " + getStateName(currentState) + "]");
        }

        boolean started = false;
        // assume this is the first run of the QueueManager
        boolean firstRun = true;
        try {
            logger.info("Starting {}", this);

            //
            // the DataStore has a nice method of verifying the directory exists
            // or creating it if it doesn't exist, let's try that first
            //
            datastore = createDataStoreFromCurrentConfiguration();

            // make sure the datastore supports ascending iterators
            if (!datastore.hasAscendingIteratorSupport()) {
                throw new QueueFatalException("The DataStore for a QueueManager must support ascending iterators");
            }

            // try to open the datastore
            datastore.open();

            // get a file object to both .map and .run files
            File mapFile = getMapFile();
            File runFile = getRunFile();

            // do both the files exist?
            boolean mapFileExists = mapFile.exists();
            boolean runFileExists = runFile.exists();

            // if one of these files exists, make sure the other file exists
            if (mapFileExists && !runFileExists) {
                throw new QueueFatalException("QueueManager map file [" + mapFile + "] exists, but the run file [" + runFile + "] does not exist");
            } else if (!mapFileExists && runFileExists) {
                throw new QueueFatalException("QueueManager run file [" + runFile + "] exists, but the map file [" + mapFile + "] does not exist");
            } else if (mapFileExists && runFileExists) {
                // not first time running
                firstRun = false;
            } else {
                // first time running!
            }

            //
            // create or load run info
            //
            RunInfo runInfo = null;

            if (firstRun) {
                logger.info("First run of QueueManager, creating an initial RunInfo based on current configuration");
                // create a new RunInfo from configuration
                runInfo = RunInfoFactory.create(configuration);
            } else {
                logger.info("Previous run of QueueManager found, loading run file [{}]", runFile);
                // create a run info based on previous run
                runInfo = RunInfoFileUtil.readRunInfo(runFile);
                // is this current run compatible with previous run?
                RunInfo currentRun = RunInfoFactory.create(configuration);
                // check compatability of the current run with the previous run
                currentRun.checkCompatability(runInfo);
            }

            //
            // create or load queueInfo map
            //
            if (firstRun) {
                logger.info("First run of QueueManager, creating an initially empty QueueInfo map");
                // create an empty map
                queueInfoMap = new TreeMap<Integer,QueueInfo>();
            } else {
                logger.info("Previous run of QueueManager found, loading map file {}", mapFile);
                // create a new RunInfo from configuration
                queueInfoMap = QueueInfoFileUtil.readQueueInfoMap(mapFile);
            }

            //
            // create instances required for actually running
            //
            CompositeKeyUtil keyutil = new CompositeKeyUtil(runInfo.getQueueIdByteLength(), runInfo.getItemIdByteLength());
            queueFactory = new DefaultQueueFactory(this, keyutil, datastore);

            // load initial queues
            TreeMap<Integer,Queue> queues = queueFactory.loadQueues(queueInfoMap);

            logger.info("Loaded {} persistent queues from datastore", queues.size());

            // create the initial hashmap of queueNames to queues
            this.queueMap.clear();
            for (Map.Entry<Integer,Queue> entry : queues.entrySet()) {
                this.queueMap.put(entry.getValue().getName(), entry.getValue());
                // make sure to trigger the event
                this.triggerQueueCreated(entry.getValue());
            }

            //
            // create run and map files if this is the first run
            //
            if (firstRun) {
                boolean ok = false;
                try {
                    logger.info("First run of QueueManager, creating run file {}", runFile);
                    RunInfoFileUtil.writeRunInfo(runFile, runInfo);
                    logger.info("First run of QueueManager, creating map file {}", mapFile);
                    QueueInfoFileUtil.writeQueueInfoMap(mapFile, queueInfoMap);
                    ok = true;
                } finally {
                    // make sure to delete these two files if it didn't start correctly
                    if (!ok) {
                        runFile.delete();
                        mapFile.delete();
                    }
                }
            }

            logger.info("Successfully started {}", this);

            // if we got here, then everything succeeded and queueManager is started
            started = true;
        } finally {
            // always make sure to clean up the state
            if (started) {
                state.set(STATE_STARTED);

                // check for the presence of our health check queue and entry
                // this will help determine whether the queue is working correctly,
                // or maybe corrupted...
                if (!firstRun) {
                    failedRestartHealthCheckTest = testMissingHealthCheckRestartEntry();
                }

            } else {
                // make sure we stop the datastore if something failed after it
                if (datastore != null) {
                    try {
                        datastore.close();
                    } catch (Exception e) {
                        logger.error("QueueManager failed to start properly and unable to properly close DataStore", e);
                    }
                }
                datastore = null;

                // make sure clear any queues
                if (this.queueMap != null) {
                    this.queueMap.clear();
                }

                queueFactory = null;
                queueInfoMap = null;

                state.set(STATE_STOPPED);
            }
        }
    }


    public void stop() throws QueueInvalidStateException, QueueFatalException {
        // write shutdown entry to health check queue before we stop
        try {
            Queue<String> queue = createQueue(configuration.getHealthCheckQueueName()+HEALTH_CHECK_SHUTDOWN_QUEUE, String.class, this.HEALTH_CHECK_TRANSCODER);
            queue.getTemporarySession().put(this.HEALTH_CHECK_ENTRY, 10000);
            logger.info("Wrote health check shutdown entry");
        } catch (Exception e) {
            logger.error("Problem writing health check shutdown queue entry: "+ e);
        }

        // NOTE: opportunistic locking
        // try to set the state to "starting" only if the current state is "stopped"
        if (!state.compareAndSet(STATE_STARTED, STATE_STOPPING)) {
            int currentState = state.get();
            // is it already stopped or stopping?
            if (currentState == STATE_STOPPED || currentState == STATE_STOPPING) {
                logger.debug("Ignoring stop request since its already stopped or stopping");
                return;
            } else {
                throw new QueueInvalidStateException("Unable to stop " + toString() + " since its not currently started [current state is " + getStateName(currentState) + "]");
            }
        }

        try {
            logger.info("Stopping {}", this);

            // make sure clear any queues
            if (this.queueMap != null) {
                // stop all queues
                for (Map.Entry<String,Queue> entry : queueMap.entrySet()) {
                    logger.info("Shutting down queue [{}] with id [{}]", entry.getKey(), entry.getValue().getId());
                    // shutdown this queue, unload anything using it
                    entry.getValue().shutdown();
                    // let everything know this queue is gone
                    triggerQueueDestroyed(entry.getValue());
                }
                // clear the whole queue map
                this.queueMap.clear();
            }

            // make sure we stop the datastore if something failed after it
            if (datastore != null) {
                try {
                    datastore.close();
                } catch (Exception e) {
                    logger.error("QueueManager failed to properly close DataStore", e);
                }
            }
            datastore = null;

            queueFactory = null;
            queueInfoMap = null;

            logger.info("Successfully stopped {}", this);
        } finally {
            // always make sure to clean up the state
            state.set(STATE_STOPPED);
        }
    }

    /*
     * validate that the health check queue exists, and contains the
     * restart entry
     * if these don't exist, the queue might be corrupt
     * @return true if missing
     */
    private boolean testMissingHealthCheckRestartEntry() {
        boolean failed = false;
        try {
            Queue<String> queue = getQueue(configuration.getHealthCheckQueueName()+HEALTH_CHECK_SHUTDOWN_QUEUE);
            if (queue == null) {
                logger.warn("Health Check shutdown queue doesn't exist");
                failed = true;
            }
           
            try {
                String value = (String) queue.getTemporarySession().take(1000l);
                if (value == null || !HEALTH_CHECK_ENTRY.equals(value)) {
                    logger.warn("No shutdown entry in Health Check shutdown queue");
                    failed = true;
                }
            } catch (QueueTimeoutException e) {
                logger.warn("No shutdown entry in Health Check shutdown queue");
                failed = true;
            }
           
        } catch (Exception e) {
            logger.error("Problem reading from health check queue: ", e);
        }
        return failed;
    }

    public boolean hasQueue(String queueName) {
        return this.queueMap.containsKey(queueName);
    }

    /**
     * Gets the Queue by its name, but only if it already exists.  If the Queue
     * does not already exist, this will return null.  If unsure whether the
     * queue may already exist, its safer to always call createQueue with the
     * types of elements and transcoder.
     *
     * NOTE: This call is reentrant.  No need for external synchronization.
     *
     * @param queueName The name of the Queue to get
     * @return The Queue mapped by this name or null if it doesn't yet exist.
     */
    @SuppressWarnings("unchecked")
    public <E> Queue<E> getQueue(String queueName) throws QueueInvalidStateException {
        // verify this manager is started
        verifyIsStarted("createQueue", queueName);
       
        if (hasQueue(queueName)) {
            return this.queueMap.get(queueName);
        } else {
            return null;
        }
    }

    private void verifyIsStarted(String methodName, String queueName) throws QueueInvalidStateException {
        if (state.get() != STATE_STARTED) {
            throw new QueueInvalidStateException("Unable to complete " + methodName + (queueName != null ? " for Queue ["+queueName+"] " : "") + " since QueueManager is not started");
        }
    }

    /**
     * Creates or gets a Queue by name.  If the Queue does not yet exist, the
     * Queue will be created with the supplied element type and transcoder.
     * If the Queue already exists, the same Queue instance will be returned
     * on subsequent calls.  The first time a Queue is created, its creation
     * may take some time depending on how many other queues already exist.
     * It may be good to pre-create your Queues at program startup.
     *
     * NOTE: This call is reentrant.  No need for external synchronization.

     * @param queueName The name of the Queue to get (or create if needed).
     * @param elementType The type of element contained in this Queue.
     * @param transcoder The transcoder to encode/decode an element to/from
     *      a byte array.
     * @return The Queue that was created or the one previously created.
     * @throws QueueFatalException
     * @throws MaxQueuesReachedException
     * @throws InterruptedException
     * @throws DataStoreFatalException
     */
    @SuppressWarnings("unchecked")
    public <E> Queue<E> createQueue(String queueName, Class<E> elementType, Transcoder<E> transcoder) throws QueueInvalidStateException, QueueFatalException, MaxQueuesReachedException, InterruptedException, DataStoreFatalException {
  return (Queue<E>)createQueue(DefaultQueue.class, null, queueName, elementType, transcoder);
    }
   
    /**
     * Creates or gets a Priority ueue by name.  If the Queue does not yet exist, the
     * Queue will be created with the supplied element type and transcoder.
     * If the Queue already exists, the same Queue instance will be returned
     * on subsequent calls.  The first time a Queue is created, its creation
     * may take some time depending on how many other queues already exist.
     * It may be good to pre-create your Queues at program startup.
     *
     * NOTE: This call is reentrant.  No need for external synchronization.
     *
     * @param queueName The name of the Queue to get (or create if needed).
     * @param elementType The type of element contained in this Queue.
     * @param transcoder The transcoder to encode/decode an element to/from
     * a byte array.
     * @return The Queue that was created or the one previously created.
     * @throws QueueFatalException
     * @throws MaxQueuesReachedException
     * @throws InterruptedException
     * @throws DataStoreFatalException
     */
    @SuppressWarnings(value = "unchecked")
    public <E> Queue<PriorityMQMessage<E>> createPriorityQueue(String queueName, Class<E> elementType, Transcoder<E> transcoder) throws QueueInvalidStateException, QueueFatalException, MaxQueuesReachedException, InterruptedException, DataStoreFatalException {
  return (Queue<PriorityMQMessage<E>>)createQueue(DirectPriorityQueue.class, null, queueName, elementType, transcoder);
    }

    /**
     * Creates or gets a Queue by name/type.  If the Queue does not yet exist, the
     * Queue will be created with the supplied element type and transcoder.
     * If the Queue already exists, the same Queue instance will be returned
     * on subsequent calls.  The first time a Queue is created, its creation
     * may take some time depending on how many other queues already exist.
     * It may be good to pre-create your Queues at program startup.
     *
     * NOTE: This call is reentrant.  No need for external synchronization.
     *
     * @param queueType The class of the Queue type to be created
     * @param properties Additional properties for creating the queue
     * @param queueName The name of the Queue to get (or create if needed).
     * @param elementType The type of element contained in this Queue.
     * @param transcoder The transcoder to encode/decode an element to/from
     * a byte array.
     * @return The Queue that was created or the one previously created.
     * @throws QueueFatalException
     * @throws MaxQueuesReachedException
     * @throws InterruptedException
     * @throws DataStoreFatalException
     */
    @SuppressWarnings(value = "unchecked")
    public <E> Queue<E> createQueue(Class<? extends InitializingQueue> queueType, Map<String,Object> properties, String queueName, Class<E> elementType, Transcoder<E> transcoder) throws QueueInvalidStateException, QueueFatalException, MaxQueuesReachedException, InterruptedException, DataStoreFatalException {
        // verify this manager is started
        verifyIsStarted("createQueue", queueName);

        // 99% of the time, the queue should already exist
        // a concurrent hashmap impl does not block on a retrieve, so this operation
        // should be super fast and
        Queue queue = queueMap.get(queueName);

        if (queue != null) {
      if (queueType.isInstance(queue)) {
    logger.trace("Queue {} already exists. Returning.", queueName);
    return queue;
      } else {
    logger.error("Queue {} already exists, but is of type {} rather than required type {}", queueName, queue.getClass().getName(), queueType);
    throw new QueueFatalException("Queue is of incorrect type.");
      }
        }

        // since the queue isn't here, we may need to create it -- we will now
        // validate that the queue name is valid
        QueueNameValidator.validate(queueName);

        boolean createdNewQueue = false;

        // if it's null, then the queue needs to be created, get an exclusive
        // lock for modifying the hashmap of queues
        lock.lockInterruptibly();
        try {
            // make sure the queue wasn't created by another thread
            // that grabbed the lock first
            if (queueMap.containsKey(queueName)) {
                return queueMap.get(queueName);
            }

            // at this point, we've guaranteed we have an exclusive lock and
            // that another thread didn't create the queue while we were waiting for it

            // get the next free numeric id we'll assign to the queue
      int queueId = this.assignNextQueueInfo(queueName, queueType, elementType, transcoder.getClass());

            if (queueId == -1) {
                // unable to find a free queue id which means we've reached our
                // maximum number of possible queues
                throw new MaxQueuesReachedException("Reached maximum number of [" + configuration.getMaxQueues() + "] queues");
            }

            logger.info("Assigned id [{}] to queue [{}]", queueId, queueName);

            try {
                // create the queue
    queue = queueFactory.createQueue(queueType, properties, queueId, queueName, elementType, transcoder);

                // save the updated QueueInfo map
                QueueInfoFileUtil.writeQueueInfoMap(getMapFile(), queueInfoMap);

                createdNewQueue = true;
            } catch (IOException e) {
                //logger.error("Unable to properly create queue and update map file", e);
                throw new QueueFatalException("Failed to create queue and update map file", e);
            } finally {
                if (!createdNewQueue) {
                    // unassign queueId
                    this.deleteQueueInfo(queueId);
                    logger.info("Since queue failed to be created correctly, deleting assigned queueId [{}] for the new queue [{}]", queueId, queueName);
                }
            }

            // add the queue to the hashmap
            queueMap.put(queueName, queue);

            logger.info("Successfully created queue [{}] with id [{}]", queueName, queueId);
        } finally {
            lock.unlock();
        }

        if (createdNewQueue) {
            // call trigger outside the lock
            triggerQueueCreated(queue);
        }

        return queue;
    }

    public boolean deleteQueue(String queueName) throws QueueInvalidStateException, QueueFatalException, DataStoreFatalException, InterruptedException {
        // verify this manager is started
        verifyIsStarted("deleteQueue", queueName);

        Queue queue = null;

        // if it's null, then the queue needs to be created, get an exclusive
        // lock for modifying the hashmap of queues
        lock.lockInterruptibly();
        try {
            // remove the queue if exists
            queue = queueMap.remove(queueName);

            if (queue == null) {
                return false;
            }

            logger.info("Deleting queue [{}] with id [{}]", queueName, queue.getId());

            // remove this from our queue info map
            deleteQueueInfo(queue.getId());

            // destroy all the sessions (they'll all return an error)
            queue.destroyAllSessions();

            // purge all the data and items from it
            queue.purge();

            // shut it down
            queue.shutdown();

            try {
                // save the updated QueueInfo map
                QueueInfoFileUtil.writeQueueInfoMap(getMapFile(), queueInfoMap);
            } catch (IOException e) {
                throw new QueueFatalException("Failed to cleanly delete queue [" + queueName + "] while updating map file", e);
            }
        } finally {
            lock.unlock();
        }

        logger.info("Successfully deleted queue [{}]", queueName);

        // call trigger outside the lock
        triggerQueueDestroyed(queue);

        return true;
    }

    /**
     * Get all queues managed by this QueueManager.
     * @return All queues managed by this QueueManager.
     */
    public Enumeration<Queue> getQueues() {
        return this.queueMap.elements();
    }

    public Map<Integer,QueueInfo> getQueueInfoMap() {
  return Collections.unmodifiableMap(this.queueInfoMap);
    }

    protected ConcurrentHashMap<String,Queue> getQueueMap() {
        return this.queueMap;
    }

    protected void deleteQueueInfo(int queueId) {
        this.queueInfoMap.remove(queueId);
    }

    protected int assignNextQueueInfo(String queueName, Class queueType, Class elementType, Class transcoderType) {
        // create a new queueInfo object
        QueueInfo queueInfo = new QueueInfo(queueName, queueType, elementType, transcoderType);

        // start search from 0, continue till max, and try to atomically search
        // for the next free queue id
        for (int i = 0; i < configuration.getMaxQueues(); i++) {
            if (!this.queueInfoMap.containsKey(i)) {
                this.queueInfoMap.put(i, queueInfo);
                return i;
            }
            // continue trying the next id
        }

        // if we get here, then all ids are taken!
        return -1;
    }

    public String getName() {
        return configuration.getName();
    }

    public String getDirectory() {
        return configuration.getDirectory();
    }

    public int getMaxQueues() {
        return configuration.getMaxQueues();
    }

    public long getMaxItemsPerQueue() {
        return configuration.getMaxItemsPerQueue();
    }

    public String getDataStoreUrl() {
        return configuration.getDataStoreUrl();
    }

    /**
     * Returns the underlying datastore. WARNING: Use of the datastore directly may cause
     * unpredictable behavior.
     */
    public DataStore getDataStore() {
  return this.datastore;
    }

}
TOP

Related Classes of com.cloudhopper.mq.queue.impl.DefaultQueueManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.