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 long lastStateTime = lastState.isPresent() ? lastState.get().time() : 0;
126 int lastStateFailCount = lastState.isPresent() ? lastState.get().failCount() : 0;
127 long waitTime = (lastStateTime + nextReaction.get().getReaction().updateInterval()) - System.currentTimeMillis();
128 logger.debug(format("Time to wait for next Reaction: %d millseconds.", waitTime));
130 synchronized (reactions) {
132 logger.info(format("Waiting until %tc.", lastStateTime + nextReaction.get().getReaction().updateInterval()));
133 reactions.wait(waitTime);
134 } catch (InterruptedException ie1) {
139 /* re-start loop to check for new reactions. */
144 logger.info(format("Running Query for %s...", reactionName));
145 Query query = nextReaction.get().getReaction().query();
146 net.pterodactylus.rhynodge.State state;
148 logger.debug("Querying system...");
149 state = query.state();
151 state = FailedState.INSTANCE;
153 logger.debug("System queried.");
154 } catch (Throwable t1) {
155 logger.warn("Querying system failed!", t1);
156 state = new AbstractState(t1) {
157 /* no further state. */
160 logger.debug(format("State is %s.", state));
162 /* convert states. */
163 for (Filter filter : nextReaction.get().getReaction().filters()) {
164 if (state.success()) {
165 net.pterodactylus.rhynodge.State newState = filter.filter(state);
166 //logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
170 if (!state.success()) {
171 state.setFailCount(lastStateFailCount + 1);
173 Optional<net.pterodactylus.rhynodge.State> lastSuccessfulState = stateManager.loadLastSuccessfulState(reactionName);
176 boolean triggerHit = false;
177 Trigger trigger = nextReaction.get().getReaction().trigger();
178 if (lastSuccessfulState.isPresent() && lastSuccessfulState.get().success() && state.success()) {
179 net.pterodactylus.rhynodge.State newState = trigger.mergeStates(lastSuccessfulState.get(), state);
181 /* save new state. */
182 stateManager.saveState(reactionName, newState);
184 triggerHit = trigger.triggers();
186 /* save first or error state. */
187 stateManager.saveState(reactionName, state);
190 /* run action if trigger was hit. */
191 logger.debug(format("Trigger was hit: %s.", triggerHit));
193 logger.info("Executing Action...");
194 nextReaction.get().getReaction().action().execute(trigger.output(nextReaction.get().getReaction()));
200 private Optional<NextReaction> getNextReaction() {
201 while (isRunning()) {
202 synchronized (reactions) {
203 if (reactions.isEmpty()) {
204 logger.debug("Sleeping while no Reactions available.");
207 } catch (InterruptedException ie1) {
208 /* ignore, we’re looping anyway. */
214 /* find next reaction. */
215 SortedMap<Long, Pair<String, Reaction>> nextReactions = newTreeMap();
216 synchronized (reactions) {
217 for (Entry<String, Reaction> reactionEntry : reactions.entrySet()) {
218 Optional<net.pterodactylus.rhynodge.State> state = stateManager.loadLastState(reactionEntry.getKey());
219 long stateTime = state.isPresent() ? state.get().time() : 0;
220 nextReactions.put(stateTime + reactionEntry.getValue().updateInterval(), Pair.of(reactionEntry.getKey(), reactionEntry.getValue()));
222 Pair<String, Reaction> keyReaction = nextReactions.get(nextReactions.firstKey());
223 return of(new NextReaction(keyReaction.getKey(), keyReaction.getValue(), nextReactions.firstKey()));
229 private static class NextReaction {
231 private final String key;
232 private final Reaction reaction;
233 private final long nextTime;
235 private NextReaction(String key, Reaction reaction, long nextTime) {
237 this.reaction = reaction;
238 this.nextTime = nextTime;
241 public String getKey() {
245 public Reaction getReaction() {
249 public long getNextTime() {