/*
* (C) Copyright 2012-2013 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.work;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.work.api.Work;
import org.nuxeo.ecm.core.work.api.Work.State;
import org.nuxeo.ecm.core.work.api.WorkQueueDescriptor;
/**
* Implementation of a {@link WorkQueuing} using in-memory queuing.
*
* @since 5.8
*/
public class MemoryWorkQueuing implements WorkQueuing {
private static final Log log = LogFactory.getLog(MemoryWorkQueuing.class);
protected final WorkManagerImpl mgr;
// @GuardedBy("this")
protected final WorkQueueDescriptorRegistry workQueueDescriptors;
// @GuardedBy("this")
protected final Map<String, BlockingQueue<Runnable>> allScheduled = new HashMap<String, BlockingQueue<Runnable>>();
// @GuardedBy("this")
// queueId -> workId -> work
protected final Map<String, Map<String, Work>> allRunning = new HashMap<String, Map<String, Work>>();
// @GuardedBy("this")
// queueId -> workId -> work
protected final Map<String, Map<String, Work>> allCompleted = new HashMap<String, Map<String, Work>>();
public MemoryWorkQueuing(WorkManagerImpl mgr, WorkQueueDescriptorRegistry workQueueDescriptors) {
this.mgr = mgr;
this.workQueueDescriptors = workQueueDescriptors;
}
@Override
public synchronized void init() {
allScheduled.clear();
allRunning.clear();
allCompleted.clear();
}
// called synchronized
protected WorkQueueDescriptor getDescriptor(String queueId) {
WorkQueueDescriptor descriptor = workQueueDescriptors.get(queueId);
if (descriptor == null) {
throw new IllegalArgumentException("No such work queue: " + queueId);
}
return descriptor;
}
@Override
public BlockingQueue<Runnable> initScheduleQueue(String queueId) {
if (allScheduled.containsKey(queueId)) {
throw new IllegalStateException(queueId + " was already configured");
}
final BlockingQueue<Runnable> queue = newBlockingQueue(getDescriptor(queueId));
allScheduled.put(queueId, queue);
return queue;
}
@Override
public BlockingQueue<Runnable> getScheduledQueue(String queueId) {
if (!allScheduled.containsKey(queueId)) {
throw new IllegalStateException(queueId + " was not configured yet");
}
return allScheduled.get(queueId);
}
// called synchronized
protected Map<String, Work> getRunning(String queueId) {
Map<String, Work> running = allRunning.get(queueId);
if (running == null) {
allRunning.put(queueId, running = newRunningMap());
}
return running;
}
// called synchronized
protected Map<String, Work> getCompleted(String queueId) {
Map<String, Work> completed = allCompleted.get(queueId);
if (completed == null) {
allCompleted.put(queueId, completed = newCompletedMap());
}
return completed;
}
protected BlockingQueue<Runnable> newBlockingQueue(
WorkQueueDescriptor workQueueDescriptor) {
if (workQueueDescriptor.usePriority) {
log.warn("Priority queues are now deprecated and function as regular queues");
}
int capacity = workQueueDescriptor.capacity;
if (capacity <= 0) {
capacity = -1; // unbounded
}
return new MemoryBlockingQueue(this, capacity);
}
protected Map<String, Work> newRunningMap() {
return new HashMap<String, Work>();
}
protected Map<String, Work> newCompletedMap() {
return new LinkedHashMap<String, Work>();
}
@Override
public synchronized void workRunning(String queueId, Work work) {
// work is already taken from the scheduled queue
// by the thread pool executor
getRunning(queueId).put(work.getId(), work);
}
@Override
public synchronized void workCompleted(String queueId, Work work) {
getRunning(queueId).remove(work.getId());
getCompleted(queueId).put(work.getId(), work);
}
@Override
public Work find(String workId, State state) {
if (state == null) {
Work w = findScheduled(workId);
if (w == null) {
w = findRunning(workId);
}
return w;
}
switch (state) {
case SCHEDULED:
return findScheduled(workId);
case RUNNING:
return findRunning(workId);
case COMPLETED:
return findCompleted(workId);
default:
return null;
}
}
@Override
public boolean isWorkInState(String workId, State state) {
if (state == null) {
return isScheduled(workId) || isRunning(workId);
}
switch (state) {
case SCHEDULED:
return isScheduled(workId);
case RUNNING:
return isRunning(workId);
case COMPLETED:
return isCompleted(workId);
default:
return false;
}
}
@Override
public State getWorkState(String workId) {
// TODO this is linear, but isScheduled is buggy
if (findScheduled(workId) != null) {
return State.SCHEDULED;
}
if (isRunning(workId)) {
return State.RUNNING;
}
if (isCompleted(workId)) {
return State.COMPLETED;
}
return null;
}
@Override
public synchronized List<Work> listWork(String queueId, State state) {
switch (state) {
case SCHEDULED:
return listScheduled(queueId);
case RUNNING:
return listRunning(queueId);
case COMPLETED:
return listCompleted(queueId);
default:
throw new IllegalArgumentException(String.valueOf(state));
}
}
@Override
public synchronized List<String> listWorkIds(String queueId, State state) {
if (state == null) {
return listNonCompletedIds(queueId);
}
switch (state) {
case SCHEDULED:
return listScheduledIds(queueId);
case RUNNING:
return listRunningIds(queueId);
case COMPLETED:
return listCompletedIds(queueId);
default:
throw new IllegalArgumentException(String.valueOf(state));
}
}
@Override
public int getQueueSize(String queueId, State state) {
switch (state) {
case SCHEDULED:
return getScheduledSize(queueId);
case RUNNING:
return getRunningSize(queueId);
case COMPLETED:
return getCompletedSize(queueId);
default:
throw new IllegalArgumentException(String.valueOf(state));
}
}
protected synchronized int getScheduledSize(String queueId) {
BlockingQueue<Runnable> scheduled = allScheduled.get(queueId);
return scheduled == null ? 0 : scheduled.size();
}
protected synchronized int getRunningSize(String queueId) {
Map<String, Work> running = allRunning.get(queueId);
return running == null ? 0 : running.size();
}
protected synchronized int getCompletedSize(String queueId) {
Map<String, Work> completed = allCompleted.get(queueId);
return completed == null ? 0 : completed.size();
}
protected synchronized boolean isScheduled(String workId) {
for (BlockingQueue<Runnable> scheduled : allScheduled.values()) {
MemoryBlockingQueue q = (MemoryBlockingQueue) scheduled;
if (q.containsWorkId(workId)) {
return true;
}
}
return false;
}
protected synchronized boolean isRunning(String workId) {
for (Map<String, Work> running : allRunning.values()) {
if (running.containsKey(workId)) {
return true;
}
}
return false;
}
protected synchronized boolean isCompleted(String workId) {
for (Map<String, Work> completed : allCompleted.values()) {
if (completed.containsKey(workId)) {
return true;
}
}
return false;
}
protected synchronized Work findScheduled(String workId) {
for (BlockingQueue<Runnable> scheduled : allScheduled.values()) {
for (Runnable r : scheduled) {
Work w = WorkHolder.getWork(r);
if (w.getId().equals(workId)) {
return w;
}
}
}
return null;
}
protected synchronized Work findRunning(String workId) {
for (Map<String, Work> running : allRunning.values()) {
Work w = running.get(workId);
if (w != null) {
return w;
}
}
return null;
}
protected synchronized Work findCompleted(String workId) {
for (Map<String, Work> completed : allCompleted.values()) {
Work w = completed.get(workId);
if (w != null) {
return w;
}
}
return null;
}
// no synchronized as scheduled queue is thread-safe
protected List<Work> listScheduled(String queueId) {
BlockingQueue<Runnable> scheduled = getScheduledQueue(queueId);
List<Work> list = new ArrayList<Work>(scheduled.size());
for (Runnable r : scheduled) {
Work w = WorkHolder.getWork(r);
list.add(w);
}
return list;
}
// called synchronized
protected List<Work> listRunning(String queueId) {
return new ArrayList<Work>(getRunning(queueId).values());
}
// called synchronized
protected List<Work> listCompleted(String queueId) {
return new ArrayList<Work>(getCompleted(queueId).values());
}
// no synchronized as scheduled queue is thread-safe
protected List<String> listScheduledIds(String queueId) {
BlockingQueue<Runnable> scheduled = getScheduledQueue(queueId);
List<String> list = new ArrayList<String>(scheduled.size());
for (Runnable r : scheduled) {
Work w = WorkHolder.getWork(r);
list.add(w.getId());
}
return list;
}
// called synchronized
protected List<String> listRunningIds(String queueId) {
return new ArrayList<String>(getRunning(queueId).keySet());
}
// called synchronized
protected List<String> listNonCompletedIds(String queueId) {
List<String> list = listScheduledIds(queueId);
list.addAll(listRunningIds(queueId));
return list;
}
// called synchronized
protected List<String> listCompletedIds(String queueId) {
return new ArrayList<String>(getCompleted(queueId).keySet());
}
@Override
public Work removeScheduled(String queueId, String workId) {
for (Iterator<Runnable> it = getScheduledQueue(queueId).iterator(); it.hasNext();) {
Runnable r = it.next();
Work w = WorkHolder.getWork(r);
if (w.getId().equals(workId)) {
it.remove();
return w;
}
}
return null;
}
@Override
public int setSuspending(String queueId) {
// for in-memory queuing, there's no suspend
// drain scheduled queue and mark work canceled
List<Runnable> scheduled = new ArrayList<Runnable>();
getScheduledQueue(queueId).drainTo(scheduled);
for (Runnable r : scheduled) {
Work work = WorkHolder.getWork(r);
work.setWorkInstanceState(State.CANCELED);
}
return scheduled.size();
}
@Override
public Set<String> getCompletedQueueIds() {
return new HashSet<String>(allCompleted.keySet());
}
@Override
public synchronized void clearCompletedWork(String queueId,
long completionTime) {
Map<String, Work> completed = getCompleted(queueId);
if (completionTime <= 0) {
completed.clear();
} else {
for (Iterator<Work> it = completed.values().iterator(); it.hasNext();) {
Work w = it.next();
if (w.getCompletionTime() < completionTime) {
it.remove();
}
}
}
}
}