1 package net.pterodactylus.rhynodge.engine;
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;
8 import java.io.IOException;
9 import java.io.PrintWriter;
10 import java.io.StringWriter;
11 import java.util.Optional;
13 import net.pterodactylus.rhynodge.Action;
14 import net.pterodactylus.rhynodge.Filter;
15 import net.pterodactylus.rhynodge.Query;
16 import net.pterodactylus.rhynodge.Reaction;
17 import net.pterodactylus.rhynodge.State;
18 import net.pterodactylus.rhynodge.actions.EmailAction;
19 import net.pterodactylus.rhynodge.Merger;
20 import net.pterodactylus.rhynodge.output.DefaultOutput;
21 import net.pterodactylus.rhynodge.output.Output;
22 import net.pterodactylus.rhynodge.states.FailedState;
24 import org.apache.log4j.Logger;
27 * Runs a {@link Reaction}, starting with its {@link Query}, running the {@link
28 * State} through its {@link Filter}s, and finally checking the {@link Merger}
29 * for whether an {@link Action} needs to be executed.
31 * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
33 public class ReactionRunner implements Runnable {
35 private static final Logger logger = getLogger(ReactionRunner.class);
36 private final Reaction reaction;
37 private final ReactionState reactionState;
38 private final EmailAction errorEmailAction;
40 public ReactionRunner(Reaction reaction, ReactionState reactionState, EmailAction errorEmailAction) {
41 this.reactionState = reactionState;
42 this.reaction = reaction;
43 this.errorEmailAction = errorEmailAction;
48 State state = runQuery();
49 state = runStateThroughFilters(state);
50 if (!state.success()) {
51 logger.info(format("Reaction %s failed in %s.", reaction.name(), state));
52 saveStateWithIncreasedFailCount(state);
53 errorEmailAction.execute(createErrorOutput(reaction, state));
56 Optional<State> lastSuccessfulState = reactionState.loadLastSuccessfulState();
57 if (!lastSuccessfulState.isPresent()) {
58 logger.info(format("No last state for %s.", reaction.name()));
59 reactionState.saveState(state);
62 Merger merger = reaction.merger();
63 State newState = merger.mergeStates(lastSuccessfulState.get(), state);
64 reactionState.saveState(newState);
65 if (newState.triggered()) {
66 logger.info(format("Trigger was hit for %s, executing action...", reaction.name()));
67 reaction.action().execute(newState.output(reaction));
69 logger.info(format("Reaction %s finished.", reaction.name()));
72 private void saveStateWithIncreasedFailCount(State state) {
73 Optional<State> lastState = reactionState.loadLastState();
74 state.setFailCount(lastState.map(State::failCount).orElse(0) + 1);
75 reactionState.saveState(state);
78 private Output createErrorOutput(Reaction reaction, State state) {
79 DefaultOutput output = new DefaultOutput(String.format("Error while processing “%s!”", reaction.name()));
80 output.addText("text/plain", createErrorEmailText(reaction, state));
81 output.addText("text/html", createErrorEmailText(reaction, state));
85 private String createErrorEmailText(Reaction reaction, State state) {
86 StringBuilder emailText = new StringBuilder();
87 emailText.append(String.format("An error occured while processing “%s.”\n\n", reaction.name()));
88 appendExceptionToEmailText(state.exception(), emailText);
89 return emailText.toString();
92 private void appendExceptionToEmailText(Throwable exception, StringBuilder emailText) {
93 if (exception != null) {
94 try (StringWriter stringWriter = new StringWriter();
95 PrintWriter printWriter = new PrintWriter(stringWriter)) {
96 exception.printStackTrace(printWriter);
97 emailText.append(stringWriter.toString());
98 } catch (IOException ioe1) {
99 /* StringWriter doesn’t throw. */
100 throw new RuntimeException(ioe1);
105 private State runQuery() {
106 logger.info(format("Querying %s...", reaction.name()));
108 return ofNullable(reaction.query().state()).orElse(INSTANCE);
109 } catch (Throwable t1) {
110 logger.warn(format("Could not query %s.", reaction.name()), t1);
111 return new FailedState(t1);
115 private State runStateThroughFilters(State state) {
116 State currentState = state;
117 for (Filter filter : reaction.filters()) {
118 if (currentState.success()) {
119 logger.debug(format("Filtering state through %s...", filter.getClass().getSimpleName()));
121 currentState = filter.filter(currentState);
122 } catch (Throwable t1) {
123 logger.warn(format("Error during filter %s for %s.", filter.getClass().getSimpleName(), reaction.name()), t1);
124 return new FailedState(t1);