/*
* The MIT License (MIT)
*
* Copyright (c) 2013 Brien L. Wheeler (brienwheeler@yahoo.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.brienwheeler.svc.monitor.telemetry.impl;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.brienwheeler.lib.concurrent.StoppableThread;
import com.brienwheeler.lib.monitor.telemetry.ITelemetryInfoProcessor;
import com.brienwheeler.lib.monitor.telemetry.TelemetryInfo;
import com.brienwheeler.lib.svc.GracefulShutdown;
import com.brienwheeler.lib.svc.ServiceState;
import com.brienwheeler.lib.util.ValidationUtils;
public class AsynchronousTelemetryInfoProcessor extends TelemetryServiceBase
implements ITelemetryInfoProcessor
{
public static enum QueueFullPolicy {
DISCARD_OLDEST,
DISCARD_OFFERED,
}
public static enum ShutdownBehavior {
DISCARD,
PROCESS,
}
protected final Log log = LogFactory.getLog(getClass());
private final AtomicReference<StoppableThread> backgroundThread =
new AtomicReference<StoppableThread>();
private final AtomicReference<BlockingQueue<TelemetryInfo>> queue =
new AtomicReference<BlockingQueue<TelemetryInfo>>();
private final AtomicReference<QueueFullPolicy> queueFullPolicy =
new AtomicReference<QueueFullPolicy>(QueueFullPolicy.DISCARD_OLDEST);
private final AtomicReference<ShutdownBehavior> shutdownBehavior =
new AtomicReference<ShutdownBehavior>(ShutdownBehavior.PROCESS);
private int maxCapacity = Integer.MAX_VALUE;
public void setQueueFullPolicy(QueueFullPolicy queueFullPolicy)
{
ValidationUtils.assertNotNull(queueFullPolicy, "queueFullPolicy cannot be null");
ensureState(ServiceState.STOPPED, "can't change QueueFullPolicy when not STOPPED");
this.queueFullPolicy.set(queueFullPolicy);
}
public void setShutdownBehavior(ShutdownBehavior shutdownBehavior)
{
ValidationUtils.assertNotNull(shutdownBehavior, "shutdownBehavior cannot be null");
ensureState(ServiceState.STOPPED, "can't change ShutdownBehavior when not STOPPED");
this.shutdownBehavior.set(shutdownBehavior);
}
public void setMaxCapacity(int maxCapacity)
{
ValidationUtils.assertTrue(maxCapacity > 0, "maxCapacity must be greater than zero");
ensureState(ServiceState.STOPPED, "can't change MaxCapacity when not STOPPED");
this.maxCapacity = maxCapacity;
}
@Override
protected void onStart() throws InterruptedException
{
super.onStart();
LinkedBlockingQueue<TelemetryInfo> queue = new LinkedBlockingQueue<TelemetryInfo>(maxCapacity);
this.queue.set(queue);
TelemetryInfoProcessThread backgroundThread = new TelemetryInfoProcessThread(queue);
this.backgroundThread.set(backgroundThread);
backgroundThread.start();
}
@Override
protected void onStop() throws InterruptedException
{
backgroundThread.get().shutdown();
super.onStop();
}
@Override
@GracefulShutdown
public void process(TelemetryInfo telemetryInfo)
{
telemetryInfo.checkPublished();
BlockingQueue<TelemetryInfo> queue = this.queue.get();
switch (queueFullPolicy.get())
{
case DISCARD_OFFERED :
queue.offer(telemetryInfo);
// return regardless of success
return;
case DISCARD_OLDEST :
while (!queue.offer(telemetryInfo))
{
// if offer failed, remove and discard one element from queue and try
// again
queue.poll();
}
return;
}
}
// testability
protected void beforeTake()
{
}
// testability
protected void afterProcess()
{
}
class TelemetryInfoProcessThread extends StoppableThread
{
private final BlockingQueue<TelemetryInfo> queue;
TelemetryInfoProcessThread(BlockingQueue<TelemetryInfo> queue)
{
super(AsynchronousTelemetryInfoProcessor.class.getSimpleName(), log);
this.queue = queue;
}
@Override
public void onRun()
{
while (!isShutdown())
{
try {
beforeTake();
TelemetryInfo telemetryInfo = queue.take();
callProcessors(telemetryInfo);
afterProcess();
}
catch (InterruptedException e) {
// probably isShutdown(), continue and check
Thread.currentThread().interrupt();
}
}
switch (shutdownBehavior.get())
{
case DISCARD :
log.info("discarding " + queue.size() + " queued TelemetryInfo at shutdown");
break;
case PROCESS :
log.info("processing " + queue.size() + " queued TelemetryInfo at shutdown");
for (TelemetryInfo telemetryInfo : queue)
callProcessors(telemetryInfo);
break;
}
queue.clear();
return;
}
}
}