package net.pterodactylus.reactor.engine;
+import java.util.HashMap;
import java.util.Map;
-import java.util.Set;
+import java.util.Map.Entry;
import java.util.SortedMap;
-import java.util.concurrent.TimeUnit;
import net.pterodactylus.reactor.Filter;
import net.pterodactylus.reactor.Query;
import net.pterodactylus.reactor.Reaction;
import net.pterodactylus.reactor.Trigger;
import net.pterodactylus.reactor.states.AbstractState;
+import net.pterodactylus.reactor.states.FailedState;
+import net.pterodactylus.reactor.states.StateManager;
+import org.apache.commons.lang3.tuple.Pair;
import org.apache.log4j.Logger;
import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AbstractExecutionThreadService;
-import com.google.common.util.concurrent.Uninterruptibles;
/**
* Reactor main engine.
/** The logger. */
private static final Logger logger = Logger.getLogger(Engine.class);
- /** All defined reactions. */
- private final Set<Reaction> reactions = Sets.newHashSet();
+ /** The state manager. */
+ private final StateManager stateManager = new StateManager("states");
- /** Reaction states. */
- private final Map<Reaction, ReactionExecution> reactionExecutions = Maps.newHashMap();
+ /** All defined reactions. */
+ /* synchronize on itself. */
+ private final Map<String, Reaction> reactions = new HashMap<String, Reaction>();
//
// ACCESSORS
/**
* Adds the given reaction to this engine.
*
+ * @param name
+ * The name of the reaction
* @param reaction
* The reaction to add to this engine
*/
- @SuppressWarnings("synthetic-access")
- public void addReaction(Reaction reaction) {
- reactions.add(reaction);
- reactionExecutions.put(reaction, new ReactionExecution());
+ public void addReaction(String name, Reaction reaction) {
+ synchronized (reactions) {
+ reactions.put(name, reaction);
+ reactions.notifyAll();
+ }
+ }
+
+ /**
+ * Removes the reaction with the given name.
+ *
+ * @param name
+ * The name of the reaction to remove
+ */
+ public void removeReaction(String name) {
+ synchronized (reactions) {
+ if (!reactions.containsKey(name)) {
+ return;
+ }
+ reactions.remove(name);
+ reactions.notifyAll();
+ }
}
//
while (isRunning()) {
/* delay if we have no reactions. */
- if (reactions.isEmpty()) {
- logger.trace("Sleeping for 1 second while no Reactions available.");
- Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
- continue;
+ synchronized (reactions) {
+ if (reactions.isEmpty()) {
+ logger.debug("Sleeping while no Reactions available.");
+ try {
+ reactions.wait();
+ } catch (InterruptedException ie1) {
+ /* ignore, we’re looping anyway. */
+ }
+ continue;
+ }
}
/* find next reaction. */
- SortedMap<Long, Reaction> nextReactions = Maps.newTreeMap();
- for (Reaction reaction : reactions) {
- ReactionExecution reactionExecution = reactionExecutions.get(reaction);
- nextReactions.put(reactionExecution.lastExecutionTime() + reaction.updateInterval(), reaction);
+ SortedMap<Long, Pair<String, Reaction>> nextReactions = Maps.newTreeMap();
+ String reactionName;
+ Reaction nextReaction;
+ synchronized (reactions) {
+ for (Entry<String, Reaction> reactionEntry : reactions.entrySet()) {
+ net.pterodactylus.reactor.State state = stateManager.loadLastState(reactionEntry.getKey());
+ long stateTime = (state != null) ? state.time() : 0;
+ nextReactions.put(stateTime + reactionEntry.getValue().updateInterval(), Pair.of(reactionEntry.getKey(), reactionEntry.getValue()));
+ }
+ reactionName = nextReactions.get(nextReactions.firstKey()).getLeft();
+ nextReaction = nextReactions.get(nextReactions.firstKey()).getRight();
}
- Reaction nextReaction = nextReactions.get(nextReactions.firstKey());
- ReactionExecution reactionExecution = reactionExecutions.get(nextReaction);
- logger.debug(String.format("Next Reaction: %s.", nextReaction));
+ logger.debug(String.format("Next Reaction: %s.", reactionName));
/* wait until the next reaction has to run. */
- while (isRunning()) {
- long waitTime = (reactionExecution.lastExecutionTime() + nextReaction.updateInterval()) - System.currentTimeMillis();
- logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
- if (waitTime <= 0) {
- break;
+ net.pterodactylus.reactor.State lastState = stateManager.loadLastState(reactionName);
+ long lastStateTime = (lastState != null) ? lastState.time() : 0;
+ int lastStateFailCount = (lastState != null) ? lastState.failCount() : 0;
+ long waitTime = (lastStateTime + nextReaction.updateInterval()) - System.currentTimeMillis();
+ logger.debug(String.format("Time to wait for next Reaction: %d millseconds.", waitTime));
+ if (waitTime > 0) {
+ synchronized (reactions) {
+ try {
+ logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
+ reactions.wait(waitTime);
+ } catch (InterruptedException ie1) {
+ /* we’re looping! */
+ }
}
- try {
- logger.debug(String.format("Waiting for %d milliseconds.", waitTime));
- TimeUnit.MILLISECONDS.sleep(waitTime);
- } catch (InterruptedException ie1) {
- /* we’re looping! */
- }
- }
- /* are we still running? */
- if (!isRunning()) {
- break;
+ /* re-start loop to check for new reactions. */
+ continue;
}
/* run reaction. */
- reactionExecution.setLastExecutionTime(System.currentTimeMillis());
+ logger.info(String.format("Running Query for %s...", reactionName));
Query query = nextReaction.query();
net.pterodactylus.reactor.State state;
try {
logger.debug("Querying system...");
state = query.state();
+ if (state == null) {
+ state = FailedState.INSTANCE;
+ }
logger.debug("System queried.");
} catch (Throwable t1) {
logger.warn("Querying system failed!", t1);
/* convert states. */
for (Filter filter : nextReaction.filters()) {
- net.pterodactylus.reactor.State newState = filter.filter(state);
- logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
- state = newState;
+ if (state.success()) {
+ net.pterodactylus.reactor.State newState = filter.filter(state);
+ logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
+ state = newState;
+ }
+ }
+ if (!state.success()) {
+ state.setFailCount(lastStateFailCount + 1);
}
- reactionExecution.addState(state);
+ net.pterodactylus.reactor.State lastSuccessfulState = stateManager.loadLastSuccessfulState(reactionName);
+ stateManager.saveState(reactionName, state);
- /* only run trigger if we have collected two states. */
+ /* only run trigger if we have collected two successful states. */
Trigger trigger = nextReaction.trigger();
boolean triggerHit = false;
- if (reactionExecution.previousState() != null) {
+ if ((lastSuccessfulState != null) && lastSuccessfulState.success() && state.success()) {
logger.debug("Checking Trigger for changes...");
- triggerHit = trigger.triggers(reactionExecution.currentState(), reactionExecution.previousState());
+ triggerHit = trigger.triggers(state, lastSuccessfulState);
}
/* run action if trigger was hit. */
logger.debug(String.format("Trigger was hit: %s.", triggerHit));
if (triggerHit) {
logger.info("Executing Action...");
- nextReaction.action().execute(trigger.trigger());
+ nextReaction.action().execute(trigger.output());
}
}
}
- /**
- * Stores execution states of a {@link Reaction}.
- *
- * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
- */
- private static class ReactionExecution {
-
- /** The time the reaction was last executed. */
- private long lastExecutionTime;
-
- /** The previous state of the reaction. */
- private net.pterodactylus.reactor.State previousState;
-
- /** The current state of the reaction. */
- private net.pterodactylus.reactor.State currentState;
-
- //
- // ACCESSORS
- //
-
- /**
- * Returns the time the reaction was last executed. If the reaction was
- * not yet executed, this method returns {@code 0}.
- *
- * @return The last execution time of the reaction (in milliseconds
- * since Jan 1, 1970 UTC)
- */
- public long lastExecutionTime() {
- return lastExecutionTime;
- }
-
- /**
- * Returns the current state of the reaction. If the reaction was not
- * yet executed, this method returns {@code null}.
- *
- * @return The current state of the reaction
- */
- public net.pterodactylus.reactor.State currentState() {
- return currentState;
- }
-
- /**
- * Returns the previous state of the reaction. If the reaction was not
- * yet executed at least twice, this method returns {@code null}.
- *
- * @return The previous state of the reaction
- */
- public net.pterodactylus.reactor.State previousState() {
- return previousState;
- }
-
- /**
- * Sets the last execution time of the reaction.
- *
- * @param lastExecutionTime
- * The last execution time of the reaction (in milliseconds
- * since Jan 1, 1970 UTC)
- * @return This execution
- */
- public ReactionExecution setLastExecutionTime(long lastExecutionTime) {
- this.lastExecutionTime = lastExecutionTime;
- return this;
- }
-
- //
- // ACTIONS
- //
-
- /**
- * Adds the given state as current state and moves the current state
- * into the previous state.
- *
- * @param state
- * The new current state
- * @return This execution
- */
- public ReactionExecution addState(net.pterodactylus.reactor.State state) {
- previousState = currentState;
- currentState = state;
- return this;
- }
-
- }
-
}