2 * Rhynodge - Engine.java - Copyright © 2013 David Roden
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package net.pterodactylus.rhynodge.engine;
20 import static com.google.common.base.Optional.absent;
21 import static com.google.common.base.Optional.of;
22 import static com.google.common.collect.Maps.newTreeMap;
23 import static java.lang.String.format;
25 import java.util.HashMap;
27 import java.util.Map.Entry;
28 import java.util.SortedMap;
30 import net.pterodactylus.rhynodge.Filter;
31 import net.pterodactylus.rhynodge.Query;
32 import net.pterodactylus.rhynodge.Reaction;
33 import net.pterodactylus.rhynodge.Trigger;
34 import net.pterodactylus.rhynodge.states.AbstractState;
35 import net.pterodactylus.rhynodge.states.FailedState;
36 import net.pterodactylus.rhynodge.states.StateManager;
38 import com.google.common.base.Optional;
39 import com.google.common.util.concurrent.AbstractExecutionThreadService;
40 import org.apache.commons.lang3.tuple.Pair;
41 import org.apache.log4j.Logger;
44 * Rhynodge main engine.
46 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
48 public class Engine extends AbstractExecutionThreadService {
51 private static final Logger logger = Logger.getLogger(Engine.class);
53 /** The state manager. */
54 private final StateManager stateManager;
56 /** All defined reactions. */
57 /* synchronize on itself. */
58 private final Map<String, Reaction> reactions = new HashMap<String, Reaction>();
61 * Creates a new engine.
66 public Engine(StateManager stateManager) {
67 this.stateManager = stateManager;
75 * Adds the given reaction to this engine.
78 * The name of the reaction
80 * The reaction to add to this engine
82 public void addReaction(String name, Reaction reaction) {
83 synchronized (reactions) {
84 reactions.put(name, reaction);
85 reactions.notifyAll();
90 * Removes the reaction with the given name.
93 * The name of the reaction to remove
95 public void removeReaction(String name) {
96 synchronized (reactions) {
97 if (!reactions.containsKey(name)) {
100 reactions.remove(name);
101 reactions.notifyAll();
106 // ABSTRACTSERVICE METHODS
114 while (isRunning()) {
115 Optional<NextReaction> nextReaction = getNextReaction();
116 if (!nextReaction.isPresent()) {
120 String reactionName = nextReaction.get().getKey();
121 logger.debug(format("Next Reaction: %s.", reactionName));
123 /* wait until the next reaction has to run. */
124 Optional<net.pterodactylus.rhynodge.State> lastState = stateManager.loadLastState(reactionName);
125 int lastStateFailCount = lastState.isPresent() ? lastState.get().failCount() : 0;
126 long waitTime = nextReaction.get().getNextTime() - System.currentTimeMillis();
127 logger.debug(format("Time to wait for next Reaction: %d millseconds.", waitTime));
129 waitForNextReactionToStart(nextReaction, waitTime);
131 /* re-start loop to check for new reactions. */
136 logger.info(format("Running Query for %s...", reactionName));
137 Query query = nextReaction.get().getReaction().query();
138 net.pterodactylus.rhynodge.State state;
140 logger.debug("Querying system...");
141 state = query.state();
143 state = FailedState.INSTANCE;
145 logger.debug("System queried.");
146 } catch (Throwable t1) {
147 logger.warn("Querying system failed!", t1);
148 state = new AbstractState(t1) {
149 /* no further state. */
152 logger.debug(format("State is %s.", state));
154 /* convert states. */
155 for (Filter filter : nextReaction.get().getReaction().filters()) {
156 if (state.success()) {
157 state = filter.filter(state);
160 if (!state.success()) {
161 state.setFailCount(lastStateFailCount + 1);
163 Optional<net.pterodactylus.rhynodge.State> lastSuccessfulState = stateManager.loadLastSuccessfulState(reactionName);
166 boolean triggerHit = false;
167 Trigger trigger = nextReaction.get().getReaction().trigger();
168 if (lastSuccessfulState.isPresent() && lastSuccessfulState.get().success() && state.success()) {
169 net.pterodactylus.rhynodge.State newState = trigger.mergeStates(lastSuccessfulState.get(), state);
171 /* save new state. */
172 stateManager.saveState(reactionName, newState);
174 triggerHit = trigger.triggers();
176 /* save first or error state. */
177 stateManager.saveState(reactionName, state);
180 /* run action if trigger was hit. */
181 logger.debug(format("Trigger was hit: %s.", triggerHit));
183 logger.info("Executing Action...");
184 nextReaction.get().getReaction().action().execute(trigger.output(nextReaction.get().getReaction()));
190 private void waitForNextReactionToStart(Optional<NextReaction> nextReaction, long waitTime) {
191 synchronized (reactions) {
193 logger.info(format("Waiting until %tc.", nextReaction.get().getNextTime()));
194 reactions.wait(waitTime);
195 } catch (InterruptedException ie1) {
201 private Optional<NextReaction> getNextReaction() {
202 while (isRunning()) {
203 synchronized (reactions) {
204 if (reactions.isEmpty()) {
205 logger.debug("Sleeping while no Reactions available.");
208 } catch (InterruptedException ie1) {
209 /* ignore, we’re looping anyway. */
215 /* find next reaction. */
216 SortedMap<Long, Pair<String, Reaction>> nextReactions = newTreeMap();
217 synchronized (reactions) {
218 for (Entry<String, Reaction> reactionEntry : reactions.entrySet()) {
219 Optional<net.pterodactylus.rhynodge.State> state = stateManager.loadLastState(reactionEntry.getKey());
220 long stateTime = state.isPresent() ? state.get().time() : 0;
221 nextReactions.put(stateTime + reactionEntry.getValue().updateInterval(), Pair.of(reactionEntry.getKey(), reactionEntry.getValue()));
223 Pair<String, Reaction> keyReaction = nextReactions.get(nextReactions.firstKey());
224 return of(new NextReaction(keyReaction.getKey(), keyReaction.getValue(), nextReactions.firstKey()));
230 private static class NextReaction {
232 private final String key;
233 private final Reaction reaction;
234 private final long nextTime;
236 private NextReaction(String key, Reaction reaction, long nextTime) {
238 this.reaction = reaction;
239 this.nextTime = nextTime;
242 public String getKey() {
246 public Reaction getReaction() {
250 public long getNextTime() {