Java tutorial
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) 2016 Pixida GmbH */ package de.pixida.logtest.engine; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.pixida.logtest.automatondefinitions.AutomatonLoadingException; import de.pixida.logtest.automatondefinitions.IAutomatonDefinition; import de.pixida.logtest.automatondefinitions.IEdgeDefinition; import de.pixida.logtest.automatondefinitions.INodeDefinition; import de.pixida.logtest.engine.AutomatonNode.Type; import de.pixida.logtest.engine.conditions.IEventDescription; import de.pixida.logtest.engine.conditions.IScriptEnvironment; import de.pixida.logtest.logreaders.ILogEntry; public class Automaton { public static final String DEFAULT_SCRIPTING_LANGUAGE = "JavaScript"; private static final Logger LOG = LoggerFactory.getLogger(Automaton.class); private final class EventForConditionEvaluation implements IEventDescription { private EventForConditionEvaluation() { // Empty constructor needed by checkstyle } @Override public boolean isLogEntry() { return Automaton.this.currentEvent.isLogEntry(); } @Override public long getLogEntryTime() { return Automaton.this.currentEvent.getLogEntry().getTime(); } @Override public boolean isEof() { return Automaton.this.currentEvent.isEof(); } @Override public String getLogEntryPayload() { return Automaton.this.currentEvent.getLogEntry().getPayload(); } @Override public String getChannel() { if (Automaton.this.currentEvent.getLogEntry() == null) { return ILogEntry.DEFAULT_CHANNEL; } else { return Automaton.this.currentEvent.getLogEntry().getChannel(); } } @Override public boolean getSupportsChannels() { return Automaton.this.currentEvent.getLogEntry() != null; } } public class ScriptEnvironment implements IScriptEnvironment // Make sure all methods which should be callable from a script are public, otherwise it won't work! { private static final String SCRIPT_LOG_OUTPUT_FORMAT = "Script output: {}"; // Note the current logEntry information may be null when the onLoad-Action is executed private Long time; private String payload; private Long lineNumber; private List<String> regExpConditionMatchingGroups; ScriptEnvironment() { // Empty constructor needed by checkstyle } public void info(final String msg) { for (final String line : this.splitLogLinesFromScript(msg)) { LOG.info(SCRIPT_LOG_OUTPUT_FORMAT, line); } } public void debug(final String msg) { for (final String line : this.splitLogLinesFromScript(msg)) { LOG.debug(SCRIPT_LOG_OUTPUT_FORMAT, line); } } public void reject(final String msg) { LOG.debug("Script requested to reject with message: {}", msg); Automaton.this.rejectFlag = true; Automaton.this.rejectMessage = msg; this.checkOnlyEitherRejectOrAcceptAreSet(); } public void reject() { LOG.debug("Script requested to reject without message"); Automaton.this.rejectFlag = true; Automaton.this.rejectMessage = null; this.checkOnlyEitherRejectOrAcceptAreSet(); } public void halt(final String msg) { LOG.debug("Script requested to halt with message: {}", msg); Automaton.this.haltFlag = true; } public void halt() { LOG.debug("Script requested to halt without message"); Automaton.this.haltFlag = true; } public void accept(final String msg) { LOG.debug("Script requested to accept with message: {}", msg); Automaton.this.acceptFlag = true; this.checkOnlyEitherRejectOrAcceptAreSet(); } public void accept() { LOG.debug("Script requested to accept without message"); Automaton.this.acceptFlag = true; this.checkOnlyEitherRejectOrAcceptAreSet(); } public String getParameter(final String name) { final String value = Automaton.this.parameters.get(name); if (value == null) { LOG.error("Script requested parameter '{}' which was not set!", name); throw new ExecutionException("Script requested parameter which is not set: " + name); } else { return value; } } public Long getLogEntryTime() { return this.time; } public String getLogEntryPayload() { return this.payload; } public Long getLogEntryLineNumber() { return this.lineNumber; } public String getRegExpConditionMatchingGroups(final int group) { LOG.trace("Script requesting reg exp matching group {}", group); if (this.regExpConditionMatchingGroups == null) { LOG.trace("No reg exp matching group available"); return null; } if (group < 0 || group >= this.regExpConditionMatchingGroups.size()) { LOG.debug("Returning null for invalid index '{}' of matches of regular expression condition: '{}'", group, this.regExpConditionMatchingGroups); // Throw no exception as by returning null, the script can check if the regular expression really matched. return null; } return this.regExpConditionMatchingGroups.get(group); } private void checkOnlyEitherRejectOrAcceptAreSet() { if (Automaton.this.acceptFlag && Automaton.this.rejectFlag) { final String msg = "reject() and accept() were called from scripts at the same time!"; LOG.error(msg); throw new ExecutionException(msg); } } private String[] splitLogLinesFromScript(final String msg) { return msg.split("(\r|\n|\r\n)"); } void updateEnvironment(final Event event) { LOG.trace("Updating scripting environment"); this.time = event.isLogEntry() ? event.getLogEntry().getTime() : null; this.payload = event.isLogEntry() ? event.getLogEntry().getPayload() : null; this.lineNumber = event.isLogEntry() ? event.getLogEntry().getLineNumber() : null; } @Override public void setRegExpConditionMatchingGroups(final List<String> value) { LOG.trace("Reg exp matching group set to: {}", value); this.regExpConditionMatchingGroups = value; } } private class LastTransition { private final AutomatonEdge edge; private final Event event; private final long timeMs; LastTransition(final AutomatonEdge aEdge, final Event aEvent, final long aTimeMs) { this.edge = aEdge; this.event = aEvent; this.timeMs = aTimeMs; } AutomatonNode getSourceNode() { return this.edge.getSourceNode(); } Event getEvent() { return this.event; } AutomatonEdge getEdge() { return this.edge; } long getTimeMs() { return this.timeMs; } } private enum NodeSuccessState { SUCCESS, NON_SUCCESS_NODE, SUCCESS_NODE_BUT_SUCCESS_CONDITION_FAILED } private final IAutomatonDefinition automatonDefinition; private EmbeddedScript onLoad; private final List<AutomatonNode> nodes = new ArrayList<>(); private final List<AutomatonEdge> edges = new ArrayList<>(); private AutomatonNode initialNode; private AutomatonNode currentNode; private final LinkedList<AutomatonNode> visitedNodes = new LinkedList<>(); // Used during processing, cached as member variable private Throwable thrownException; private ScriptEngine scriptingEngine; private ScriptEnvironment scriptEnvironment; private final AutomatonParameters parameters; private boolean haltFlag; private boolean acceptFlag; private boolean rejectFlag; private String rejectMessage; private LastTransition lastTransition; private Event currentEvent; private String description; private String scriptLanguage; private final TimingInfo timingInfo = new TimingInfo(); public Automaton(final IAutomatonDefinition aAutomatonDefinition, final Map<String, String> aParameters) { LOG.debug("Creating automaton with definition '{}' and parameters '{}'", aAutomatonDefinition, aParameters); Validate.notNull(aAutomatonDefinition); Validate.notNull(aParameters); this.automatonDefinition = aAutomatonDefinition; this.parameters = new AutomatonParameters(aParameters); try { this.loadAutomatonFromDefinition(); if (this.description != null) { this.description = this.parameters.insertAllParameters(this.description); } LOG.debug("Automaton description: {}", this.description); this.checkAutomatonAndFindInitialNode(); this.compileScripts(); } catch (final InvalidAutomatonDefinitionException iade) { final String errorsWithoutStackTraces = ExceptionUtils.getThrowableList(iade).stream() .map(e -> e.getMessage()).collect(Collectors.joining("; ")); LOG.error("Error while initializing automaton '{}': {}", this.automatonDefinition, errorsWithoutStackTraces); this.thrownException = iade; } catch (final RuntimeException re) { this.thrownException = re; throw re; } } public void proceedWithLogEntry(final ILogEntry logEntry) { LOG.trace("Proceeding with log entry: {}", logEntry); Validate.notNull(logEntry); Validate.notNull(logEntry.getPayload()); this.timingInfo.setTimeOfCurrentEvent(logEntry.getTime()); this.currentEvent = Event.fromLogEntry(logEntry); this.pushEvent(); } public void pushEof() { LOG.trace("Proceeding with EOF"); this.currentEvent = Event.fromEof(); this.pushEvent(); } public boolean canProceed() { LOG.trace("Checking if automaton can proceed"); if (this.thrownException != null) { LOG.trace("Cannot proceed because an exception was thrown"); return false; } if (this.haltFlag) { LOG.trace("Cannot proceed because of halt flag"); return false; } if (this.acceptFlag) { LOG.trace("Cannot proceed because of accept flag"); return false; } if (this.rejectFlag) { LOG.trace("Cannot proceed because of reject flag"); return false; } AutomatonNode checkNode; if (this.currentNode == null) { // Initialization complete but no log entry processed yet checkNode = this.initialNode; } else { checkNode = this.currentNode; } if (checkNode.getType() == Type.FAILURE) { LOG.trace("Cannot proceed because we're in a failure node"); return false; } if (!checkNode.hasOutgoingEdges()) { LOG.trace("Cannot proceed because there are no outgoing edges"); return false; } return true; } public boolean succeeded() { if (this.automatonDefect()) { LOG.debug("Run failed: Automaton is defect"); return false; } if (this.acceptFlag) { LOG.debug("Run succeeded: Accept flag was set"); return true; } if (this.rejectFlag) { LOG.debug("Run failed: Reject flag was set"); return false; } AutomatonNode checkNode; if (this.currentNode == null) { // Initialization complete but no log entry processed yet checkNode = this.initialNode; } else { checkNode = this.currentNode; } final NodeSuccessState nodeState = this.getNodeSuccessState(checkNode); final boolean success = nodeState == NodeSuccessState.SUCCESS; if (success) { LOG.debug("Run succeeded: Node is succeeding"); } else { LOG.debug("Run failed: Node is not succeeding but {}", nodeState); } return success; } public boolean automatonDefect() { return this.thrownException != null; } public String getErrorReason() { if (this.succeeded()) { return null; } else { // Exceptions have priority if (this.thrownException != null) { return this.thrownException.getMessage(); } else { if (this.rejectFlag) { String msg = "A script called reject()"; if (StringUtils.isNotBlank(this.rejectMessage)) { msg += ": " + this.rejectMessage; } return msg; } final AutomatonNode node = this.currentNode != null ? this.currentNode : this.initialNode; final NodeSuccessState failureReason = this.getNodeSuccessState(node); if (failureReason != NodeSuccessState.SUCCESS) { return this.asssembleFinalNodeFailureMsg(node, failureReason); } else { LOG.error( "Automaton has not succeeded but there's no user readable error message why this happened"); return "Unknown error"; } } } } public String getDescription() { return this.description; } private String asssembleFinalNodeFailureMsg(final AutomatonNode node, final NodeSuccessState failureReason) { String msg; if (failureReason == NodeSuccessState.NON_SUCCESS_NODE) { msg = "Final node '" + node + "' is not a succeeding state"; } else if (failureReason == NodeSuccessState.SUCCESS_NODE_BUT_SUCCESS_CONDITION_FAILED) { msg = "Final node '" + node + "' is a succeeding state BUT success check expression gave 'false'"; } else { throw new RuntimeException("Internal error - unhandled failure reason"); } if (this.lastTransition != null) { msg += " (entered from node '" + this.lastTransition.getSourceNode() + "' via edge '" + this.lastTransition.getEdge() + "' at automaton time '" + this.lastTransition.getTimeMs() + "'"; if (this.lastTransition.getEvent().isLogEntry()) { msg += " with log line '" + this.lastTransition.getEvent().getLogEntry().getLineNumber() + "'"; } else { msg += " with EOF"; } msg += ")"; } return msg; } private void pushEvent() { try { this.enterInitialNodeIfNotYetDone(); if (!this.canProceed()) { LOG.trace("Cannot proceed anymore"); return; } assert this.currentNode != null; this.scriptEnvironment.updateEnvironment(this.currentEvent); final AutomatonNode eventStartNode = this.currentNode; this.visitedNodes.clear(); while (this.proceed()) { if (!this.canProceed()) { LOG.trace("Cannot proceed anymore"); break; } if (this.currentNode.getWait()) { LOG.trace("Wait flag set in current node; finishing transition"); break; } } final AutomatonNode eventEndNode = this.currentNode; if (eventStartNode != eventEndNode) { this.timingInfo.setTimeOfLastTransition(this.timingInfo.getTimeOfCurrentEvent()); } LOG.debug("Microtransitions: {}", this.visitedNodes.size()); } catch (final RuntimeException re) { LOG.trace("Got exception during execution", re); this.thrownException = re; throw re; } } private NodeSuccessState getNodeSuccessState(final AutomatonNode node) { if (node.getType() != Type.SUCCESS) { return NodeSuccessState.NON_SUCCESS_NODE; } else { if (node.getSuccessCheckExp().exists() && !node.getSuccessCheckExp().runAndGetBooleanResult()) { LOG.debug("Cannot succeed with node '{}' as success check expression tells us 'no'", node); return NodeSuccessState.SUCCESS_NODE_BUT_SUCCESS_CONDITION_FAILED; } else { return NodeSuccessState.SUCCESS; } } } private void enterInitialNodeIfNotYetDone() { if (this.currentNode == null) { if (this.thrownException != null) { // Do not start processing when the automaton could not be initialized return; } this.onLoad.run(); if (this.scriptEnvironment != null) { this.scriptEnvironment.updateEnvironment(this.currentEvent); // Load log entry after onLoad was executed } this.currentNode = this.initialNode; // Start with first timestamp, or 0, if there is none this.timingInfo .setStartTime(this.currentEvent.isLogEntry() ? this.currentEvent.getLogEntry().getTime() : 0L); this.timingInfo.setTimeOfLastMicrotransition(this.timingInfo.getStartTime()); this.timingInfo.setTimeOfLastTransition(this.timingInfo.getStartTime()); this.timingInfo.setTimeOfCurrentEvent(this.timingInfo.getStartTime()); this.currentNode.getOnEnter().run(); } } private void checkAutomatonAndFindInitialNode() { this.checkInitialNodeExistsAndFindIt(); this.checkFailureNodesHaveNoOutgoingEdges(); this.checkAllEdgesHaveAtLeastOneCondition(); this.checkSuccessCheckExpIsOnlyAppliedToSuccessNodes(); } private void checkSuccessCheckExpIsOnlyAppliedToSuccessNodes() { final List<AutomatonNode> nodesWithSuccessCheckExpButNotSuccessType = this.nodes.stream() .filter(node -> node.getSuccessCheckExp().exists() && node.getType() != Type.SUCCESS) .collect(Collectors.toList()); if (nodesWithSuccessCheckExpButNotSuccessType.size() > 0) { throw new InvalidAutomatonDefinitionException( "Nodes have success check expression, but are not of success type: " + nodesWithSuccessCheckExpButNotSuccessType.stream().map(edge -> edge.toString()) .collect(Collectors.joining(", "))); } } private void checkAllEdgesHaveAtLeastOneCondition() { final List<AutomatonEdge> edgesWithoutCondition = this.edges.stream() .filter(edge -> !edge.hasActiveConditions()).collect(Collectors.toList()); if (edgesWithoutCondition.size() > 0) { throw new InvalidAutomatonDefinitionException("Edges have no condition: " + edgesWithoutCondition .stream().map(edge -> edge.toString()).collect(Collectors.joining(", "))); } } private void checkFailureNodesHaveNoOutgoingEdges() { for (final AutomatonNode node : this.nodes) { if (node.getType() == Type.FAILURE && node.hasOutgoingEdges()) { throw new InvalidAutomatonDefinitionException( "A failure node must not have outgoing edges: " + node); } } } private void checkInitialNodeExistsAndFindIt() { final List<AutomatonNode> initialNodes = this.nodes.stream().filter(node -> node.getType() == Type.INITIAL) .collect(Collectors.toList()); if (initialNodes.size() > 1) { throw new InvalidAutomatonDefinitionException("Multiple initial nodes defined: " + initialNodes.stream().map(node -> node.toString()).collect(Collectors.joining(", "))); } if (initialNodes.size() == 0) { throw new InvalidAutomatonDefinitionException("No initial node defined."); } this.initialNode = initialNodes.get(0); } private void loadAutomatonFromDefinition() { LOG.debug("Loading automaton from definition"); try { this.automatonDefinition.load(); } catch (final AutomatonLoadingException ale) { throw new InvalidAutomatonDefinitionException("Failed to load automaton!", ale); } Validate.isTrue(this.scriptingEngine == null); // Must not be initialized yet; otherwise, the setting has no more effect this.scriptLanguage = this.automatonDefinition.getScriptLanguage(); this.initScriptEngine(); final List<? extends INodeDefinition> externalNodes = this.automatonDefinition.getNodes(); final List<? extends IEdgeDefinition> externalEdges = this.automatonDefinition.getEdges(); this.onLoad = new EmbeddedScript(this.automatonDefinition.getOnLoad()); this.description = this.automatonDefinition.getDescription(); final Map<INodeDefinition, AutomatonNode> mapNodeDefinitionsToInternalNode = new HashMap<>(); this.loadNodesFromDefinition(externalNodes, mapNodeDefinitionsToInternalNode); this.loadEdgesFromDefinition(externalEdges, mapNodeDefinitionsToInternalNode); LOG.debug("Loaded '{}' nodes and '{}' edges", this.nodes.size(), this.edges.size()); } private void loadEdgesFromDefinition(final List<? extends IEdgeDefinition> externalEdges, final Map<INodeDefinition, AutomatonNode> mapNodeDefinitionsToInternalNode) { LOG.debug("Loading edges from definition"); for (final IEdgeDefinition edgeDefinition : externalEdges) { final INodeDefinition src = edgeDefinition.getSource(); final AutomatonNode srcNode = mapNodeDefinitionsToInternalNode.get(src); if (srcNode == null) { throw new InvalidAutomatonDefinitionException( "Source node '" + src + "' of edge '" + edgeDefinition + "' not found!"); } final INodeDefinition dest = edgeDefinition.getDestination(); final AutomatonNode destNode = mapNodeDefinitionsToInternalNode.get(dest); if (destNode == null) { throw new InvalidAutomatonDefinitionException( "Destination node '" + dest + "' of edge '" + edgeDefinition + "' not found!"); } final AutomatonEdge newEdge = new AutomatonEdge(edgeDefinition); newEdge.setSource(srcNode); newEdge.setDestination(destNode); newEdge.setOnWalk(new EmbeddedScript(edgeDefinition.getOnWalk())); if (edgeDefinition.getChannel() == IEdgeDefinition.DEFAULT_CHANNEL) { newEdge.setChannel(ILogEntry.DEFAULT_CHANNEL); } else { newEdge.setChannel(edgeDefinition.getChannel()); } IEdgeDefinition.RequiredConditions requiredConditionsSetting = edgeDefinition.getRequiredConditions(); if (requiredConditionsSetting == null) { requiredConditionsSetting = IEdgeDefinition.DEFAULT_REQUIRED_CONDITIONS_VALUE; } if (requiredConditionsSetting == IEdgeDefinition.RequiredConditions.ALL) { newEdge.setRequiredConditionsSetting(AutomatonEdge.RequiredConditions.ALL); } else if (requiredConditionsSetting == IEdgeDefinition.RequiredConditions.ONE) { newEdge.setRequiredConditionsSetting(AutomatonEdge.RequiredConditions.ONE); } newEdge.initConditions(this.parameters, this.scriptingEngine); this.edges.add(newEdge); srcNode.addOutgoingEdge(newEdge); destNode.addIncomingEdge(newEdge); } LOG.debug("Edges from definition loaded"); } private void loadNodesFromDefinition(final List<? extends INodeDefinition> externalNodes, final Map<INodeDefinition, AutomatonNode> mapNodeDefinitionsToInternalNode) { LOG.debug("Loading nodes from definition"); for (final INodeDefinition nodeDefinition : externalNodes) { final AutomatonNode newNode = new AutomatonNode(nodeDefinition); final String externalNodeName = nodeDefinition.toString(); newNode.setName(this.parameters.insertAllParameters( externalNodeName == null ? null : this.parameters.insertAllParameters(externalNodeName))); if (nodeDefinition.getType() == INodeDefinition.Type.INITIAL) { newNode.setType(AutomatonNode.Type.INITIAL); } if (nodeDefinition.getType() == INodeDefinition.Type.FAILURE) { newNode.setType(AutomatonNode.Type.FAILURE); } if (nodeDefinition.getType() == INodeDefinition.Type.SUCCESS) { newNode.setType(AutomatonNode.Type.SUCCESS); } newNode.setOnEnter(new EmbeddedScript(nodeDefinition.getOnEnter())); newNode.setOnLeave(new EmbeddedScript(nodeDefinition.getOnLeave())); newNode.setSuccessCheckExp(new EmbeddedScript(nodeDefinition.getSuccessCheckExp())); newNode.setWait(nodeDefinition.getWait()); this.nodes.add(newNode); mapNodeDefinitionsToInternalNode.put(nodeDefinition, newNode); } LOG.debug("Nodes from definition loaded"); } private boolean proceed() { LOG.trace("Proceeding to next node"); final List<AutomatonEdge> matchingEdges = new ArrayList<>(this.currentNode.getOutgoingEdges().size()); for (final AutomatonEdge edge : this.currentNode.getOutgoingEdges()) { if (edge.edgeMatchesEvent(new EventForConditionEvaluation(), this.timingInfo, this.scriptEnvironment)) { matchingEdges.add(edge); LOG.trace("Found matching edge: '{}'", edge); } } if (matchingEdges.isEmpty()) { LOG.trace("No matching edges"); return false; } if (matchingEdges.size() > 1) { if (!this.checkMatchingEdgesAreEquivalent(matchingEdges)) { throw new ExecutionException("Found ambiguous nodes to proceed to from node '" + this.currentNode + "' as more than one edge is matching: " + matchingEdges.stream().map(edge -> edge.toString()).collect(Collectors.joining(", "))); } } final AutomatonEdge matchingEdge = matchingEdges.get(0); final boolean closingALoop = this.haveLoop(matchingEdge); final AutomatonNode lastNode = this.currentNode; this.proceedViaEdge(matchingEdge); if (this.currentEvent.isLogEntry()) { LOG.debug( "Proceeded to next state: Node '{}' -> '{}' via edge '{}' triggered by log entity on line '{}'", lastNode, this.currentNode, matchingEdge, this.currentEvent.getLogEntry().getLineNumber()); } else { LOG.debug("Proceeded to next state: Node '{}' -> '{}' via edge '{}' triggered by EOF", lastNode, this.currentNode, matchingEdge); } if (closingALoop) { LOG.trace("Closed loop. Stopping."); return false; } return true; } private boolean checkMatchingEdgesAreEquivalent(final List<AutomatonEdge> matchingEdges) { LOG.trace("Checking if '{}' edges point to different nodes or have scripts", matchingEdges.size()); // Edges are not equivalent if they lead to different nodes AutomatonNode dest = null; for (final AutomatonEdge edge : matchingEdges) { if (dest == null) { dest = edge.getDestinationNode(); } else { if (dest != edge.getDestinationNode()) { LOG.trace("Edges are not equivalent as some of them point to different targets ('{}' and '{}')", dest, edge.getDestinationNode()); return false; } } } // Edges are not equivalent if they contain scripting actions if (matchingEdges.stream().anyMatch(edge -> edge.getOnWalk().exists())) { LOG.trace("Edges are not equivalent as of scripting actions having potential side effects"); return false; } return true; } private boolean haveLoop(final AutomatonEdge matchingEdge) { this.visitedNodes.add(matchingEdge.getSourceNode()); if (this.visitedNodes.contains(matchingEdge.getDestinationNode())) { // As this can be intended (self loops on nodes), only log this on trace level as it could spam too much output. if (LOG.isTraceEnabled()) { this.visitedNodes.push(this.visitedNodes.getFirst()); // Close loop to make the following exception message more intuitive LOG.trace("Found loop during execution: {}, stopping at transaction base node", this.visitedNodes .stream().map(node -> node.toString()).collect(Collectors.joining(" -> "))); this.visitedNodes.pop(); } return true; } return false; } private void proceedViaEdge(final AutomatonEdge edge) { assert this.currentNode != null; this.currentNode.getOnLeave().run(); this.currentNode = edge.getDestinationNode(); if (edge.getOnWalk().exists()) { edge.beforeOnWalk(this.scriptEnvironment); edge.getOnWalk().run(); edge.afterOnWalk(this.scriptEnvironment); } this.currentNode.getOnEnter().run(); this.lastTransition = new LastTransition(edge, this.currentEvent, this.timingInfo.getTimeOfCurrentEvent()); this.timingInfo.setTimeOfLastMicrotransition(this.timingInfo.getTimeOfCurrentEvent()); } private void initScriptEngine() { final ScriptEngineManager manager = new ScriptEngineManager(); final String language = StringUtils.defaultIfBlank(this.scriptLanguage, DEFAULT_SCRIPTING_LANGUAGE); this.scriptingEngine = manager.getEngineByName(language); if (this.scriptingEngine == null) { final String msg = "Scripting engine for language '" + language + "' could not be initalized"; LOG.error(msg); throw new ExecutionException(msg); } else { LOG.debug("Using script language: {}", language); } this.scriptEnvironment = new ScriptEnvironment(); this.scriptingEngine.put("engine", this.scriptEnvironment); } private void compileScripts() { this.onLoad.compile(this.scriptingEngine); this.nodes.stream().forEach(node -> { node.getOnEnter().compile(this.scriptingEngine); node.getOnLeave().compile(this.scriptingEngine); node.getSuccessCheckExp().compile(this.scriptingEngine); }); this.edges.stream().forEach(edge -> { edge.getOnWalk().compile(this.scriptingEngine); }); } // Just for logging output / no business use @Override public String toString() { return this.automatonDefinition.toString(); } }