/**
* Copyright (C) 2008 - 2014 52°North Initiative for Geospatial Open Source
* Software GmbH
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* If the program is linked with libraries which are licensed under one of
* the following licenses, the combination of the program with the linked
* library is not considered a "derivative work" of the program:
*
* - Apache License, version 2.0
* - Apache Software License, version 1.0
* - GNU Lesser General Public License, version 3
* - Mozilla Public License, versions 1.0, 1.1 and 2.0
* - Common Development and Distribution License (CDDL), version 1.0
*
* Therefore the distribution of the program linked with libraries licensed
* under the aforementioned licenses, is permitted by the copyright holders
* if the distribution is compliant with both the GNU General Public
* icense version 2 and the aforementioned licenses.
*
* This program 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 General
* Public License for more details.
*/
package org.n52.ses.util.concurrent;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.n52.ses.api.event.MapEvent;
import org.n52.ses.util.common.ConfigurationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is the default concurrent implementation of the SES.
* It internally stores received {@link QueuedMapEventCollection}s
* in a {@link ConcurrentLinkedQueue}.
*
* @author matthes rieke <m.rieke@52north.org>
*
*/
public class FIFOWorker implements IConcurrentNotificationHandler {
private static final Logger logger = LoggerFactory
.getLogger(FIFOWorker.class);
private boolean running = true;
private ConcurrentLinkedQueue<QueuedMapEventCollection> queue;
private IPollListener listener;
private Object queueWaiter = new Object();
private long timeout = 5000;
private int notProcessedFailureCount;
private Thread thread;
private boolean useIntelligentTimeout;
private ITimeoutEstimation estimation;
protected boolean stopped;
public FIFOWorker() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
this.queue = new ConcurrentLinkedQueue<QueuedMapEventCollection>();
Class<?> clazz = Class.forName(ConfigurationRegistry.getInstance().getPropertyForKey(
ConfigurationRegistry.TIMEOUT_ESTIMATION));
this.estimation = (ITimeoutEstimation) clazz.newInstance();
this.estimation.setMinimumTimeout(Integer.parseInt(ConfigurationRegistry.getInstance().getPropertyForKey(
ConfigurationRegistry.CONCURRENT_MINIMUM_TIMEOUT)));
this.estimation.setMaximumTimeout(Integer.parseInt(ConfigurationRegistry.getInstance().getPropertyForKey(
ConfigurationRegistry.CONCURRENT_MAXIMUM_TIMEOUT)));
}
/**
* @param listener the callback which process the message
* @param timeout the timeout the worker should wait until discarding a non-processed event
* @throws IllegalAccessException due to reflections
* @throws InstantiationException due to reflections
* @throws ClassNotFoundException due to reflections
*/
public FIFOWorker(IPollListener listener, int timeout) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
this();
this.listener = listener;
setTimeout(timeout);
}
private void callPollListeners(MapEvent alert) {
if (this.listener != null) this.listener.onElementPolled(alert);
}
@Override
public QueuedMapEventCollection insertPendingEventCollection(
QueuedMapEventCollection result) {
synchronized (this.queueWaiter) {
this.queue.offer(result);
this.queueWaiter.notifyAll();
}
return result;
}
@Override
public void notifyOnDataAvailability(QueuedMapEventCollection event) {
}
@Override
public void startWorking() {
this.running = true;
this.thread = new Thread(new Runnable() {
@Override
public void run() {
while (FIFOWorker.this.running) {
QueuedMapEventCollection alerts;
synchronized (FIFOWorker.this.queueWaiter) {
while (FIFOWorker.this.queue.isEmpty()) {
/*
* is this a system shutdown?
*/
if (!FIFOWorker.this.running) return;
try {
FIFOWorker.this.queueWaiter.wait();
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
}
while (!FIFOWorker.this.queue.isEmpty()) {
if (FIFOWorker.this.stopped) return;
/*
* get message from queue
*/
alerts = queue.poll();
if (logger.isDebugEnabled())
logger.debug("####### current queue size: "+FIFOWorker.this.queue.size());
/*
* queue was empty
*/
if (alerts == null) continue;
/*
* check if we got the next valid element,
* otherwise wait.
*/
if (!alerts.getFuture().isDone()) {
try {
/*
* wait the timeout time and check again one time,
* else discard
*/
long t = FIFOWorker.this.getCurrenTimeout();
if (logger.isDebugEnabled()) {
logger.debug("current estimated timeout: "+t);
}
try {
if (alerts.getFuture().get(t, TimeUnit.MILLISECONDS) != null) {
/*
* we have waited the timeout period,
* processing did not take place.
* discard this one and go for the next.
*/
alerts.getFuture().cancel(true);
FIFOWorker.this.notProcessedFailureCount++;
continue;
}
} catch (ExecutionException e) {
logger.warn(e.getMessage());
FIFOWorker.this.notProcessedFailureCount++;
continue;
} catch (TimeoutException e) {
logger.warn(e.getMessage());
FIFOWorker.this.notProcessedFailureCount++;
continue;
}
/*
* update the timeout
*/
if (FIFOWorker.this.useIntelligentTimeout) {
alerts.setElapsedTime();
estimation.updateTimeout(alerts.getElapsedTime(), true);
}
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
if (!alerts.isFilled()) {
/*
* it was cancelled, ignore it
*/
FIFOWorker.this.notProcessedFailureCount++;
continue;
}
/*
* update the timeout
*/
if (FIFOWorker.this.useIntelligentTimeout) {
estimation.updateTimeout(alerts.getElapsedTime());
}
/*
* we polled a processed element, yay!
* forward it to our listener
*/
if (alerts.getCollection() != null) {
for (MapEvent alert : alerts.getCollection()) {
if (logger.isInfoEnabled()) {
logger.info(alert.toString());
}
callPollListeners(alert);
}
}
}
}
}
});
this.thread.start();
}
protected long getCurrenTimeout() {
if (this.useIntelligentTimeout) {
return this.estimation.getCurrenTimeout();
}
return this.timeout;
}
@Override
public void stopWorking() {
this.stopped = true;
this.running = false;
synchronized (this.queueWaiter) {
this.queueWaiter.notifyAll();
}
}
@Override
public int getNotProcessedFailureCount() {
return this.notProcessedFailureCount;
}
@Override
public void resetFailures() {
this.notProcessedFailureCount = 0;
}
@Override
public void joinUntilEmpty() {
this.running = false;
synchronized (this.queueWaiter) {
this.queueWaiter.notifyAll();
}
try {
this.thread.join();
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
startWorking();
}
@Override
public void setTimeout(long l) {
this.timeout = l;
if (this.estimation != null) {
this.estimation.setMaximumTimeout((int) timeout);
}
}
@Override
public void setPollListener(IPollListener pl) {
this.listener = pl;
}
@Override
public void setUseIntelligentTimeout(boolean b) {
this.useIntelligentTimeout = b;
}
}