/*********************************************************************************** * * Copyright (c) 2014 Kamil Baczkowicz * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * * Kamil Baczkowicz - initial API and implementation and/or initial documentation * */ package pl.baczkowicz.spy.scripts; import java.io.IOException; import java.io.InputStreamReader; import java.util.concurrent.Executor; import javax.script.ScriptException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pl.baczkowicz.spy.eventbus.IKBus; import pl.baczkowicz.spy.files.FileUtils; import pl.baczkowicz.spy.scripts.events.ScriptStateChangeEvent; import pl.baczkowicz.spy.utils.ThreadingUtils; /** * This runnable implementation is responsible for running a script in its own thread. */ public class ScriptRunner implements Runnable { /** Diagnostic logger. */ private final static Logger logger = LoggerFactory.getLogger(ScriptRunner.class); /** The associated script. */ private final Script script; /** The Event Bus. */ private IKBus eventBus; /** The executor. */ private Executor executor; /** The thread running the script. */ private Thread runningThread; private Object lastReturnValue; private Exception lastThrownException; /** * Creates a ScriptRunner. * * @param eventBus The event bus to be used * @param script The associated script * @param executor The executor to be used */ public ScriptRunner(final IKBus eventBus, final Script script, final Executor executor) { this.script = script; this.eventBus = eventBus; this.executor = executor; } /** * Runs once or in a loop if repeat flag is set. */ public void run() { if (script.isAsynchronous()) { ThreadingUtils.logThreadStarting("Script " + script.getName()); } script.touch(); runningThread = Thread.currentThread(); boolean firstRun = true; // Either a first run or repeat is on but not stopped by user while (firstRun || (script.isRepeat() && !ScriptRunningState.STOPPED.equals(script.getStatus()))) { logger.debug("Running [{}] script: first run = {}, repeat = {}, state = {}", script.getName(), firstRun, script.isRepeat(), script.getStatus()); firstRun = false; changeState(ScriptRunningState.RUNNING); if (script.isAsynchronous()) { new Thread(new ScriptHealthDetector(eventBus, script, executor)).start(); } try { runScript(); } catch (Exception e) { changeState(ScriptRunningState.FAILED); setLastThrownException(e); logger.error("Script execution exception", e); break; } if (script.isRepeat()) { logger.debug("Re-running script {}", script.getName()); } } script.stop(); if (script.isAsynchronous()) { ThreadingUtils.logThreadEnding(); } } /** * Runs the script and checks the returned value. * * @throws ScriptException Thrown when a script executor error occurs * @throws IOException Thrown when cannot load the script */ private void runScript() throws ScriptException, IOException { // Clear the last returned value lastReturnValue = null; setLastThrownException(null); // Script in a file if (script.getScriptFile() != null) { lastReturnValue = script.getScriptEngine().eval(new InputStreamReader(FileUtils.loadFileByName(script.getScriptFile().getAbsolutePath()))); // lastReturnValue = script.getScriptEngine().eval(new FileReader(script.getScriptFile())); logger.debug("Script {} returned with value {}", script.getName(), lastReturnValue); } // In-line script else if (script.getScriptContent() != null) { lastReturnValue = script.getScriptEngine().eval(script.getScriptContent()); logger.debug("Inline script {} returned with value {}", script.getName(), lastReturnValue); } else { logger.warn("No script content defined for script {}", script.getName()); } // If nothing returned, assume all good if (lastReturnValue == null) { changeState(ScriptRunningState.FINISHED); } // If boolean returned, check if OK else if (lastReturnValue instanceof Boolean) { if ((Boolean) lastReturnValue) { changeState(ScriptRunningState.FINISHED); } else { changeState(ScriptRunningState.STOPPED); } } // Anything else, assume all good else { changeState(ScriptRunningState.FINISHED); } } /** * Gets the last returned value for the script. * * @return the lastReturnValue */ public Object getLastReturnValue() { return lastReturnValue; } /** * Changes the state of the script. * * @param newState New state */ private void changeState(final ScriptRunningState newState) { changeState(eventBus, script.getName(), newState, script, executor); } /** * Changes the state of the script. * * @param eventBus The event bus to be used * @param scriptName The script name * @param newState The new state requested * @param script The script itself * @param executor The executor to be used */ public static void changeState(final IKBus eventBus, final String scriptName, final ScriptRunningState newState, final Script script, final Executor executor) { logger.trace("Changing [{}] script's state to [{}]", scriptName, newState); script.setStatus(newState); if (eventBus != null && executor != null) { executor.execute(new Runnable() { public void run() { script.nofityChange(); eventBus.publish(new ScriptStateChangeEvent(scriptName, newState)); logger.trace("Notified [{}] script's state to [{}]", scriptName, newState); } }); } else { script.nofityChange(); logger.trace("Notified [{}] script's state to [{}]", scriptName, newState); } } /** * Gets the runner's thread. * * @return The runner's Thread object */ public Thread getThread() { return this.runningThread; } /** * Gets last thrown exception or null if none. * * @return the lastThrownException */ public Exception getLastThrownException() { return lastThrownException; } /** * Sets last thrown exception. * * @param lastThrownException the lastThrownException to set */ public void setLastThrownException(Exception lastThrownException) { this.lastThrownException = lastThrownException; } }