/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, 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.jboss.monitor.services;
import java.util.LinkedList;
import java.util.List;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.ObjectName;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.jboss.logging.Logger;
import org.jboss.monitor.alarm.AlarmManager;
import org.jboss.system.ListenerServiceMBeanSupport;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong;
/**
* A simple listener that can subscribe for any combination
* of notifications, and asynchronously process them using
* a script written using any of the languages supported by
* the apache Bean Scripting Framework (BSF).
*
* The following variables are setup for the script to use:
*
* "log" - service Logger
* "server" - the MBeanServer
* "manager" - alarm manager helper
*
* "notification" - the Notification to be processed
* "handback" - the Object sent with the notification
*
* By setting up a Timer using the TimerService to periodicaly
* emit notifications, we can use those notifications as triggers
* for performing any sort of polling operation.
*
* One of the intented uses of this service is to use the "manager"
* (see org.jboss.monitor.alarm.AlarmManager) in the script,
* help maintain a list of active system alarms in the
* ActiveAlarmTable service.
*
* @author <a href="mailto:dimitris@jboss.org">Dimitris Andreadis</a>
* @version $Revision: 81038 $
*/
public class ScriptingListener extends ListenerServiceMBeanSupport
implements ScriptingListenerMBean
{
// Private Data --------------------------------------------------
/** The Script */
private String script;
/** The language the script is written into */
private String language;
/** Dynamic subscriptions flag */
private boolean dynamicSubscriptions;
/** Set to deliver notification directly */
private ObjectName targetListener;
/** The number of notifications received/enqueued */
private SynchronizedLong notificationsReceived;
/** The number of notifications processed/dequeued by the script */
private SynchronizedLong notificationsProcessed;
/** The total time (msecs) spent executing the script */
private SynchronizedLong totalProcessingTime;
/** Bean Scripting Framework entry point */
private BSFManager manager;
/** Enqueued notifications */
private List queue;
/** Signals stop processing */
private boolean stopRequested;
/** The thread running the script */
private Thread processorThread;
/** The alarm manager helper */
private AlarmManager alm = new AlarmManager(this);
// Constructors --------------------------------------------------
/**
* CTOR
*/
public ScriptingListener()
{
queue = new LinkedList();
notificationsReceived = new SynchronizedLong(0);
notificationsProcessed = new SynchronizedLong(0);
totalProcessingTime = new SynchronizedLong(0);
}
// ScriptNotificationListenerMBean Implementation ----------------
/**
* @jmx:managed-attribute
*/
public void setScript(String script)
{
this.script = script;
}
/**
* @jmx:managed-attribute
*/
public String getScript()
{
return script;
}
/**
* @jmx:managed-attribute
*/
public void setScriptLanguage(String language)
{
this.language = language;
}
/**
* @jmx:managed-attribute
*/
public String getScriptLanguage()
{
return language;
}
/**
* @jmx:managed-attribute
*/
public void setDynamicSubscriptions(boolean dynamicSubscriptions)
{
this.dynamicSubscriptions = dynamicSubscriptions;
}
/**
* @jmx:managed-attribute
*/
public boolean getDynamicSubscriptions()
{
return this.dynamicSubscriptions;
}
/**
* @jmx:managed-attribute
*/
public long getNotificationsReceived()
{
return notificationsReceived.get();
}
/**
* @jmx:managed-attribute
*/
public long getNotificationsProcessed()
{
return notificationsProcessed.get();
}
/**
* @jmx:managed-attribute
*/
public long getTotalProcessingTime()
{
return totalProcessingTime.get();
}
/**
* @jmx:managed-attribute
*/
public long getAverageProcessingTime()
{
long processed = notificationsProcessed.get();
return (processed == 0) ? 0 : totalProcessingTime.get() / processed;
}
// Lifecycle control (ServiceMBeanSupport) -----------------------
/**
* Start
*/
public void startService() throws Exception
{
log.debug("Initializing BSFManager for language '" + language + "'");
// This is needed until BSF adds it
BSFManager.registerScriptingEngine(
"groovy",
"org.codehaus.groovy.bsf.GroovyEngine",
new String[] { "groovy", "gy" }
);
// I suppose we need one BSFManager per processing thread
manager = new BSFManager();
manager.setClassLoader(Thread.currentThread().getContextClassLoader());
manager.loadScriptingEngine(language);
manager.declareBean("log", log, Logger.class);
manager.declareBean("server", server, MBeanServer.class);
manager.declareBean("manager", alm, AlarmManager.class);
// test with a dummy notification first, to see if the script is valid
Notification testNotification = new Notification("jboss.script.test", serviceName, 0);
manager.declareBean("notification", testNotification, Notification.class);
manager.declareBean("handback", "", Object.class);
manager.exec(language, "in-memory-script", 0, 0, script);
// Start the ScriptProcessor in its own thread
processorThread = new Thread(new ScriptProcessor(), "ScriptProcessor[" + serviceName + "]");
processorThread.start();
// subscribe for notifications
super.subscribe(dynamicSubscriptions);
}
/**
* Stop
*/
public void stopService() throws Exception
{
// unsubscribe for notifications
super.unsubscribe();
log.debug("Stopping " + processorThread.getName());
// tell the ScriptProcessor to stop
stopRequested = true;
// notify the processing thread in case it is waiting on the queue
synchronized (queue)
{
queue.notify();
}
try
{
// wait for the processor to finish, but not for too long
processorThread.join(5000);
}
catch (InterruptedException e)
{
// set interrupted status
Thread.currentThread().interrupt();
}
// cleanup
queue.clear();
manager.terminate();
}
// ListenerServiceMBeanSupport overrides -------------------------
/**
* Overriden to add handling!
*/
public void handleNotification2(Notification notification, Object handback)
{
// count the received notifications
notificationsReceived.increment();
// append the received notification to the end of the list,
// for processing from a different thread
synchronized (queue)
{
queue.add(new QueueEntry(notification, handback));
// hint to the processing thread to kick-in
queue.notify();
}
}
// Inner ---------------------------------------------------------
/**
* Simple data holder
*/
private static class QueueEntry
{
public Notification notification;
public Object handback;
public QueueEntry(Notification notification, Object handback)
{
this.notification = notification;
this.handback = handback;
}
}
/**
* Inner class to encapsulate script execution logic
*/
private class ScriptProcessor implements Runnable
{
public void run()
{
String name = Thread.currentThread().getName();
log.debug("Started thread: " + name);
while (!stopRequested)
{
QueueEntry entry;
synchronized (queue)
{
while (queue.isEmpty() && !stopRequested)
{
try
{
queue.wait();
}
catch (InterruptedException e)
{
// ignore
}
}
if (stopRequested)
{
// done
break;
}
else
{
// extract the first entry for processing
entry = (QueueEntry)queue.remove(0);
}
}
// use this for measurement
long start = System.currentTimeMillis();
// we have a notification to process
try
{
manager.declareBean("notification", entry.notification, Notification.class);
manager.declareBean("handback", entry.handback == null ? "" : entry.handback, Object.class);
manager.exec(language, "in-memory-script", 0, 0, script);
}
catch (BSFException e)
{
log.warn("Caught exception", e);
}
// measure time spent processing the script
long stop = System.currentTimeMillis();
totalProcessingTime.add(stop - start);
notificationsProcessed.increment();
}
log.debug("Stopped thread: " + name);
}
}
}