/** * Copyright (c) 2009 - 2010 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org> * * This file is part of org.appwork.utils.event.queue * * This software is licensed under the Artistic License 2.0, * see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php * for details */ package org.appwork.utils.event.queue; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.atomic.AtomicInteger; import org.appwork.utils.logging.Log; /** * @author daniel * @param <D> * @param <T> * */ public abstract class Queue extends Thread { public enum QueuePriority { HIGH, LOW, NORM } protected boolean debugFlag = false; protected QueuePriority[] prios; protected HashMap<QueuePriority, ArrayList<QueueAction<?, ? extends Throwable>>> queue = new HashMap<QueuePriority, ArrayList<QueueAction<?, ? extends Throwable>>>(); protected final Object queueLock = new Object(); protected ArrayList<QueueAction<?, ? extends Throwable>> queueThreadHistory = new ArrayList<QueueAction<?, ? extends Throwable>>(20); protected Thread thread = null; protected boolean waitFlag = true; protected static AtomicInteger QUEUELOOPPREVENTION = new AtomicInteger(0); public Queue(final String id) { super(id); Queue.QUEUELOOPPREVENTION.incrementAndGet(); /* init queue */ this.prios = QueuePriority.values(); for (final QueuePriority prio : this.prios) { this.queue.put(prio, new ArrayList<QueueAction<?, ? extends Throwable>>()); } /* jvm should not wait for waiting queues */ this.setDaemon(true); this.start(); } /** * This method adds an action to the queue. if the caller is a queueaction * itself, the action will be executed directly. In this case, this method * can throw Exceptions. If the caller is not the QUeuethread, this method * is not able to throw exceptions, but the exceptions are passed to the * exeptionhandler method of the queueaction * * @param <T> * @param <E> * @param item * @throws T */ public <E, T extends Throwable> void add(final QueueAction<?, T> action) throws T { /* set calling Thread to current item */ action.reset(); action.setCallerThread(this, Thread.currentThread()); if (this.isQueueThread(action)) { /* * call comes from current running item, so lets start item */ this.startItem(action, false); } else { /* call does not come from current running item, so lets queue it */ this.internalAdd(action); } } /** * Only use this method if you can asure that the caller is NEVER the queue * itself. if you are not sure use #add * * @param <E> * @param <T> * @param action * @throws T */ public <E, T extends Throwable> void addAsynch(final QueueAction<?, T> action) { /* set calling Thread to current item */ if (action.allowAsync() == false && this.isQueueThread(action)) { throw new RuntimeException("called addAsynch from the queue itself"); } else { action.reset(); action.setCallerThread(this, Thread.currentThread()); this.internalAdd(action); } } @SuppressWarnings("unchecked") public <E, T extends Throwable> E addWait(final QueueAction<E, T> item) throws T { /* set calling Thread to current item */ item.reset(); item.setCallerThread(this, Thread.currentThread()); if (this.isQueueThread(item)) { /* * call comes from current running item, so lets start item * excaption handling is passed to top item. startItem throws an * exception in error case */ this.startItem(item, false); } else { /* call does not come from current running item, so lets queue it */ this.internalAdd(item); /* wait till item is finished */ try { while (!item.isFinished()) { synchronized (item) { item.wait(1000); } } } catch (final InterruptedException e) { item.handleException(e); } if (item.getExeption() != null) { // throw exception if item canot handle the exception itself if (!item.callExceptionHandler()) { if (item.getExeption() instanceof RuntimeException) { throw (RuntimeException) item.getExeption(); } else { throw (T) item.getExeption(); } } } if (item.gotKilled() && !item.gotStarted()) { item.handleException(new InterruptedException("Queue got killed!")); } } return item.getResult(); } public void enqueue(final QueueAction<?, ?> action) { /* set calling Thread to current item */ action.reset(); action.setCallerThread(this, Thread.currentThread()); this.internalAdd(action); } protected QueueAction<?, ? extends Throwable> getLastHistoryItem() { synchronized (this.queueThreadHistory) { if (this.queueThreadHistory.size() == 0) { return null; } return this.queueThreadHistory.get(this.queueThreadHistory.size() - 1); } } /* returns size of queue with given priority */ public long getQueueSize(final QueuePriority prio) { if (prio == null) { return -1; } synchronized (this.queueLock) { final ArrayList<QueueAction<?, ? extends Throwable>> ret = this.queue.get(prio); if (ret == null) { return -1; } return ret.size(); } } /** * Overwrite this to hook before a action execution */ protected void handlePreRun() { // TODO Auto-generated method stub } public void internalAdd(final QueueAction<?, ?> action) { synchronized (this.queueLock) { this.queue.get(action.getQueuePrio()).add(action); } synchronized (this) { if (this.waitFlag) { this.waitFlag = false; this.notify(); } } } /** * returns true if this queue shows debug info * * @return */ public boolean isDebug() { return this.debugFlag; } public boolean isEmpty() { synchronized (this.queueLock) { for (final QueuePriority prio : this.prios) { if (!this.queue.get(prio).isEmpty()) { return false; } } return true; } } /** * this functions returns true if the current running Thread is our * QueueThread OR the SourceQueueItem chain is rooted in current running * QueueItem */ public boolean isQueueThread(final QueueAction<?, ? extends Throwable> item) { if (Thread.currentThread() == this.thread) { return true; } QueueAction<?, ? extends Throwable> last = item; Thread t = null; /* * we walk through actionHistory to check if we are still in our * QueueThread */ int loopprevention = 0; while (last != null && (t = last.getCallerThread()) != null) { if (t != null && t instanceof Queue) { if (t == this.thread) { if (this.debugFlag) { org.appwork.utils.logging.Log.L.warning("Multiple queues detected-> external synchronization may be required! " + item); } return true; } last = ((Queue) t).getLastHistoryItem(); if (loopprevention > Queue.QUEUELOOPPREVENTION.get()) { /* * loop prevention: while can only loop max * QUEUELOOPPREVENTION times, cause no more different queues * exist */ if (this.debugFlag) { org.appwork.utils.logging.Log.L.warning("QueueLoopPrevention!"); } break; } loopprevention++; } else { break; } } return false; } public boolean isWaiting() { return this.waitFlag; } public void killQueue() { synchronized (this.queueLock) { for (final QueuePriority prio : this.prios) { for (final QueueAction<?, ? extends Throwable> item : this.queue.get(prio)) { /* kill item */ item.kill(); synchronized (item) { item.notify(); } } /* clear queue */ this.queue.get(prio).clear(); } } } @Override public void run() { if (this.thread != null) { return; } this.thread = this; QueueAction<?, ? extends Throwable> item = null; while (true) { try { this.handlePreRun(); synchronized (this) { while (this.waitFlag) { try { this.wait(); } catch (final Exception e) { org.appwork.utils.logging.Log.exception(e); } } } synchronized (this.queueLock) { item = null; for (final QueuePriority prio : this.prios) { if (this.queue.get(prio).size() > 0) { item = this.queue.get(prio).remove(0); break; } } if (item == null) { this.waitFlag = true; } } if (item == null || this.waitFlag) { continue; } try { this.startItem(item, true); } catch (final Throwable e) { } } catch (final Throwable e) { Log.L.info("Queue rescued!"); Log.exception(e); } } } /** * changes this queue's debugFlag * * @param b */ public void setDebug(final boolean b) { this.debugFlag = b; } /* if you override this, DON'T forget to notify item when its done! */ @SuppressWarnings("unchecked") protected <T extends Throwable> void startItem(final QueueAction<?, T> item, final boolean callExceptionhandler) throws T { try { if (this.thread != item.getCallerThread()) { synchronized (this.queueThreadHistory) { this.queueThreadHistory.add(item); } } item.start(this); } catch (final Throwable e) { if (!callExceptionhandler || !item.callExceptionHandler()) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw (T) e; } } } finally { item.setFinished(true); if (this.thread != item.getCallerThread()) { synchronized (this.queueThreadHistory) { if (this.queueThreadHistory.size() != 0) { this.queueThreadHistory.remove(this.queueThreadHistory.size() - 1); } } } synchronized (item) { item.notify(); } } } }