/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2006-2011 The OpenNMS Group, Inc.
* OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc.
*
* OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
*
* OpenNMS(R) is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* OpenNMS(R) 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.
*
* You should have received a copy of the GNU General Public License
* along with OpenNMS(R). If not, see:
* http://www.gnu.org/licenses/
*
* For more information contact:
* OpenNMS(R) Licensing <license@opennms.org>
* http://www.opennms.org/
* http://www.opennms.com/
*******************************************************************************/
package org.opennms.netmgt.scriptd;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.bsf.BSFException;
import org.apache.bsf.BSFManager;
import org.apache.log4j.Category;
import org.opennms.core.fiber.PausableFiber;
import org.opennms.core.queue.FifoQueue;
import org.opennms.core.queue.FifoQueueException;
import org.opennms.core.utils.ThreadCategory;
import org.opennms.netmgt.EventConstants;
import org.opennms.netmgt.config.ScriptdConfigFactory;
import org.opennms.netmgt.config.scriptd.Engine;
import org.opennms.netmgt.config.scriptd.EventScript;
import org.opennms.netmgt.config.scriptd.ReloadScript;
import org.opennms.netmgt.config.scriptd.StartScript;
import org.opennms.netmgt.config.scriptd.StopScript;
import org.opennms.netmgt.config.scriptd.Uei;
import org.opennms.netmgt.dao.NodeDao;
import org.opennms.netmgt.model.OnmsNode;
import org.opennms.netmgt.xml.event.Event;
import org.opennms.netmgt.xml.event.Parm;
import org.opennms.netmgt.xml.event.Script;
/**
* This class is used as a thread for launching scripts to handle received
* events.
*
* @author <a href="mailto:jim.doble@tavve.com">Jim Doble</a>
* @author <a href="http://www.opennms.org"/>OpenNMS</a>
*
*/
final class Executor implements Runnable, PausableFiber {
/**
* The input queue of events.
*/
private final FifoQueue<Event> m_execQ;
/**
* The worker thread that executes the <code>run</code> method.
*/
private Thread m_worker;
/**
* The name of this Fiber
*/
private final String m_name;
/**
* The status of this fiber.
*/
private int m_status;
/**
* The configuration.
*/
private ScriptdConfigFactory m_config;
/**
* The configured scripts (no UEI specified).
*/
private ArrayList<EventScript> m_eventScripts;
/**
* The configured scripts (UEI specified).
*/
private Map<String,List<EventScript>> m_eventScriptMap;
/**
* The BSF manager
*/
private BSFManager m_mgr;
/**
* The DAO object for fetching nodes
*/
private NodeDao m_nodeDao;
/**
* Constructs a new action daemon execution environment. The constructor
* takes three arguments that define the source of commands to be executed and
* the maximum time that a command may run.
*
* @param execQ
* The execution queue
* @param config
* The <em>Scriptd</em> configuration.
* @param nodeDao
* The <em>DAO</em> for fetching node information
*/
Executor(FifoQueue<Event> execQ, ScriptdConfigFactory config, NodeDao nodeDao) {
m_execQ = execQ;
m_config = config;
loadConfig();
m_worker = null;
m_name = "Scriptd-Executor";
m_mgr = null;
m_status = START_PENDING;
m_nodeDao = nodeDao;
}
/**
* Load the m_scripts and m_scriptMap data structures from the
* configuration.
*/
private void loadConfig() {
EventScript[] scripts = m_config.getEventScripts();
m_eventScripts = new ArrayList<EventScript>();
m_eventScriptMap = new ConcurrentHashMap<String,List<EventScript>>();
for (int i = 0; i < scripts.length; i++) {
Uei[] ueis = scripts[i].getUei();
if (ueis.length == 0) {
m_eventScripts.add(scripts[i]);
} else {
for (int j = 0; j < ueis.length; j++) {
String uei = ueis[j].getName();
List<EventScript> list = m_eventScriptMap.get(uei);
if (list == null) {
list = new ArrayList<EventScript>();
list.add(scripts[i]);
m_eventScriptMap.put(uei, list);
} else {
list.add(scripts[i]);
}
}
}
}
}
/**
* The main worker of the fiber. This method is executed by the encapsulated
* thread to read events from the execution queue and to execute any
* configured scripts, allowing these scripts to react to the received
* event. If the thread is interrupted or the status changes to
* <code>STOP_PENDING</code> then the method will return as quickly as
* possible.
*/
public void run() {
ThreadCategory log = ThreadCategory.getInstance(Executor.class);
synchronized (this) {
m_status = RUNNING;
}
for (;;) {
synchronized (this) {
// if stopped or stop pending then break out
if (m_status == STOP_PENDING || m_status == STOPPED) {
break;
}
// if paused or pause pending then block
while (m_status == PAUSE_PENDING || m_status == PAUSED) {
m_status = PAUSED;
try {
wait();
} catch (InterruptedException ex) {
// exit
break;
}
}
// if resume pending then change to running
if (m_status == RESUME_PENDING) {
m_status = RUNNING;
}
}
// Extract the next event
Event event = null;
try {
event = m_execQ.remove(1000);
if (event == null) // status check time
{
continue; // goto top of loop
}
} catch (InterruptedException ex) {
break;
} catch (FifoQueueException ex) {
log.warn("The input event queue has errors, exiting...", ex);
break;
}
// check for reload event
if (isReloadConfigEvent(event)) {
try {
ScriptdConfigFactory.reload();
m_config = ScriptdConfigFactory.getInstance();
loadConfig();
ReloadScript[] reloadScripts = m_config.getReloadScripts();
for (int i = 0; i < reloadScripts.length; i++) {
try {
m_mgr.exec(reloadScripts[i].getLanguage(), "", 0, 0, reloadScripts[i].getContent());
}
catch (BSFException ex) {
log.error("Reload script[" + i + "] failed.", ex);
}
}
log.debug("Script configuration reloaded");
}
catch (Throwable ex) {
log.error("Unable to reload ScriptD configuration: ", ex);
}
}
Script[] attachedScripts = event.getScript();
List<EventScript> mapScripts = null;
try {
mapScripts = m_eventScriptMap.get(event.getUei());
}
catch (Throwable ex) {
}
if (attachedScripts.length > 0 || mapScripts != null || m_eventScripts.size() > 0) {
log.debug("Executing scripts for: " + event.getUei());
m_mgr.registerBean("event", event);
// And the events node
OnmsNode node = null;
if (event.hasNodeid()) {
Long nodeLong = event.getNodeid();
Integer nodeInt = new Integer(nodeLong.intValue());
node = m_nodeDao.get(nodeInt);
m_mgr.registerBean("node", node);
}
// execute the scripts attached to the event
log.debug("Executing attached scripts");
if (attachedScripts.length > 0) {
for (int i = 0; i < attachedScripts.length; i++) {
try {
Script script = attachedScripts[i];
m_mgr.exec(script.getLanguage(), "", 0, 0, script.getContent());
}
catch (BSFException ex) {
log.error("Attached script [" + i + "] execution failed", ex);
}
}
}
// execute the scripts mapped to the UEI
log.debug("Executing mapped scripts");
if (mapScripts != null) {
for (int i = 0; i < mapScripts.size(); i++) {
try {
EventScript script = (EventScript) mapScripts.get(i);
m_mgr.exec(script.getLanguage(), "", 0, 0, script.getContent());
}
catch (BSFException ex) {
log.error("UEI-specific event handler script execution failed: " + event.getUei(), ex);
}
}
}
// execute the scripts that are not mapped to any UEI
log.debug("Executing global scripts");
for (int i = 0; i < m_eventScripts.size(); i++) {
try {
EventScript script = (EventScript) m_eventScripts.get(i);
m_mgr.exec(script.getLanguage(), "", 0, 0, script.getContent());
}
catch (BSFException ex) {
log.error("Non-UEI-specific event handler script [" + i + "] execution failed", ex);
}
}
if (node != null)
m_mgr.unregisterBean("node");
m_mgr.unregisterBean("event");
log.debug("Finished executing scripts for: " + event.getUei());
}
} // end infinite loop
synchronized (this) {
m_status = STOPPED;
}
} // end run
private boolean isReloadConfigEvent(Event event) {
boolean isTarget = false;
if (EventConstants.RELOAD_DAEMON_CONFIG_UEI.equals(event.getUei())) {
List<Parm> parmCollection = event.getParmCollection();
for (Parm parm : parmCollection) {
if (EventConstants.PARM_DAEMON_NAME.equals(parm.getParmName()) && "Scriptd".equalsIgnoreCase(parm.getValue().getContent())) {
isTarget = true;
break;
}
}
//Depreciating this one...
} else if ("uei.opennms.org/internal/reloadScriptConfig".equals(event.getUei())) {
isTarget = true;
}
return isTarget;
}
/**
* Starts the fiber. If the fiber has already been run or is currently
* running then an exception is generated. The status of the fiber is
* updated to <code>STARTING</code> and will transition to <code>
* RUNNING</code>
* when the fiber finishes initializing and begins processing the
* encapsulaed queue.
*
* @throws java.lang.IllegalStateException
* Thrown if the fiber is stopped or has never run.
*/
public synchronized void start() {
ThreadCategory log = ThreadCategory.getInstance(Executor.class);
if (m_worker != null) {
throw new IllegalStateException("The fiber has already been run");
}
m_status = STARTING;
Engine[] engines = m_config.getEngines();
for (int i = 0; i < engines.length; i++) {
Engine engine = engines[i];
log.debug("Registering engine: " + engine.getLanguage());
String[] extensions = null;
String extensionList = engines[i].getExtensions();
if (extensionList != null) {
StringTokenizer st = new StringTokenizer(extensionList);
extensions = new String[st.countTokens()];
int j = 0;
while (st.hasMoreTokens()) {
extensions[j++] = st.nextToken();
}
}
BSFManager.registerScriptingEngine(engines[i].getLanguage(), engines[i].getClassName(), extensions);
}
m_mgr = new BSFManager();
m_mgr.registerBean("log", ThreadCategory.getInstance(Executor.class));
StartScript[] startScripts = m_config.getStartScripts();
for (int i = 0; i < startScripts.length; i++) {
try {
m_mgr.exec(startScripts[i].getLanguage(), "", 0, 0, startScripts[i].getContent());
}
catch (BSFException ex) {
log.error("Start script[" + i + "] failed.", ex);
}
}
m_worker = new Thread(this, getName());
m_worker.start();
}
/**
* Stops a currently running fiber. If the fiber has already been stopped
* then the command is silently ignored. If the fiber was never started then
* an exception is generated.
*
* @throws java.lang.IllegalStateException
* Thrown if the fiber was never started.
*/
public synchronized void stop() {
Category log = (Category) m_mgr.lookupBean("log");
if (m_worker == null) {
throw new IllegalStateException("The fiber has never been run");
}
if (m_status != STOPPED) {
m_status = STOP_PENDING;
}
if (m_worker.isAlive()) {
m_worker.interrupt();
}
StopScript[] stopScripts = m_config.getStopScripts();
notifyAll();
for (int i = 0; i < stopScripts.length; i++) {
try {
m_mgr.exec(stopScripts[i].getLanguage(), "", 0, 0, stopScripts[i].getContent());
}
catch (BSFException ex) {
log.error("Stop script[" + i + "] failed.", ex);
}
}
log.debug("Stopped");
}
/**
* Pauses a currently running fiber. If the fiber was not in a running or
* resuming state then the command is silently discarded. If the fiber is
* not running or has terminated then an exception is generated.
*
* @throws java.lang.IllegalStateException
* Thrown if the fiber is stopped or has never run.
*/
public synchronized void pause() {
if (m_worker == null || !m_worker.isAlive()) {
throw new IllegalStateException("The fiber is not running");
}
if (m_status == RUNNING || m_status == RESUME_PENDING) {
m_status = PAUSE_PENDING;
notifyAll();
}
}
/**
* Resumes the fiber if it is paused. If the fiber was not in a paused or
* pause pending state then the request is discarded. If the fiber has not
* been started or has already stopped then an exception is generated.
*
* @throws java.lang.IllegalStateException
* Thrown if the fiber is stopped or has never run.
*/
public synchronized void resume() {
if (m_worker == null || !m_worker.isAlive()) {
throw new IllegalStateException("The fiber is not running");
}
if (m_status == PAUSED || m_status == PAUSE_PENDING) {
m_status = RESUME_PENDING;
notifyAll();
}
}
/**
* Returns the name of this fiber.
*
* @return The name of the fiber.
*/
public String getName() {
return m_name;
}
/**
* Returns the current status of the pausable fiber.
*
* @return The current status of the fiber.
* @see org.opennms.core.fiber.PausableFiber
* @see org.opennms.core.fiber.Fiber
*/
public synchronized int getStatus() {
if (m_worker != null && !m_worker.isAlive()) {
m_status = STOPPED;
}
return m_status;
}
}