Split the engine in smaller parts.
[rhynodge.git] / src / main / java / net / pterodactylus / rhynodge / engine / ReactionRunner.java
1 package net.pterodactylus.rhynodge.engine;
2
3 import static java.lang.String.format;
4 import static java.util.Optional.ofNullable;
5 import static net.pterodactylus.rhynodge.states.FailedState.INSTANCE;
6 import static org.apache.log4j.Logger.getLogger;
7
8 import java.util.Optional;
9
10 import net.pterodactylus.rhynodge.Action;
11 import net.pterodactylus.rhynodge.Filter;
12 import net.pterodactylus.rhynodge.Query;
13 import net.pterodactylus.rhynodge.Reaction;
14 import net.pterodactylus.rhynodge.State;
15 import net.pterodactylus.rhynodge.Trigger;
16 import net.pterodactylus.rhynodge.states.FailedState;
17
18 import org.apache.log4j.Logger;
19
20 /**
21  * Runs a {@link Reaction}, starting with its {@link Query}, running the {@link
22  * State} through its {@link Filter}s, and finally checking the {@link Trigger}
23  * for whether an {@link Action} needs to be executed.
24  *
25  * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
26  */
27 public class ReactionRunner implements Runnable {
28
29         private static final Logger logger = getLogger(ReactionRunner.class);
30         private final Reaction reaction;
31         private final ReactionState reactionState;
32
33         public ReactionRunner(Reaction reaction, ReactionState reactionState) {
34                 this.reactionState = reactionState;
35                 this.reaction = reaction;
36         }
37
38         @Override
39         public void run() {
40                 State state = runQuery();
41                 state = runStateThroughFilters(state);
42                 if (!state.success()) {
43                         logger.info(format("Reaction %s failed.", reaction.name()));
44                         saveStateWithIncreasedFailCount(state);
45                         return;
46                 }
47                 Optional<State> lastSuccessfulState = reactionState.loadLastSuccessfulState();
48                 if (!lastSuccessfulState.isPresent()) {
49                         logger.info(format("No last state for %s.", reaction.name()));
50                         reactionState.saveState(state);
51                         return;
52                 }
53                 Trigger trigger = reaction.trigger();
54                 State newState = trigger.mergeStates(lastSuccessfulState.get(), state);
55                 reactionState.saveState(newState);
56                 if (trigger.triggers()) {
57                         logger.info(format("Trigger was hit for %s, executing action...", reaction.name()));
58                         reaction.action().execute(trigger.output(reaction));
59                 }
60                 logger.info(format("Reaction %s finished.", reaction.name()));
61         }
62
63         private void saveStateWithIncreasedFailCount(State state) {
64                 Optional<State> lastState = reactionState.loadLastState();
65                 state.setFailCount(lastState.map(State::failCount).orElse(0) + 1);
66                 reactionState.saveState(state);
67         }
68
69         private State runQuery() {
70                 logger.info(format("Querying %s...", reaction.name()));
71                 try {
72                         return ofNullable(reaction.query().state()).orElse(INSTANCE);
73                 } catch (Throwable t1) {
74                         logger.warn(format("Could not query %s.", reaction.name()), t1);
75                         return new FailedState(t1);
76                 }
77         }
78
79         private State runStateThroughFilters(State state) {
80                 State currentState = state;
81                 for (Filter filter : reaction.filters()) {
82                         if (currentState.success()) {
83                                 logger.debug(format("Filtering state through %s...", filter.getClass().getSimpleName()));
84                                 try {
85                                         currentState = filter.filter(currentState);
86                                 } catch (Throwable t1) {
87                                         logger.warn(format("Error during filter %s for %s.", filter.getClass().getSimpleName(), reaction.name()), t1);
88                                 }
89                         }
90                 }
91                 return currentState;
92         }
93
94 }