/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.sentry;
import org.helios.apmrouter.jmx.ScheduledThreadPoolFactory;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* <p>Title: Sentry</p>
* <p>Description: Singleton scheduler for managing sentry scheduled task executions</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.sentry.Sentry</code></p>
*/
public class Sentry implements ThreadFactory, RejectedExecutionHandler, Thread.UncaughtExceptionHandler {
/** The singleton instance */
private static volatile Sentry instance = null;
/** The singleton instance ctor lock */
private static final Object lock = new Object();
/** The sentry thread pool's thread group */
private final ThreadGroup threadGroup = new ThreadGroup("SentrySchedulerThreadGroup");
/** A serial number factory for sentry threads */
private final AtomicInteger serial = new AtomicInteger(0);
/** The sentry scheduled thread pool */
private final ScheduledThreadPoolExecutor scheduler = ScheduledThreadPoolFactory.newScheduler("AgentScheduler");
/** A map of sentry triggers keyed by the watched object the trigger was created for */
private final Map<SentryWatched, SentryTrigger> watches = new ConcurrentHashMap<SentryWatched, SentryTrigger>();
/**
* Returns the Sentry instance
* @return the Sentry instance
*/
public static Sentry getInstance() {
if(instance==null) {
synchronized(lock) {
if(instance==null) {
instance = new Sentry();
}
}
}
return instance;
}
private Sentry() {
}
/**
* Registers a new watched object
* @param watched The object to watch
* @return A state control that allows the watched object (or someone else) to change the state of the watched object
* and trigger events in the sentry.
*/
public SentryStateControl register(SentryWatched watched) {
if(watched==null) throw new IllegalArgumentException("The passed watched object was null", new Throwable());
SentryTrigger trigger = watches.get(watched);
if(trigger==null) {
synchronized(watches) {
trigger = watches.get(watched);
if(trigger==null) {
trigger = new SentryTrigger(this, new SentryTask(watched), SentryState.PENDING);
}
}
}
return trigger;
}
/**
* Processes state changes based on sets in the registered {@link SentryStateControl}s
* @param oldState The prior sentry state
* @param newState The new sentry state
* @param task The task reference for the state changed watched object
*/
void onStateChange(SentryState oldState, SentryState newState, SentryTask task) {
log("Task [" + task.getWatched().getName() + "] switched from " + oldState + "-->" + newState);
if(newState==SentryState.CANCELLED) {
if(task.getScheduleHandle()!=null) {
task.getScheduleHandle().cancel(true);
task.setScheduleHandle(null);
}
watches.remove(task.getWatched());
}
if(task.isPolled()) {
if(newState==SentryState.POLLING) {
ScheduledFuture<?> sf = scheduler.scheduleAtFixedRate(task, task.getPeriod(), task.getPeriod(), TimeUnit.MILLISECONDS);
task.setScheduleHandle(sf);
log("Scheduled Poller for [" + task.getWatched().getName() + "]");
}
} else {
if(newState==SentryState.CALLBACK) {
if(task.getScheduleHandle()!=null) {
task.getScheduleHandle().cancel(true);
task.setScheduleHandle(null);
}
} else if(newState==SentryState.DISCONNECTED) {
log("Task [" + task.getWatched().getName() + "] disconnected. Attempting immediate reconnect");
if(!((CallbackSentryWatched)task.getWatched()).connect(-1)) {
ScheduledFuture<?> sf = scheduler.scheduleAtFixedRate(task, task.getPeriod(), task.getPeriod(), TimeUnit.MILLISECONDS);
task.setScheduleHandle(sf);
log("Task [" + task.getWatched().getName() + "] immediate reconnect failed. Scheduled reconnected loop");
}
}
}
}
public static void log(Object msg) {
System.out.println("[Sentry]" + msg);
}
/**
* {@inheritDoc}
* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable)
*/
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("Sentry Uncaught exception on thread [" + t + "]. Stack trace follows:");
e.printStackTrace(System.err);
}
/**
* {@inheritDoc}
* @see java.util.concurrent.RejectedExecutionHandler#rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor)
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.err.println("Sentry Rejected Execution of task [" + r + "]");
}
/**
* {@inheritDoc}
* @see java.util.concurrent.ThreadFactory#newThread(java.lang.Runnable)
*/
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(threadGroup, r, "SentrySchedulerThread#" + serial.incrementAndGet());
t.setDaemon(true);
t.setUncaughtExceptionHandler(this);
return t;
}
}