package com.grendelscan.queues;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.grendelscan.data.database.Database;
import com.grendelscan.data.database.DatabaseUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.grendelscan.scan.InterruptedScanException;
import com.grendelscan.scan.Scan;
public abstract class AbstractScanQueue implements DatabaseUser
{
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractScanQueue.class);
private static final int MIN_QUEUE_AGE_BEFORE_THREAD_CREATION = 30000;
private final String name;
private boolean paused = false;
private long queueEmptyStart;
private long queueFullStart;
boolean shutdown = false;
private boolean running = true;
private final QueueThreadGroup threadGroup;
final List<AbstractQueueThread> threads; // intentionally default visibility
private final String queueTableName;
protected final Database database;
public abstract QueueItem getNextQueueItem();
protected abstract int getMaxThreadCount();
protected abstract void removeQueueItem(QueueItem finishedItem);
protected abstract AbstractQueueThread getNewThread();
protected abstract String getDBPath();
protected AbstractScanQueue(String queueName, String queueTableName)
{
this.name = queueName;
this.queueTableName = queueTableName;
threadGroup = new QueueThreadGroup(name + " - thread group");
threads = new ArrayList<AbstractQueueThread>();
queueFullStart = 0;
database = new Database(getDBPath());
initializeDatabase();
}
public void start(int initialThreadCount)
{
startNewThreads(initialThreadCount);
}
private synchronized void startNewThreads(int count)
{
if (count + threads.size() > getMaxThreadCount())
{
count = getMaxThreadCount() - threads.size();
}
for (int i = 0; i < count; i++)
{
AbstractQueueThread newThread = getNewThread();
threads.add(newThread);
newThread.start();
}
}
private synchronized void stopThreads(int count)
{
if (count >= threads.size())
{
count = threads.size() - 1;
}
for (int i = 0; i < count; i++)
{
getThreads().get(i).setLastWorkItem(true);
}
}
public int getQueueLength()
{
int length = 0;
try
{
length = database.selectSimpleInt(
"SELECT COUNT(*) " +
"FROM " + queueTableName + " " +
"WHERE locked = 0", new Object[]{});
}
catch (Throwable e)
{
LOGGER.error("Problem with getting " + name + " queue length: " + e.toString(), e);
}
return length;
}
public String getName()
{
return name;
}
public QueueThreadGroup getThreadGroup()
{
return threadGroup;
}
public List<AbstractQueueThread> getThreads()
{
return threads;
}
/**
*
* @return True if the queue isn't running
*/
public void handlePause_isRunning() throws InterruptedScanException
{
while(isPaused() && running)
{
synchronized(this)
{
try
{
wait(250);
}
catch (InterruptedException e)
{
throw new InterruptedScanException(getName() + " queue was interrupted: " + e.toString(), e);
}
}
}
if (!running)
{
throw new InterruptedScanException(getName() + " queue is stopped");
}
}
// final public void checkIsRunning() throws InterruptedException
// {
// if (!running)
// {
// throw new InterruptedException(getName() + " queue is stopped");
// }
// Scan.getInstance().isTerminated();
// }
public void setPaused(boolean paused)
{
this.paused = paused;
}
void pruneThreads()
{
List<AbstractQueueThread> tempThreads = new ArrayList<AbstractQueueThread>(threads);
for(AbstractQueueThread thread: tempThreads)
{
if(!thread.isAlive())
{
threads.remove(thread);
}
}
}
@Override
public void shutdown(final boolean gracefully)
{
LOGGER.info("Shutting down " + getName() + " queue");
running = false;
for (int i = 0; i < threads.size(); i++)
{
threads.get(i).interupt();
}
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
int timeout = 5000;
int delay = 250;
while (timeout > 0 && threads.size() > 0)
{
pruneThreads();
synchronized(this)
{
try
{
wait(delay);
}
catch (InterruptedException e)
{
break;
}
}
timeout -= delay;
}
interuptThreads();
try
{
database.stop(gracefully).join();
}
catch (InterruptedException e)
{
LOGGER.debug(getName() + " shutdown interupted: " + e.toString(), e);
}
shutdown = true;
}
}, getName() + " queue shutdown");
t.start();
}
public void checkThreadCount() throws InterruptedScanException
{
Date date = new Date();
if (threads.size() > getMaxThreadCount())
{
stopThreads(threads.size() - getMaxThreadCount());
}
handlePause_isRunning();
if (getQueueLength() > 0)
{
queueEmptyStart = 0;
if (threads.size() < getMaxThreadCount())
{
if (queueFullStart > 0)
{
if (date.getTime() - queueFullStart > MIN_QUEUE_AGE_BEFORE_THREAD_CREATION)
{
startNewThreads(1);
}
}
else
{
queueFullStart = date.getTime();
startNewThreads(1);
}
}
}
else
{
queueFullStart = 0;
if (queueEmptyStart > 0)
{
if (date.getTime() - queueFullStart > MIN_QUEUE_AGE_BEFORE_THREAD_CREATION)
{
stopThreads(1);
}
}
else
{
queueEmptyStart = date.getTime();
}
}
pruneThreads();
}
protected abstract void initializeOldDatabase();
private void initializeDatabase()
{
try
{
if (database.tableExists(queueTableName))
{
LOGGER.debug("Initializing old database for " + name + " job storage");
database.execute("UPDATE " + queueTableName + " SET locked = 0");
initializeOldDatabase();
}
else
{
initializeNewDatabase();
}
}
catch (Throwable e)
{
LOGGER.error("Problem with updating database: " + e.toString(), e);
System.exit(1);
}
}
protected abstract void initializeNewDatabase();
public String getQueueTableName()
{
return queueTableName;
}
/**
* Intentionally default visibility
*/
void interuptThreads()
{
pruneThreads();
for(AbstractQueueThread thread: threads)
{
thread.interupt();
}
}
public final boolean isShutdown()
{
return shutdown;
}
public final boolean isPaused()
{
return paused || Scan.getInstance().isPaused();
}
}