package org.marketcetera.core;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.concurrent.ThreadSafe;
import org.marketcetera.util.log.SLF4JLoggerProxy;
import org.marketcetera.util.misc.ClassVersion;
import org.springframework.context.Lifecycle;
/* $License$ */
/**
* Provides a framework for processing data in a separate thread.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: QueueProcessor.java 16901 2014-05-11 16:14:11Z colin $
* @since 2.4.0
*/
@ThreadSafe
@ClassVersion("$Id: QueueProcessor.java 16901 2014-05-11 16:14:11Z colin $")
public abstract class QueueProcessor<Clazz>
implements Runnable, Lifecycle
{
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#isRunning()
*/
@Override
public boolean isRunning()
{
return running.get();
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#start()
*/
@Override
public synchronized void start()
{
if(isRunning()) {
return;
}
try {
onStart();
} catch (Exception e) {
Messages.UNABLE_TO_START.warn(this,
e,
threadDescriptor);
throw new RuntimeException(e);
}
keepAlive.set(true);
thread = new Thread(this,
threadDescriptor);
thread.start();
running.set(true);
}
/* (non-Javadoc)
* @see org.springframework.context.Lifecycle#stop()
*/
@Override
public synchronized void stop()
{
if(!isRunning()) {
return;
}
try {
onStop();
} catch (Exception e) {
Messages.ERROR_DURING_STOP.warn(this,
e,
threadDescriptor);
}
keepAlive.set(false);
if(thread != null) {
thread.interrupt();
try {
thread.join();
} catch (InterruptedException ignored) {}
}
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
@Override
public void run()
{
try {
lastException = null;
interrupted.set(false);
Messages.STARTED.info(this,
threadDescriptor);
while(keepAlive.get()) {
try {
Clazz dataObject = queue.take();
SLF4JLoggerProxy.trace(this,
"Queue processor {} processing {}",
threadDescriptor,
dataObject);
processData(dataObject);
} catch (InterruptedException e) {
throw e;
} catch (Exception e) {
lastException = e;
if(shutdownOnException(e)) {
throw e;
}
Messages.IGNORING_EXCEPTION.warn(this,
e,
threadDescriptor);
}
}
} catch (InterruptedException e) {
interrupted.set(true);
Messages.INTERRUPTED.info(this,
threadDescriptor);
} catch (Exception e) {
lastException = e;
Messages.SHUTTING_DOWN_FROM_ERROR.warn(this,
e,
threadDescriptor);
stop();
} finally {
keepAlive.set(false);
running.set(false);
Messages.STOPPED.info(this,
threadDescriptor);
}
}
/**
* Gets the queue to process.
*
* @return a <code>BlockingQueue<Clazz></code> value
*/
protected BlockingQueue<Clazz> getQueue()
{
return queue;
}
/**
* Processes the given data.
*
* <p>This method is invoked when data is available in the processing queue. Data
* is guaranteed to be processed in the order mandated by the queue. No other data
* will be processed until this method returns.
*
* @param inData a <code>Clazz</code> value
* @throws Exception an <code>Exception</code> value
*/
protected abstract void processData(Clazz inData)
throws Exception;
/**
* Called when the processor starts.
*
* <p>Any exception thrown will prevent the processor from starting.
*
* @throws Exception if an error occurs
*/
protected void onStart()
throws Exception {}
/**
* Called when the processor stops.
*
* <p>Any exception thrown will not prevent the processor from stopping.
*
* @throws Exception if an error occurs
*/
protected void onStop()
throws Exception {}
/**
* Gets the last exception thrown by the processor during data processing.
*
* @return an <code>Exception</code> value or <code>null</code>
*/
protected Exception getLastException()
{
return lastException;
}
/**
* Indicates if the queue processor was interrupted during data processing.
*
* @return a <code>boolean</code> value
*/
protected boolean wasInterrupted()
{
return interrupted.get();
}
/**
* Indicates if the queue processor should shutdown as a result of the given exception.
*
* @param inException an <code>Exception</code> value
* @return a <code>boolean</code> value
*/
protected boolean shutdownOnException(Exception inException)
{
return inException instanceof InterruptedException;
}
/**
* Create a new QueueProcessor instance.
*/
protected QueueProcessor()
{
this("Unknown Queue Processor");
}
/**
* Create a new QueueProcessor instance.
*
* @param inThreadDescriptor a <code>String</code> value describing the processor
*/
protected QueueProcessor(String inThreadDescriptor)
{
if(inThreadDescriptor == null) {
throw new NullPointerException();
}
queue = new LinkedBlockingDeque<Clazz>();
threadDescriptor = inThreadDescriptor;
}
/**
* last exception thrown during data processing or <code>null</code>
*/
private volatile Exception lastException;
/**
* thread value used to process data
*/
private volatile Thread thread;
/**
* indicates if the processor was interrupted during data processing
*/
private final AtomicBoolean interrupted = new AtomicBoolean(false);
/**
* indicates if the processor is running
*/
private final AtomicBoolean running = new AtomicBoolean(false);
/**
* indicates if the processor should continue to process data
*/
private final AtomicBoolean keepAlive = new AtomicBoolean(true);
/**
* queue used to hold data to be processed
*/
private final BlockingQueue<Clazz> queue;
/**
* describes the thread
*/
private final String threadDescriptor;
}