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

com.addc.commons.queue.PersistingQueueReader 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.LinkedList;
import java.util.List;

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

import com.addc.commons.Mutex;

/**
 * The PersistingQueueReader reads items from a PersistingQueue and passes them
 * on to a PayloadDispatcher. This class will ensure that, on shutdown, any
 * outstanding items are sent on through the dispatcher or saved to the
 * persistence if the dispatcher fails.
 * 
 */
public class PersistingQueueReader implements Runnable {
    private static final Logger LOGGER= LoggerFactory.getLogger(PersistingQueueReader.class);

    private final PersistingQueueReaderState dispatcherState= new PersistingQueueReaderState<>();
    private final PersistingQueue queue;
    private final PayloadDispatcher dispatcher;
    private final Mutex delayMutex= new Mutex();
    private final ReaderDelayGenerator delayGenerator;
    private final List> listeners;
    private final String threadName;
    private Thread runnerThread;

    /**
     * Create a PersistingQueueReader
     * @param queue The {@link PersistingQueue} to read
     * @param threadName The name of the thread
     * @param dispatcher The {@link PayloadDispatcher} to send messages to
     */
    public PersistingQueueReader(PersistingQueue queue, String threadName, PayloadDispatcher dispatcher) {
        this.queue= queue;
        this.threadName= threadName;
        this.dispatcher= dispatcher;
        listeners= new LinkedList<>();
        delayGenerator= new ReaderDelayGenerator<>(this);
    }

    /**
     * Add a {@link PersistingQueueReaderListener}
     * @param listener The listener to add
     */
    public void addListener(PersistingQueueReaderListener listener) {
        synchronized (listeners) {
            listeners.add(listener);
        }
    }

    @Override
    public void run() {
        LOGGER.info("Thread starts...");
        do {
            // *******************************************************
            // If there was a Protocol Exception, wait for a while
            // *******************************************************
            if (dispatcherState.connectionLost && !isShutdown()) {
                delay();
            }

            // *******************************************************
            // If there isn't a batch to send, get one
            // *******************************************************
            if (dispatcherState.currentPayload == null) {
                dispatcherState.currentPayload= queue.take();
            }

            // *******************************************************
            // Take may return null at shutdown when the wait is
            // interrupted
            // *******************************************************
            if (dispatcherState.currentPayload != null) {
                processCurrentPayload();
            }

        } while (!isShutdown());
        LOGGER.info("Thread ends...");
    }

    /**
     * Shut down the dispatcher. Processes any outstanding payloads
     */
    public void shutdown() {
        LOGGER.info("Stop the dispatcher thread...");
        setShutdown(true);
        breakDelay();
        queue.interruptTake();

        try {
            runnerThread.join();
        } catch (InterruptedException e) {
            LOGGER.debug("Interrupted", e);
        }
        LOGGER.info("Recover any outstanding batches and send them...");
        if (dispatcherState.currentPayload == null) {
            dispatcherState.currentPayload= queue.poll();
        }

        if (dispatcherState.currentPayload != null) {
            sendPendingPayloads();
        }
        LOGGER.info("Dispatcher terminated, returning {}", dispatcherState.currentPayload);

        queue.shutdown(dispatcherState.currentPayload);

    }

    /**
     * Start the reader
     */
    public void start() {
        runnerThread= new Thread(this, threadName);
        runnerThread.start();
    }
    
    /**
     * Query whether the reader is shut down
     * @return true if the reader is dtopped
     */
    public boolean isShutdown() {
        synchronized (dispatcherState.shutdownLock) {
            return dispatcherState.shutdown;
        }
    }

    private void delay() {
        synchronized (delayMutex) {
            try {
                delayMutex.wait(delayGenerator.getDelay());
            } catch (InterruptedException e) {
                LOGGER.debug("Interrupted", e);
            }
        }
    }

    private void processCurrentPayload() {
        try {
            dispatcher.dispatch(dispatcherState.currentPayload);
            notifyForward(null);
            dispatcherState.currentPayload= null;
        } catch (DispatcherException e) {
            if (e.isRecoverable()) {
                dispatcherState.connectionLost= true;
                notifyForward(e);
                if (e.getRetryDelay() != null) {
                    delay(e.getRetryDelay());
                }
            } else {
                dispatcherState.connectionLost= true;
                notifyDispatcherError(e);
                setShutdown(true);
            }
        } catch (Exception e) {
            LOGGER.warn("Unexpected exception, aborting queue reader", e);
            setShutdown(true);
        }
    }

    private void delay(long timeout) {
        synchronized (delayMutex) {
            try {
                delayMutex.wait(timeout);
            } catch (InterruptedException e) {
                LOGGER.trace("Interrupted", e);
            }
        }
    }

    private void sendPendingPayloads() {
        boolean success= true;
        do {
            try {
                dispatcher.dispatch(dispatcherState.currentPayload);
                dispatcherState.currentPayload= null;
            } catch (Exception e) {
                dispatcherState.connectionLost= true;
                notifyForward(e);
                success= false;
            }
            // If the forward failed, currentBatch will NOT be null
            if (dispatcherState.currentPayload == null) {
                dispatcherState.currentPayload= queue.poll();
            }
        } while ((dispatcherState.currentPayload != null) && success);
    }

    private void notifyForward(Exception e) {
        synchronized (listeners) {
            for (PersistingQueueReaderListener listener : listeners) {
                listener.onProcess(dispatcherState.connectionLost, e);
            }
        }
    }

    private void notifyDispatcherError(DispatcherException e) {
        synchronized (listeners) {
            for (PersistingQueueReaderListener listener : listeners) {
                listener.onDispatcherError(e);
            }
        }
    }

    private void setShutdown(boolean b) {
        synchronized (dispatcherState.shutdownLock) {
            dispatcherState.shutdown= b;
        }
    }

    private void breakDelay() {
        synchronized (delayMutex) {
            delayMutex.notifyAll();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy