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 synchronized (reactions) {
131 logger.info(format("Waiting until %tc.", nextReaction.get().getNextTime()));
132 reactions.wait(waitTime);
133 } catch (InterruptedException ie1) {
138 /* re-start loop to check for new reactions. */
143 logger.info(format("Running Query for %s...", reactionName));
144 Query query = nextReaction.get().getReaction().query();
145 net.pterodactylus.rhynodge.State state;
147 logger.debug("Querying system...");
148 state = query.state();
150 state = FailedState.INSTANCE;
152 logger.debug("System queried.");
153 } catch (Throwable t1) {
154 logger.warn("Querying system failed!", t1);
155 state = new AbstractState(t1) {
156 /* no further state. */
159 logger.debug(format("State is %s.", state));
161 /* convert states. */
162 for (Filter filter : nextReaction.get().getReaction().filters()) {
163 if (state.success()) {
164 net.pterodactylus.rhynodge.State newState = filter.filter(state);
165 //logger.debug(String.format("Old state is %s, new state is %s.", state, newState));
169 if (!state.success()) {
170 state.setFailCount(lastStateFailCount + 1);
172 Optional<net.pterodactylus.rhynodge.State> lastSuccessfulState = stateManager.loadLastSuccessfulState(reactionName);
175 boolean triggerHit = false;
176 Trigger trigger = nextReaction.get().getReaction().trigger();
177 if (lastSuccessfulState.isPresent() && lastSuccessfulState.get().success() && state.success()) {
178 net.pterodactylus.rhynodge.State newState = trigger.mergeStates(lastSuccessfulState.get(), state);
180 /* save new state. */
181 stateManager.saveState(reactionName, newState);
183 triggerHit = trigger.triggers();
185 /* save first or error state. */
186 stateManager.saveState(reactionName, state);
189 /* run action if trigger was hit. */
190 logger.debug(format("Trigger was hit: %s.", triggerHit));
192 logger.info("Executing Action...");
193 nextReaction.get().getReaction().action().execute(trigger.output(nextReaction.get().getReaction()));
199 private Optional<NextReaction> getNextReaction() {
200 while (isRunning()) {
201 synchronized (reactions) {
202 if (reactions.isEmpty()) {
203 logger.debug("Sleeping while no Reactions available.");
206 } catch (InterruptedException ie1) {
207 /* ignore, we’re looping anyway. */
213 /* find next reaction. */
214 SortedMap<Long, Pair<String, Reaction>> nextReactions = newTreeMap();
215 synchronized (reactions) {
216 for (Entry<String, Reaction> reactionEntry : reactions.entrySet()) {
217 Optional<net.pterodactylus.rhynodge.State> state = stateManager.loadLastState(reactionEntry.getKey());
218 long stateTime = state.isPresent() ? state.get().time() : 0;
219 nextReactions.put(stateTime + reactionEntry.getValue().updateInterval(), Pair.of(reactionEntry.getKey(), reactionEntry.getValue()));
221 Pair<String, Reaction> keyReaction = nextReactions.get(nextReactions.firstKey());
222 return of(new NextReaction(keyReaction.getKey(), keyReaction.getValue(), nextReactions.firstKey()));
228 private static class NextReaction {
230 private final String key;
231 private final Reaction reaction;
232 private final long nextTime;
234 private NextReaction(String key, Reaction reaction, long nextTime) {
236 this.reaction = reaction;
237 this.nextTime = nextTime;
240 public String getKey() {
244 public Reaction getReaction() {
248 public long getNextTime() {