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;
}
}