From c3d1babcce7f077c2b57376fa7f9712b921950d8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Mon, 4 May 2015 19:10:23 +0200 Subject: [PATCH] Send an email if a reaction fails with an exception --- .../rhynodge/engine/Configuration.java | 41 ++++++++++++++++++++++ .../net/pterodactylus/rhynodge/engine/Engine.java | 13 +++---- .../rhynodge/engine/ReactionRunner.java | 37 ++++++++++++++++++- .../net/pterodactylus/rhynodge/engine/Starter.java | 22 ++++++++++-- .../rhynodge/engine/ConfigurationTest.java | 26 ++++++++++++++ .../rhynodge/engine/configuration.json | 5 +++ 6 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 src/main/java/net/pterodactylus/rhynodge/engine/Configuration.java create mode 100644 src/test/java/net/pterodactylus/rhynodge/engine/ConfigurationTest.java create mode 100644 src/test/resources/net/pterodactylus/rhynodge/engine/configuration.json diff --git a/src/main/java/net/pterodactylus/rhynodge/engine/Configuration.java b/src/main/java/net/pterodactylus/rhynodge/engine/Configuration.java new file mode 100644 index 0000000..9f1ca84 --- /dev/null +++ b/src/main/java/net/pterodactylus/rhynodge/engine/Configuration.java @@ -0,0 +1,41 @@ +package net.pterodactylus.rhynodge.engine; + +import java.io.IOException; +import java.io.InputStream; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Stores general configuration of Rhynodge. + * + * @author David ‘Bombe’ Roden + */ +public class Configuration { + + @JsonProperty + private final String smtpHostname = null; + + @JsonProperty + private String errorEmailSender = null; + + @JsonProperty + private String errorEmailRecipient = null; + + public String getSmtpHostname() { + return smtpHostname; + } + + public String getErrorEmailSender() { + return errorEmailSender; + } + + public String getErrorEmailRecipient() { + return errorEmailRecipient; + } + + public static Configuration from(InputStream inputStream) throws IOException { + return new ObjectMapper().readValue(inputStream, Configuration.class); + } + +} diff --git a/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java b/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java index 5c289c5..7103dc5 100644 --- a/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java +++ b/src/main/java/net/pterodactylus/rhynodge/engine/Engine.java @@ -29,6 +29,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import net.pterodactylus.rhynodge.Reaction; +import net.pterodactylus.rhynodge.actions.EmailAction; import net.pterodactylus.rhynodge.states.StateManager; /** @@ -41,15 +42,11 @@ public class Engine { private final StateManager stateManager; private final ScheduledExecutorService executorService; private final Map> scheduledFutures = new ConcurrentHashMap<>(); + private final EmailAction errorEmailAction; - /** - * Creates a new engine. - * - * @param stateManager - * The state manager - */ - public Engine(StateManager stateManager) { + public Engine(StateManager stateManager, EmailAction errorEmailAction) { this.stateManager = stateManager; + this.errorEmailAction = errorEmailAction; executorService = new ScheduledThreadPoolExecutor(10); } @@ -70,7 +67,7 @@ public class Engine { Optional lastState = reactionState.loadLastState(); long lastExecutionTime = lastState.map(net.pterodactylus.rhynodge.State::time).orElse(0L); long nextExecutionTime = lastExecutionTime + reaction.updateInterval(); - ReactionRunner reactionRunner = new ReactionRunner(reaction, reactionState); + ReactionRunner reactionRunner = new ReactionRunner(reaction, reactionState, errorEmailAction); ScheduledFuture future = executorService.scheduleWithFixedDelay(reactionRunner, nextExecutionTime - currentTimeMillis(), reaction.updateInterval(), MILLISECONDS); scheduledFutures.put(name, future); } diff --git a/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java b/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java index ac25e18..c9af294 100644 --- a/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java +++ b/src/main/java/net/pterodactylus/rhynodge/engine/ReactionRunner.java @@ -5,6 +5,9 @@ import static java.util.Optional.ofNullable; import static net.pterodactylus.rhynodge.states.FailedState.INSTANCE; import static org.apache.log4j.Logger.getLogger; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Optional; import net.pterodactylus.rhynodge.Action; @@ -13,6 +16,9 @@ import net.pterodactylus.rhynodge.Query; import net.pterodactylus.rhynodge.Reaction; import net.pterodactylus.rhynodge.State; import net.pterodactylus.rhynodge.Trigger; +import net.pterodactylus.rhynodge.actions.EmailAction; +import net.pterodactylus.rhynodge.output.DefaultOutput; +import net.pterodactylus.rhynodge.output.Output; import net.pterodactylus.rhynodge.states.FailedState; import org.apache.log4j.Logger; @@ -29,10 +35,12 @@ public class ReactionRunner implements Runnable { private static final Logger logger = getLogger(ReactionRunner.class); private final Reaction reaction; private final ReactionState reactionState; + private final EmailAction errorEmailAction; - public ReactionRunner(Reaction reaction, ReactionState reactionState) { + public ReactionRunner(Reaction reaction, ReactionState reactionState, EmailAction errorEmailAction) { this.reactionState = reactionState; this.reaction = reaction; + this.errorEmailAction = errorEmailAction; } @Override @@ -42,6 +50,7 @@ public class ReactionRunner implements Runnable { if (!state.success()) { logger.info(format("Reaction %s failed.", reaction.name())); saveStateWithIncreasedFailCount(state); + errorEmailAction.execute(createErrorOutput(reaction, state)); return; } Optional lastSuccessfulState = reactionState.loadLastSuccessfulState(); @@ -66,6 +75,32 @@ public class ReactionRunner implements Runnable { reactionState.saveState(state); } + private Output createErrorOutput(Reaction reaction, State state) { + DefaultOutput output = new DefaultOutput(String.format("Error while processing “%s!”", reaction.name())); + output.addText("text/plain; charset=utf-8", createEmailText(reaction, state)); + return output; + } + + private String createEmailText(Reaction reaction, State state) { + StringBuilder emailText = new StringBuilder(); + emailText.append(String.format("An error occured while processing “.”\n\n", reaction.name())); + appendExceptionToEmailText(state.exception(), emailText); + return emailText.toString(); + } + + private void appendExceptionToEmailText(Throwable exception, StringBuilder emailText) { + if (exception != null) { + try (StringWriter stringWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(stringWriter)) { + exception.printStackTrace(printWriter); + emailText.append(stringWriter.toString()); + } catch (IOException ioe1) { + /* StringWriter doesn’t throw. */ + throw new RuntimeException(ioe1); + } + } + } + private State runQuery() { logger.info(format("Querying %s...", reaction.name())); try { diff --git a/src/main/java/net/pterodactylus/rhynodge/engine/Starter.java b/src/main/java/net/pterodactylus/rhynodge/engine/Starter.java index 4c3b4db..05aff64 100644 --- a/src/main/java/net/pterodactylus/rhynodge/engine/Starter.java +++ b/src/main/java/net/pterodactylus/rhynodge/engine/Starter.java @@ -17,6 +17,10 @@ package net.pterodactylus.rhynodge.engine; +import java.io.FileInputStream; +import java.io.IOException; + +import net.pterodactylus.rhynodge.actions.EmailAction; import net.pterodactylus.rhynodge.loader.ChainWatcher; import net.pterodactylus.rhynodge.states.StateManager; @@ -36,22 +40,33 @@ public class Starter { * @param arguments * Command-line arguments */ - public static void main(String... arguments) { + public static void main(String... arguments) throws IOException { /* parse command line. */ Parameters parameters = CliFactory.parseArguments(Parameters.class, arguments); + Configuration configuration = loadConfiguration(parameters.getConfigurationFile()); /* create the state manager. */ StateManager stateManager = new StateManager(parameters.getStateDirectory()); /* create the engine. */ - Engine engine = new Engine(stateManager); + Engine engine = new Engine(stateManager, createErrorEmailAction(configuration)); /* start a watcher. */ ChainWatcher chainWatcher = new ChainWatcher(engine, parameters.getChainDirectory()); chainWatcher.start(); } + private static Configuration loadConfiguration(String configurationFile) throws IOException { + try (FileInputStream configInputStream = new FileInputStream(configurationFile)) { + return Configuration.from(configInputStream); + } + } + + private static EmailAction createErrorEmailAction(Configuration configuration) { + return new EmailAction(configuration.getSmtpHostname(), configuration.getErrorEmailSender(), configuration.getErrorEmailRecipient()); + } + /** * Definition of the command-line parameters. * @@ -75,6 +90,9 @@ public class Starter { @Option(defaultValue = "states", longName = "states", shortName = "s", description = "The directory to store states in") String getStateDirectory(); + @Option(defaultValue = "/etc/rhynodge/rhynodge.json", longName = "config", shortName = "C", description = "The name of the configuration file") + String getConfigurationFile(); + } } diff --git a/src/test/java/net/pterodactylus/rhynodge/engine/ConfigurationTest.java b/src/test/java/net/pterodactylus/rhynodge/engine/ConfigurationTest.java new file mode 100644 index 0000000..6a497e0 --- /dev/null +++ b/src/test/java/net/pterodactylus/rhynodge/engine/ConfigurationTest.java @@ -0,0 +1,26 @@ +package net.pterodactylus.rhynodge.engine; + +import java.io.IOException; +import java.io.InputStream; + +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Unit test for {@link Configuration}. + * + * @author David ‘Bombe’ Roden + */ +public class ConfigurationTest { + + @Test + public void configurationCanBeReadFromJsonFile() throws IOException { + InputStream inputStream = getClass().getResourceAsStream("configuration.json"); + Configuration configuration = Configuration.from(inputStream); + MatcherAssert.assertThat(configuration.getSmtpHostname(), Matchers.is("localhost")); + MatcherAssert.assertThat(configuration.getErrorEmailSender(), Matchers.is("errors@rhynodge.net")); + MatcherAssert.assertThat(configuration.getErrorEmailRecipient(), Matchers.is("errors@user.net")); + } + +} diff --git a/src/test/resources/net/pterodactylus/rhynodge/engine/configuration.json b/src/test/resources/net/pterodactylus/rhynodge/engine/configuration.json new file mode 100644 index 0000000..799a773 --- /dev/null +++ b/src/test/resources/net/pterodactylus/rhynodge/engine/configuration.json @@ -0,0 +1,5 @@ +{ + "smtpHostname": "localhost", + "errorEmailSender": "errors@rhynodge.net", + "errorEmailRecipient": "errors@user.net" +} -- 2.7.4