All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.addc.commons.queue.PersistingQueue Maven / Gradle / Ivy

Go to download

The addc-queues library supplies support for internal persistent queues using an optional DERBY database for storage.

The newest version!
package com.addc.commons.queue;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.addc.commons.Constants;
import com.addc.commons.database.Database;
import com.addc.commons.database.DatabaseException;
import com.addc.commons.database.derby.DerbyDatabase;
import com.addc.commons.i18n.I18nTextFactory;
import com.addc.commons.jmx.MBeanServerHelper;
import com.addc.commons.queue.persistence.DefaultElementSerializer;
import com.addc.commons.queue.persistence.PersistentQueue;
import com.addc.commons.queue.persistence.PersistentQueueException;

/**
 * The PersistingQueue supplies a bounded queue that will optionally persist any
 * overflow and save any queue elements that are left in the buffer on shutdown.
 * 

* When the queue is created/started it will fill itself with any elements in * persistence allowing recovery of any unread elements from the previos session. *

* The order of the elements added is guaranteed for any queue reader. *

* If persistence is not enabled, the queue will throttle when its buffer is * full to try to give the reader time to get elements from the queue and * liberate space in the buffer. If the throttling fails to free any space, the * element will be dropped and any {@link PersistingQueueTransitionListener}s * will be notified. *

* The class is thread-safe. * */ public class PersistingQueue { private static final Long[] RETRY_DELAYS= { 10L, 13L, 18L, 25L }; private static final Logger LOGGER= LoggerFactory.getLogger(PersistingQueue.class); private final PersistentQueue persistentQueue; private final LinkedBlockingQueue payloadBuffer; private final Object bufferLock= new Object(); private final Object takeLock= new Object(); private final List throttleDelays; private final PersistingQueueStatistics statistics; private final List listeners= Collections .synchronizedList(new LinkedList()); private final List> transitionListeners= Collections .synchronizedList(new LinkedList>()); private Iterator throttleIterator; private boolean shutdown; private boolean interruptTake; private final boolean dumpStatisticsOnExit; /** * Create new persistingQueue that will use the * {@link DefaultElementSerializer} if persistence is enabled in the * configuration and will not dump statistics on shutdown. * * @param config * The configuration for underlying persistence * @param statistics * The {@link PersistingQueueStatistics} to use. This will be * registered as an MBean if its getObjectName() does not return * null. * @param maxBufferSize * The maximum number of messages in the queue before they are * persisted or dropped * @throws DatabaseException * If the {@link Database} cannot be initialized * @throws PersistentQueueException * If the internal {@link PersistentQueue} cannot be * initialized. */ public PersistingQueue(PersistenceConfig config, PersistingQueueStatistics statistics, int maxBufferSize) throws DatabaseException, PersistentQueueException { this(config, statistics, false, maxBufferSize, null); } /** * Create new persistingQueue * * @param config * The configuration for underlying persistence * @param statistics * The {@link PersistingQueueStatistics} to use. This will be * registered as an MBean if its getObjectName() does not return * null. * @param dumpStatisticsOnExit * true to dump statistics on exit * @param maxBufferSize * The maximum number of messages in the queue before they are * persisted or dropped * @param serializer * The {@link ElementSerializer} to use if persistence is * enabled, if null a * {@link DefaultElementSerializer} will be used * @throws DatabaseException * If the {@link Database} cannot be initialized * @throws PersistentQueueException * If the internal {@link PersistentQueue} cannot be * initialized. */ public PersistingQueue(PersistenceConfig config, PersistingQueueStatistics statistics, boolean dumpStatisticsOnExit, int maxBufferSize, ElementSerializer serializer) throws DatabaseException, PersistentQueueException { this.dumpStatisticsOnExit= dumpStatisticsOnExit; this.payloadBuffer= new LinkedBlockingQueue<>(maxBufferSize); this.statistics= statistics; this.throttleDelays= Arrays.asList(RETRY_DELAYS); if (config.isPersistent()) { Database database= new DerbyDatabase(config.getDerbyDbDir(), config.isEncrypted()); if (serializer == null) { this.persistentQueue= new PersistentQueue<>(database, new DefaultElementSerializer()); } else { this.persistentQueue= new PersistentQueue<>(database, serializer); } LOGGER.info("Using persistent queue"); fillBufferFromPersistentQueue(); } else { this.persistentQueue= null; } MBeanServerHelper.getInstance().registerStandardMBean(statistics, statistics.getObjectName()); LOGGER.info("Created Persisting Queue"); } /** * This method is synchronized internally as several threads can put elements in the queue. * * @param element * element to enqueue * @throws IllegalStateException * If the queue has been shut down */ public void put(T element) { if (shutdown) { throw new IllegalStateException( I18nTextFactory.getTranslator(Constants.BASENAME).translate(Constants.QUEUE_SHUTDOWN)); } synchronized (bufferLock) { LOGGER.debug("{}", element); statistics.itemCreated(element); if (payloadBuffer.offer(element)) { checkStateAndNotify(); } else { if (isPersistent()) { saveToPersistentQueue(element); } else { // We try to give the put another chance just in case // the buffer isn't being read fast enough throttleIterator= throttleDelays.iterator(); boolean success= false; while (throttleIterator.hasNext() && !success) { throttle(); success= payloadBuffer.offer(element); } noPersistencePostProcessing(element, success); } } } LOGGER.trace("Signal take()"); synchronized (takeLock) { takeLock.notify(); } LOGGER.trace("Signalled take()"); } /** * Waits until there is something in the buffer or untill the take is interrupted. * * @return the next available element. Can be null if the take was * interrupted. * @throws IllegalStateException * If the queue has been shut down */ @SuppressWarnings("PMD.AssignmentInOperand") public T take() { if (shutdown) { throw new IllegalStateException( I18nTextFactory.getTranslator(Constants.BASENAME).translate(Constants.QUEUE_SHUTDOWN)); } T element= null; synchronized (takeLock) { // interruptTake= false; while (!interruptTake && (element= poll()) == null) { try { takeLock.wait(500L); } catch (@SuppressWarnings("unused") InterruptedException e) { LOGGER.info("The take has been interrupted"); break; } } interruptTake= false; } return element; } /** * This will interrupt an ongoing take method if there is one; otherwise it * does nothing. * */ public void interruptTake() { synchronized (takeLock) { interruptTake= true; takeLock.notifyAll(); } } /** * Returns the first element in the buffer which may be null if the buffer * is empty. After removing the first element from the buffer and if the * queue is persistent, the buffer will be filled from the persistent queue * if there are any elements available there. * * @return the next available element which may be null * @throws IllegalStateException * If the queue has been shut down */ public T poll() { if (shutdown) { throw new IllegalStateException( I18nTextFactory.getTranslator(Constants.BASENAME).translate(Constants.QUEUE_SHUTDOWN)); } T element= null; synchronized (bufferLock) { if (payloadBuffer.isEmpty()) { fillBufferFromPersistentQueue(); } if (!payloadBuffer.isEmpty()) { element= payloadBuffer.remove(); checkStateAndNotify(); fillBufferFromPersistentQueue(); } } LOGGER.debug("{}", element); return element; } /** * Saves the element passed to it (the last element polled) and any elements * stored in the buffer to the persistent queue * * @param element * A pending element that should be stored before the buffer */ public void shutdown(T element) { if (shutdown) { throw new IllegalStateException( I18nTextFactory.getTranslator(Constants.BASENAME).translate(Constants.QUEUE_SHUTDOWN)); } this.shutdown= true; if (isPersistent()) { if (element != null) { LOGGER.info("Saving first outstanding element to persistent queue..."); saveToPersistentQueue(element); } for (T payload : payloadBuffer) { LOGGER.info("Pushing queue to persistence..."); saveToPersistentQueue(payload); } persistentQueue.shutdown(); } else { if (element != null) { statistics.itemDropped(element); } for (T payload : payloadBuffer) { statistics.itemDropped(payload); } payloadBuffer.clear(); } for (PersistingQueueTransitionListener listener : transitionListeners) { listener.onShutdown(); } if (dumpStatisticsOnExit) { LOGGER.info(statistics.getQueueStatistics()); } LOGGER.info("Unregistering Statistics MBean ..."); if (!MBeanServerHelper.getInstance().unregisterMBean(statistics.getObjectName())) { LOGGER.error("Failed to unregister MBean {}", statistics.getObjectName()); } LOGGER.info("Terminated."); } /** * Query whether the queue is shut down * * @return whether the queue is shut down */ public boolean isShutdown() { return shutdown; } private void noPersistencePostProcessing(T element, boolean success) { if (success) { for (PersistingQueueTransitionListener listener : transitionListeners) { listener.onPut(); } } else { LOGGER.debug("Dropping {}", element); statistics.itemDropped(element); for (PersistingQueueTransitionListener listener : transitionListeners) { listener.onDrop(element); } } } /** * Gets all the events it can from the persistent queue and puts them in the * buffer */ @SuppressWarnings("PMD.AssignmentInOperand") private void fillBufferFromPersistentQueue() { if (isPersistent()) { LOGGER.debug("Fill the buffer from database"); T element; boolean done= false; do { element= persistentQueue.poll(); if (element == null) { done= true; } else { statistics.itemReadFromPersistence(element); LOGGER.debug("Read {} from persistence", element); done= payloadBuffer.offer(element); } } while (!done); } } private void saveToPersistentQueue(T element) { try { persistentQueue.put(element); statistics.itemWrittenToPersistence(element); LOGGER.debug("Wrote {} to persistence", element); } catch (Exception e) { LOGGER.error(I18nTextFactory.getTranslator(Constants.BASENAME).translate(Constants.QUEUE_DROP_ELEMENT), e); statistics.itemDropped(element); } } /** * Sleep for a time before trying to put the batch again in the buffer * * @param incomingBatch * @throws InterruptedException */ private void throttle() { long nextTimeToSleep= throttleIterator.next(); LOGGER.debug("Sleeping for {}", nextTimeToSleep); try { Thread.sleep(nextTimeToSleep); } catch (InterruptedException e) { LOGGER.debug("Error while throttling", e); } } /** * Adds a {@link PersistingQueueStateListener} * * @param listener * the listener to be added */ public void addListener(PersistingQueueStateListener listener) { this.listeners.add(listener); } /** * Adds a {@link PersistingQueueTransitionListener} * * @param listener * The listener to add */ public void addTransitionListener(PersistingQueueTransitionListener listener) { this.transitionListeners.add(listener); } /** * Checks the state of this batch queue and notifies all the listeners if it * is needed * */ private void checkStateAndNotify() { for (PersistingQueueStateListener listener : listeners) { if (payloadBuffer.remainingCapacity() < 2) { listener.onGettingFull(); } else if (payloadBuffer.size() == 1) { listener.onGettingEmpty(); } } for (PersistingQueueTransitionListener listener : transitionListeners) { listener.onPut(); } } /** * For testing */ boolean isPersistent() { return persistentQueue != null; } /** * For testing */ PersistentQueue getPersistentQueue() { return persistentQueue; } /** * For testing */ void clear() { payloadBuffer.clear(); if (isPersistent()) { try { persistentQueue.clear(); } catch (PersistentQueueException e) { LOGGER.error("FATAL: Error clearing persistent queue.", e); } } } /** * for testing */ LinkedBlockingQueue getPayloadBuffer() { return payloadBuffer; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy