All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.bidib.wizard.common.script.engine.ScriptEngine Maven / Gradle / Ivy
package org.bidib.wizard.common.script.engine;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import org.apache.commons.collections4.CollectionUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.wizard.api.context.ApplicationContext;
import org.bidib.wizard.api.script.ScriptCommand;
import org.bidib.wizard.api.script.ScriptEngineListener;
import org.bidib.wizard.api.script.ScriptOptionAwareCommand;
import org.bidib.wizard.api.script.ScriptStatus;
import org.bidib.wizard.api.script.Scripting;
import org.bidib.wizard.common.script.ScriptExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ScriptEngine {
private static final Logger LOGGER = LoggerFactory.getLogger(ScriptEngine.class);
private final Object scriptWorkerLock = new Object();
// private Thread scriptWorker;
private Future> scriptWorker;
private AtomicBoolean scriptRunning = new AtomicBoolean(false);
private AtomicBoolean scriptRepeating = new AtomicBoolean(false);
private List> scriptCommands;
private T scripting;
private final ApplicationContext context;
private List> engineListeners = new LinkedList>();
private final ScheduledExecutorService scriptEngineWorkers =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("scriptEngineWorkers-thread-%d").build());
public ScriptEngine(T scripting, final ApplicationContext context) {
this.scripting = scripting;
this.context = context;
}
public void addScriptEngineListener(ScriptEngineListener listener) {
engineListeners.add(listener);
}
public void removeScriptEngineListener(ScriptEngineListener listener) {
engineListeners.remove(listener);
}
public void setScriptRepeating(boolean repeating) {
this.scriptRepeating.set(repeating);
}
public void setScriptCommands(List> scriptCommands) {
LOGGER.info("Set the new script commands.");
synchronized (scriptWorkerLock) {
if (scriptRunning.get()) {
LOGGER
.warn(
"The script engine is currently processing commands. Stop the engine before setting new commands.");
throw new IllegalStateException(
"The script engine is currently processing commands. Stop the engine before setting new commands.");
}
this.scriptCommands = scriptCommands;
}
}
public boolean hasScriptCommands() {
return CollectionUtils.isNotEmpty(this.scriptCommands);
}
public void startScript() {
startStepScript(cmd -> Boolean.TRUE);
}
public void startStepScript(final Function, Boolean> beforeStepCallback) {
synchronized (scriptWorkerLock) {
if (scriptCommands != null && scriptWorker == null) {
LOGGER.info("Create and start scriptWorker.");
scriptRunning.set(true);
signalScriptStatus(ScriptStatus.RUNNING);
scriptWorker = scriptEngineWorkers.submit(() -> {
LOGGER.info("Start execution of script commands.");
try {
int currentExecution = 1;
do {
if (currentExecution > 1) {
scripting.echo("Repeating execution: " + currentExecution);
}
for (ScriptCommand command : scriptCommands) {
if (!scriptRunning.get() || Thread.currentThread().isInterrupted()) {
LOGGER.info("Script execution is stopped.");
break;
}
processCommand(command, beforeStepCallback);
}
currentExecution++;
}
while (scriptRepeating.get() && scriptRunning.get());
if (context.get(Scripting.KEY_SCRIPT_ERRORS) != null) {
LOGGER.warn("Script errors detected: {}", context.get(Scripting.KEY_SCRIPT_ERRORS));
signalScriptStatus(ScriptStatus.FINISHED_WITH_ERRORS);
}
else {
signalScriptStatus(ScriptStatus.FINISHED);
}
}
catch (ScriptExecutionException ex) {
LOGGER
.warn(
"Executing script command has failed. Set the scriptStatus to aborted. Failing script command: {}",
ex.getScriptCommand(), ex);
if (ex.getScriptCommand() != null) {
addError(context, ex.getMessage() + " - Failing command: " + ex.getScriptCommand());
}
else {
addError(context, ex.getMessage());
}
signalScriptStatus(ScriptStatus.ABORTED);
}
catch (Exception ex) {
LOGGER.warn("Executing script command has failed. Set the scriptStatus to aborted.", ex);
addError(context, ex.getMessage());
signalScriptStatus(ScriptStatus.ABORTED);
}
finally {
LOGGER.info("Script worker has finished.");
// check if an option is set
checkOptions(context, beforeStepCallback);
// the script has finished
scriptRunning.set(false);
LOGGER.info("Release the script worker.");
scriptWorker = null;
}
});
LOGGER.info("Start script worker has passed.");
}
else if (scriptWorker != null) {
LOGGER.warn("Script worker is still running.");
}
}
}
protected void checkOptions(
final ApplicationContext context, final Function, Boolean> beforeStepCallback) {
final Map options = context.get(Scripting.KEY_OPTIONS, Map.class);
if (!options.isEmpty()) {
try {
// check if we must disconnect
Object optionDisconnectOnError = options.get(Scripting.OPTION_DISCONNECT_ON_ERROR);
if (optionDisconnectOnError instanceof String) {
boolean disconnectOnError = Boolean.parseBoolean((String) optionDisconnectOnError);
if (disconnectOnError) {
LOGGER.info("Disconnect on error is enabled.");
if (ScriptStatus.ABORTED == scriptStatus) {
ScriptCommand disconnectCommand =
this.scriptCommands
.stream().filter(cmd -> cmd instanceof ScriptOptionAwareCommand)
.filter(cmd -> Scripting.OPTION_DISCONNECT_ON_ERROR
.equals(((ScriptOptionAwareCommand) cmd).getOptionKey()))
.findFirst().orElse(null);
if (disconnectCommand != null) {
LOGGER.info("Submit the disconnect command for execution: {}", disconnectCommand);
scriptEngineWorkers.submit(() -> processCommand(disconnectCommand, beforeStepCallback));
}
else {
LOGGER.warn("No disconnect command configured.");
}
}
}
}
}
catch (Exception ex) {
LOGGER.warn("Evaluate options failed.", ex);
}
}
}
private void processCommand(
ScriptCommand command, final Function, Boolean> beforeStepCallback) {
updateCurrentCommand(command);
if (beforeStepCallback.apply(command)) {
command.execute(scripting, context);
}
else {
LOGGER.warn("Execution of steps was aborted by user in beforeStepCallback.");
throw new ScriptExecutionException("Execution of steps was aborted by user.", command);
}
}
protected void addError(final ApplicationContext context, String errorDescription) {
List scriptErrors = (List) context.get(Scripting.KEY_SCRIPT_ERRORS);
if (scriptErrors == null) {
scriptErrors = new LinkedList<>();
context.register(Scripting.KEY_SCRIPT_ERRORS, scriptErrors);
}
LOGGER.info("Add script error: {}", errorDescription);
scriptErrors.add(errorDescription);
}
/**
* Initiate stop the script worker but don't wait for termination.
*/
public void stopScript() {
stopScript(null);
}
/**
* Initiate stop the script worker and wait at most waitForTermination
for termination.
*
* @param waitForTermination
* the time to wait in milliseconds
*/
public void stopScript(Long waitForTermination) {
scriptRunning.set(false);
LOGGER.info("Stop the script.");
synchronized (scriptWorkerLock) {
if (scriptWorker != null) {
LOGGER.info("Interrupt the script worker: {}", scriptWorker);
// scriptWorker.interrupt();
boolean successful = scriptWorker.cancel(true);
LOGGER.info("Interrupt the script worker was successful: {}", successful);
if (waitForTermination != null) {
long waitTime = waitForTermination.longValue();
LOGGER.info("Wait for termination of script worker for {}ms", waitTime);
try {
// scriptWorker.join(waitTime);
scriptWorker.get(waitTime, TimeUnit.MILLISECONDS);
}
catch (CancellationException ex) {
LOGGER.warn("Wait for termination of script worker has finished with CancellationException.");
}
catch (InterruptedException | ExecutionException | TimeoutException ex) {
LOGGER.warn("Wait for termination of script worker was interrupted.", ex);
}
// release the script worker instance
scriptWorker = null;
}
else {
LOGGER.info("Do not wait for termination of script worker.");
}
}
else {
LOGGER.info("Script worker is not available.");
}
}
}
private ScriptStatus scriptStatus = ScriptStatus.STOPPED;
private void signalScriptStatus(ScriptStatus scriptStatus) {
this.scriptStatus = scriptStatus;
for (ScriptEngineListener listener : engineListeners) {
listener.scriptStatusChanged(scriptStatus);
}
}
private void updateCurrentCommand(final ScriptCommand command) {
for (ScriptEngineListener listener : engineListeners) {
listener.currentCommandChanged(command);
}
}
}