From 5d5a53385b865329f83a928a5e8b7e0f6367452a Mon Sep 17 00:00:00 2001 From: "Matt Pearce (OSC)" Date: Wed, 14 Nov 2018 10:13:37 +0000 Subject: [PATCH 01/10] Add *.iml files to git ignore list. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 88941c7b..4129ead2 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ target bin .idea .iml +*.iml From 926bcd0dc658b73cb861d9587463090ef714f71a Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Tue, 20 Nov 2018 11:56:23 +0000 Subject: [PATCH 02/10] Implement basic persistence framework definition. --- pom.xml | 1 + .../persistence/PersistenceConfiguration.java | 89 +++++++++++++++++++ .../rre/persistence/PersistenceHandler.java | 51 +++++++++++ .../rre/persistence/PersistenceManager.java | 49 ++++++++++ rre-maven-plugin/pom.xml | 2 +- .../plugin/elasticsearch/RREvaluateMojo.java | 11 +++ .../elasticsearch/RREvaluateMojoTest.java | 61 +++++++++++++ .../persistence/no_persistence_pom.xml | 41 +++++++++ .../resources/persistence/persistence_pom.xml | 51 +++++++++++ 9 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java create mode 100644 rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java create mode 100644 rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java create mode 100644 rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojoTest.java create mode 100644 rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/no_persistence_pom.xml create mode 100644 rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/persistence_pom.xml diff --git a/pom.xml b/pom.xml index c0b0c352..cc2e0d41 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ 1.8 2.9.4 github + UTF-8 diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java new file mode 100644 index 00000000..42a526d1 --- /dev/null +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java @@ -0,0 +1,89 @@ +package io.sease.rre.persistence; + +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration details for the persistence manager. + *

+ * Configuration consists of a set of handler names, mapped to their + * implementation classes. The handler names are then used to refer to + * specific sets of configuration details, allowing the same implementation + * to be used multiple times with separate destinations (for example). + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class PersistenceConfiguration { + + /** + * Default configuration object, with configuration for persisting to + * a single JSON output file. + */ + public static final PersistenceConfiguration DEFAULT_CONFIG = defaultConfiguration(); + + private boolean useTimestampAsVersion = false; + private Map handlers; + // Supplying type params for nested map breaks Maven initialisation + private Map handlerConfiguration; + + public PersistenceConfiguration() { + // Do nothing - required for Maven initialisation + } + + PersistenceConfiguration(boolean useTimestampAsVersion, + Map handlers, + Map handlerConfiguration) { + this.useTimestampAsVersion = useTimestampAsVersion; + this.handlers = handlers; + this.handlerConfiguration = handlerConfiguration; + } + + /** + * Should the persistence framework use a timestamp in place of the + * configuration version? This timestamp should be consistent for + * the duration of the evaluation. + *

+ * This will take effect when there is only one configuration set + * available - eg. for users who modify the same configuration set + * rather than creating a separate one each iteration. + * + * @return {@code true} if a timestamp should be used in place of the + * version string when persisting query output. + */ + public boolean isUseTimestampAsVersion() { + return useTimestampAsVersion; + } + + /** + * @return a map of handler name to implementation classes. + */ + public Map getHandlers() { + return handlers; + } + + /** + * @return a map of handler name to a map containing configuration + * details for the handler. + */ + public Map getHandlerConfiguration() { + return handlerConfiguration; + } + + /** + * Build a default PersistenceConfiguration, with handlers set to write + * to the standard JSON output file. + * + * @return a PersistenceConfiguration object. + */ + private static PersistenceConfiguration defaultConfiguration() { + final String jsonKey = "json"; + Map handlers = new HashMap<>(); + handlers.put(jsonKey, "io.sease.rre.persistence.impl.JsonPersistenceHandler"); + Map jsonConfig = new HashMap<>(); + jsonConfig.put("outputFile", "target/rre/evaluation.json"); + Map handlerConfig = new HashMap<>(); + handlerConfig.put(jsonKey, jsonConfig); + + return new PersistenceConfiguration(false, handlers, handlerConfig); + } +} diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java new file mode 100644 index 00000000..38948737 --- /dev/null +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java @@ -0,0 +1,51 @@ +package io.sease.rre.persistence; + +import io.sease.rre.core.domain.Query; + +import java.util.Map; + +/** + * A persistence handler can be used to record the output from all queries + * run during an evaluation. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public interface PersistenceHandler { + + /** + * Configure the persistence handler implementation. + * + * @param name the name given to the handler in the main configuration. + * @param configuration the configuration parameters specific to the + * handler. + */ + void configure(String name, Map configuration); + + /** + * Execute any necessary tasks required to initialise the handler. + */ + void beforeStart(); + + /** + * Execute any necessary start-up tasks. + */ + void start(); + + /** + * Record a query. + * @param q the query. + */ + void recordQuery(Query q); + + /** + * Execute any tasks necessary before stopping - for example, writing out + * buffered content. + */ + void beforeStop(); + + /** + * Execute any necessary shutdown tasks - for example, closing output + * streams. + */ + void stop(); +} diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java new file mode 100644 index 00000000..569c1a9f --- /dev/null +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java @@ -0,0 +1,49 @@ +package io.sease.rre.persistence; + +import io.sease.rre.core.domain.Query; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * The general manager class for all persistence handlers. This provides + * methods for starting, processing, and shutting down a number of handlers + * via single method calls. + *

+ * Persistence handlers should be constructed outside the manager, including + * any required initialisation via the {@link PersistenceHandler#configure(String, Map)} + * method, then registered using {@link #registerHandler(PersistenceHandler)}. + *

+ * Most other methods apply to all registered handlers. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class PersistenceManager { + + private final List handlers = new ArrayList<>(); + + public void registerHandler(PersistenceHandler handler) { + handlers.add(handler); + } + + public void beforeStart() { + handlers.parallelStream().forEach(PersistenceHandler::beforeStart); + } + + public void start() { + handlers.parallelStream().forEach(PersistenceHandler::start); + } + + public void recordQuery(Query query) { + handlers.parallelStream().forEach(h -> h.recordQuery(query)); + } + + public void beforeStop() { + handlers.parallelStream().forEach(PersistenceHandler::beforeStop); + } + + public void stop() { + handlers.parallelStream().forEach(PersistenceHandler::stop); + } +} diff --git a/rre-maven-plugin/pom.xml b/rre-maven-plugin/pom.xml index 30f3d5dd..478b4dbe 100644 --- a/rre-maven-plugin/pom.xml +++ b/rre-maven-plugin/pom.xml @@ -83,7 +83,7 @@ junit junit - 3.8.1 + 4.12 test diff --git a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java index 889b8279..ec0d8063 100644 --- a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java +++ b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.sease.rre.core.Engine; import io.sease.rre.core.domain.Evaluation; +import io.sease.rre.persistence.PersistenceConfiguration; import io.sease.rre.search.api.SearchPlatform; import io.sease.rre.search.api.impl.Elasticsearch; import org.apache.maven.plugin.AbstractMojo; @@ -19,6 +20,8 @@ import java.util.List; import java.util.Map; +import static java.util.Optional.ofNullable; + /** * RREvalutation Mojo (Apache Solr binding). * @@ -61,6 +64,9 @@ public class RREvaluateMojo extends AbstractMojo { @Parameter(name = "port", defaultValue = "9200") private int port; + @Parameter(name = "persistence") + private PersistenceConfiguration persistence = PersistenceConfiguration.DEFAULT_CONFIG; + @Override public void execute() throws MojoExecutionException { final URL [] urls = compilePaths.stream() @@ -114,4 +120,9 @@ private void write(final Evaluation evaluation) throws IOException { final ObjectMapper mapper = new ObjectMapper(); mapper.writerWithDefaultPrettyPrinter().writeValue(new File(outputFolder, "evaluation.json"), evaluation); } + + PersistenceConfiguration getPersistence() { + return persistence; +// return ofNullable(persistence).orElse(PersistenceConfiguration.defaultConfiguration()); + } } \ No newline at end of file diff --git a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojoTest.java b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojoTest.java new file mode 100644 index 00000000..4c63b8e9 --- /dev/null +++ b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojoTest.java @@ -0,0 +1,61 @@ +package io.sease.rre.maven.plugin.elasticsearch; + +import io.sease.rre.persistence.PersistenceConfiguration; +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.testing.MojoRule; +import org.junit.Rule; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Basic configuration tests for the ES Evaluate plugin. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class RREvaluateMojoTest { + + @Rule + public MojoRule rule = new MojoRule() + { + @Override + protected void before() throws Throwable + { + } + + @Override + protected void after() + { + } + }; + + @Test + public void testBuildsDefaultPersistenceConfig_whenNoPersistenceConfigInPom() throws Exception { + Mojo mojo = rule.lookupMojo("evaluate", "src/test/resources/persistence/no_persistence_pom.xml"); + assertNotNull(mojo); + + RREvaluateMojo rreMojo = (RREvaluateMojo) mojo; + PersistenceConfiguration persist = rreMojo.getPersistence(); + assertNotNull(persist); + assertFalse(persist.isUseTimestampAsVersion()); + assertEquals(persist.getHandlers().size(), 1); + assertTrue(persist.getHandlers().containsKey("json")); + assertTrue(persist.getHandlerConfiguration().containsKey("json")); + } + + @Test + public void testBuildsCorrectPersistenceConfig_whenPersistenceConfigInPom() throws Exception { + Mojo mojo = rule.lookupMojo("evaluate", "src/test/resources/persistence/persistence_pom.xml"); + assertNotNull(mojo); + + RREvaluateMojo rreMojo = (RREvaluateMojo) mojo; + PersistenceConfiguration persist = rreMojo.getPersistence(); + assertNotNull(persist); + assertTrue(persist.isUseTimestampAsVersion()); + assertEquals(persist.getHandlers().size(), 1); + assertTrue(persist.getHandlers().containsKey("testJson")); + assertTrue(persist.getHandlerConfiguration().containsKey("testJson")); + assertNotNull(persist.getHandlerConfiguration().get("testJson")); + assertEquals(persist.getHandlerConfiguration().get("testJson").get("destinationFile"), "blah.txt"); + } +} diff --git a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/no_persistence_pom.xml b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/no_persistence_pom.xml new file mode 100644 index 00000000..a4a248c7 --- /dev/null +++ b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/no_persistence_pom.xml @@ -0,0 +1,41 @@ + + 4.0.0 + io.sease + rre-maven-elasticsearch-plugin + 6.3.2 + pom + + + 1.8 + 1.8 + + + + + sease + https://raw.github.com/SeaseLtd/rated-ranking-evaluator/mvn-repo + + + + + + io.sease + rre-maven-elasticsearch-plugin + ${esVersion} + + + + + + search-quality-evaluation + package + + evaluate + + + + + + + diff --git a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/persistence_pom.xml b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/persistence_pom.xml new file mode 100644 index 00000000..96b50850 --- /dev/null +++ b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/test/resources/persistence/persistence_pom.xml @@ -0,0 +1,51 @@ + + 4.0.0 + io.sease + rre-maven-elasticsearch-plugin + 6.3.2 + pom + + + 1.8 + 1.8 + + + + + sease + https://raw.github.com/SeaseLtd/rated-ranking-evaluator/mvn-repo + + + + + + io.sease + rre-maven-elasticsearch-plugin + ${esVersion} + + + true + + io.sease.rre.persistence.impl.JsonPersistenceHandler + + + + blah.txt + + + + + + + search-quality-evaluation + package + + evaluate + + + + + + + From edea16c5123a6995ed003264900019390bf29387 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Tue, 20 Nov 2018 16:08:44 +0000 Subject: [PATCH 03/10] Add JsonPersistenceHandler implementation. --- .../sease/rre/core/domain/DomainMember.java | 6 + .../rre/persistence/PersistenceException.java | 25 ++++ .../rre/persistence/PersistenceHandler.java | 22 +++- .../rre/persistence/PersistenceManager.java | 35 ++++- .../impl/JsonPersistenceHandler.java | 123 ++++++++++++++++++ .../persistence/PersistenceManagerTest.java | 72 ++++++++++ .../impl/JsonPersistenceHandlerTest.java | 83 ++++++++++++ 7 files changed, 361 insertions(+), 5 deletions(-) create mode 100644 rre-core/src/main/java/io/sease/rre/persistence/PersistenceException.java create mode 100644 rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java create mode 100644 rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java create mode 100644 rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java diff --git a/rre-core/src/main/java/io/sease/rre/core/domain/DomainMember.java b/rre-core/src/main/java/io/sease/rre/core/domain/DomainMember.java index 6413ee07..ad19e162 100644 --- a/rre-core/src/main/java/io/sease/rre/core/domain/DomainMember.java +++ b/rre-core/src/main/java/io/sease/rre/core/domain/DomainMember.java @@ -1,5 +1,6 @@ package io.sease.rre.core.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.sease.rre.core.domain.metrics.Metric; import io.sease.rre.core.domain.metrics.impl.AveragedMetric; @@ -124,4 +125,9 @@ public void notifyCollectedMetrics() { public Map getMetrics() { return metrics; } + + @JsonIgnore + public Optional getParent() { + return ofNullable(parent); + } } \ No newline at end of file diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceException.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceException.java new file mode 100644 index 00000000..5eb275c0 --- /dev/null +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceException.java @@ -0,0 +1,25 @@ +package io.sease.rre.persistence; + +/** + * Exception thrown by the persistence framework. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class PersistenceException extends Exception { + + public PersistenceException() { + super(); + } + + public PersistenceException(String message) { + super(message); + } + + public PersistenceException(String message, Throwable cause) { + super(message, cause); + } + + public PersistenceException(Throwable cause) { + super(cause); + } +} diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java index 38948737..03a9362a 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceHandler.java @@ -7,6 +7,11 @@ /** * A persistence handler can be used to record the output from all queries * run during an evaluation. + *

+ * Exceptions during processing are expected to be handled internally. + * Exceptions thrown during the {@link #beforeStart()} and {@link #start()} + * stages may be passed up the stack, to alert the PersistenceManager that + * the handler could not be started for some reason. * * @author Matt Pearce (matt@flax.co.uk) */ @@ -21,18 +26,31 @@ public interface PersistenceHandler { */ void configure(String name, Map configuration); + /** + * @return the configuration name for this handler instance. + */ + String getName(); + /** * Execute any necessary tasks required to initialise the handler. + * + * @throws PersistenceException if any pre-start checks fail. This should + * be used to report breaking failures - eg. issues that will stop the + * handler from starting. */ - void beforeStart(); + void beforeStart() throws PersistenceException; /** * Execute any necessary start-up tasks. + * + * @throws PersistenceException if the handler cannot be started for any + * reason. */ - void start(); + void start() throws PersistenceException; /** * Record a query. + * * @param q the query. */ void recordQuery(Query q); diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java index 569c1a9f..babbdebf 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java @@ -1,6 +1,8 @@ package io.sease.rre.persistence; import io.sease.rre.core.domain.Query; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import java.util.ArrayList; import java.util.List; @@ -21,18 +23,45 @@ */ public class PersistenceManager { + private static final Logger LOGGER = LogManager.getLogger(PersistenceManager.class); + private final List handlers = new ArrayList<>(); public void registerHandler(PersistenceHandler handler) { handlers.add(handler); } - public void beforeStart() { - handlers.parallelStream().forEach(PersistenceHandler::beforeStart); + public void beforeStart() throws PersistenceException { + handlers.parallelStream().forEach(h -> { + try { + h.beforeStart(); + } catch (PersistenceException e) { + LOGGER.error("beforeStart failed for handler [" + h.getName() + "] :: " + e.getMessage()); + handlers.remove(h); + } + }); + + // Check that there are handlers available + checkHandlers(); } public void start() { - handlers.parallelStream().forEach(PersistenceHandler::start); + handlers.parallelStream().forEach(h -> { + try { + h.start(); + } catch (PersistenceException e) { + LOGGER.error("[" + h.getName() + "] failed to start :: " + e.getMessage()); + handlers.remove(h); + } + }); + + checkHandlers(); + } + + private void checkHandlers() { + if (handlers.size() == 0) { + throw new RuntimeException("No running persistence handlers!"); + } } public void recordQuery(Query query) { diff --git a/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java b/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java new file mode 100644 index 00000000..f206d6be --- /dev/null +++ b/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java @@ -0,0 +1,123 @@ +package io.sease.rre.persistence.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import io.sease.rre.core.domain.DomainMember; +import io.sease.rre.core.domain.Evaluation; +import io.sease.rre.core.domain.Query; +import io.sease.rre.persistence.PersistenceException; +import io.sease.rre.persistence.PersistenceHandler; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * JSON implementation of the {@link PersistenceHandler} interface. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class JsonPersistenceHandler implements PersistenceHandler { + + static final String DESTINATION_FILE_CONFIGKEY = "destinationFile"; + static final String PRETTY_CONFIGKEY = "pretty"; + + private static final String DEFAULT_OUTPUT_FILE = "target/rre/evaluation.json"; + + private static final Logger LOGGER = LogManager.getLogger(JsonPersistenceHandler.class); + + private String name; + private String outputFilepath; + private boolean pretty; + + private List queries = new ArrayList<>(); + + @Override + public void configure(String name, Map configuration) { + this.name = name; + this.outputFilepath = configuration.getOrDefault(DESTINATION_FILE_CONFIGKEY, DEFAULT_OUTPUT_FILE).toString(); + this.pretty = Boolean.valueOf(configuration.getOrDefault(PRETTY_CONFIGKEY, "false").toString()); + } + + @Override + public String getName() { + return name; + } + + @Override + public void beforeStart() throws PersistenceException { + // Delete the output file if it already exists + File outFile = new File(outputFilepath); + if (outFile.exists()) { + try { + Files.delete(outFile.toPath()); + } catch (IOException e) { + throw new PersistenceException("Cannot delete pre-existing output file " + outputFilepath, e); + } + } + + try (FileOutputStream fos = new FileOutputStream(outFile)) { + fos.write(new byte[0]); + } catch (IOException e) { + throw new PersistenceException("Cannot write to output file " + outputFilepath, e); + } + } + + @Override + public void start() { + // Nothing to start + } + + @Override + public void recordQuery(Query q) { + queries.add(q); + } + + @Override + public void beforeStop() { + // Collect the metrics for all of the queries + queries.forEach(Query::notifyCollectedMetrics); + + // Retrieve the top level item + DomainMember topLevel = findTopLevel(); + try { + // Write out the JSON object + ObjectMapper mapper = new ObjectMapper(); + ObjectWriter writer = (pretty ? mapper.writerWithDefaultPrettyPrinter() : mapper.writer()); + writer.writeValue(new File(outputFilepath), topLevel); + } catch (IOException e) { + LOGGER.error("Caught IOException writing queries to JSON :: " + e.getMessage()); + } + } + + private Optional> retrieveParent(DomainMember dm) { + if (dm.getParent().isPresent()) { + return retrieveParent(dm.getParent().get()); + } else { + return Optional.of(dm); + } + } + + private DomainMember findTopLevel() { + if (queries.size() > 0) { + // Get the first query, then iterate through the parents to the top + return retrieveParent(queries.get(0)).orElse(new Evaluation()); + } else { + // No queries - return an empty Evaluation object + LOGGER.warn("No queries recorded - returning empty evaluation"); + return new Evaluation(); + } + } + + @Override + public void stop() { + // Nothing to stop + } +} diff --git a/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java b/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java new file mode 100644 index 00000000..0f9f1956 --- /dev/null +++ b/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java @@ -0,0 +1,72 @@ +package io.sease.rre.persistence; + +import io.sease.rre.core.domain.Query; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; + +/** + * Unit tests for the PersistenceManager class. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class PersistenceManagerTest { + + private PersistenceManager persistenceManager; + + @Before + public void setupManager() { + this.persistenceManager = new PersistenceManager(); + } + + @After + public void tearDownManager() { + this.persistenceManager = null; + } + + @Test(expected = RuntimeException.class) + public void managerThrowsRuntimeException_whenAllHandlersFailBeforeStart() throws Exception { + persistenceManager.registerHandler(failingHandler); + persistenceManager.beforeStart(); + } + + @Test(expected = RuntimeException.class) + public void managerThrowsRuntimeException_whenAllHandlersFailStart() throws Exception { + persistenceManager.registerHandler(failingHandler); + persistenceManager.start(); + } + + private PersistenceHandler failingHandler = new PersistenceHandler() { + @Override + public void configure(String name, Map configuration) { } + + @Override + public String getName() { + return "failingHandler"; + } + + @Override + public void beforeStart() throws PersistenceException { + throw new PersistenceException(); + } + + @Override + public void start() throws PersistenceException { + throw new PersistenceException(); + } + + @Override + public void recordQuery(Query q) { } + + @Override + public void beforeStop() { } + + @Override + public void stop() { } + }; + + + +} diff --git a/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java b/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java new file mode 100644 index 00000000..48be550b --- /dev/null +++ b/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java @@ -0,0 +1,83 @@ +package io.sease.rre.persistence.impl; + +import io.sease.rre.persistence.PersistenceException; +import io.sease.rre.persistence.PersistenceHandler; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Unit tests for the JSON PersistenceHandler implementation. + * + * @author Matt Pearce (matt@flax.co.uk) + */ +public class JsonPersistenceHandlerTest { + + private static final String HANDLER_NAME = "jsonTest"; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private PersistenceHandler handler; + + @Before + public void setupHandler() { + this.handler = new JsonPersistenceHandler(); + } + + @After + public void tearDownHandler() { + this.handler = null; + } + + @Test + public void beforeStartThrowsException_whenFileCannotBeDeleted() throws Exception { + File outFile = folder.newFile(); + outFile.setReadOnly(); + + Map config = new HashMap<>(); + config.put(JsonPersistenceHandler.DESTINATION_FILE_CONFIGKEY, outFile.getAbsolutePath()); + handler.configure(HANDLER_NAME, config); + + try { + handler.beforeStart(); + fail("Expected PersistenceException"); + } catch (PersistenceException e) { + // Expected behaviour + } + } + + @Test + public void beforeStartPasses_whenFileExists() throws Exception { + File outFile = folder.newFile(); + + Map config = new HashMap<>(); + config.put(JsonPersistenceHandler.DESTINATION_FILE_CONFIGKEY, outFile.getAbsolutePath()); + handler.configure(HANDLER_NAME, config); + + handler.beforeStart(); + } + + @Test + public void beforeStop_handlesEmptyQueryList() throws Exception { + File outFile = folder.newFile(); + Map config = new HashMap<>(); + config.put(JsonPersistenceHandler.DESTINATION_FILE_CONFIGKEY, outFile.getAbsolutePath()); + config.put(JsonPersistenceHandler.PRETTY_CONFIGKEY, true); + handler.configure(HANDLER_NAME, config); + + handler.beforeStop(); + + assertTrue(outFile.exists()); + assertTrue(outFile.length() != 0); + } +} From 91b44ec2772a3c56b898571f41459cc324fdd039 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Tue, 20 Nov 2018 16:09:51 +0000 Subject: [PATCH 04/10] Add persistence configuration to constructor. Add handling of timestamp as version number. --- .../main/java/io/sease/rre/core/Engine.java | 69 ++++++++++++++----- .../plugin/elasticsearch/RREvaluateMojo.java | 3 +- .../rre/maven/plugin/solr/RREvaluateMojo.java | 7 +- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/rre-core/src/main/java/io/sease/rre/core/Engine.java b/rre-core/src/main/java/io/sease/rre/core/Engine.java index 26cf1b0d..181a50d2 100644 --- a/rre-core/src/main/java/io/sease/rre/core/Engine.java +++ b/rre-core/src/main/java/io/sease/rre/core/Engine.java @@ -7,6 +7,8 @@ import io.sease.rre.Func; import io.sease.rre.core.domain.*; import io.sease.rre.core.domain.metrics.Metric; +import io.sease.rre.persistence.PersistenceConfiguration; +import io.sease.rre.persistence.PersistenceManager; import io.sease.rre.search.api.QueryOrSearchResponse; import io.sease.rre.search.api.SearchPlatform; import org.apache.logging.log4j.LogManager; @@ -14,10 +16,6 @@ import java.io.*; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -60,6 +58,10 @@ public class Engine { private ObjectMapper mapper = new ObjectMapper(); private List versions; + private String versionTimestamp = null; + + private final PersistenceManager persistenceManager; + private final PersistenceConfiguration persistenceConfiguration; /** * Builds a new {@link Engine} instance with the given data. @@ -69,6 +71,10 @@ public class Engine { * @param corporaFolderPath the corpora folder path. * @param ratingsFolderPath the ratings folder path. * @param templatesFolderPath the query templates folder path. + * @param metrics the list of metric classes to include in the output. + * @param fields the fields to retrieve with each result. + * @param exclude a list of folders to exclude when scanning the configuration folders. + * @param include a list of folders to include from the configuration folders. */ public Engine( final SearchPlatform platform, @@ -79,7 +85,8 @@ public Engine( final List metrics, final String[] fields, final List exclude, - final List include) { + final List include, + final PersistenceConfiguration persistenceConfiguration) { this.configurationsFolder = new File(configurationsFolderPath); this.corporaFolder = new File(corporaFolderPath); this.ratingsFolder = new File(ratingsFolderPath); @@ -95,6 +102,9 @@ public Engine( .map(Func::newMetricDefinition) .filter(Objects::nonNull) .collect(toList()); + + this.persistenceConfiguration = persistenceConfiguration; + this.persistenceManager = new PersistenceManager(); } public String name(final JsonNode node) { @@ -133,14 +143,14 @@ public Evaluation evaluate(final Map configuration) { LOGGER.info("RRE: Ratings Set processing starts"); final String indexName = - requireNonNull( - ratingsNode.get(INDEX_NAME), - "WARNING!!! \"" + INDEX_NAME + "\" attribute not found!").asText(); + requireNonNull( + ratingsNode.get(INDEX_NAME), + "WARNING!!! \"" + INDEX_NAME + "\" attribute not found!").asText(); final String idFieldName = - requireNonNull( - ratingsNode.get(ID_FIELD_NAME), - "WARNING!!! \"" + ID_FIELD_NAME + "\" attribute not found!") - .asText(DEFAULT_ID_FIELD_NAME); + requireNonNull( + ratingsNode.get(ID_FIELD_NAME), + "WARNING!!! \"" + ID_FIELD_NAME + "\" attribute not found!") + .asText(DEFAULT_ID_FIELD_NAME); final File data = data(ratingsNode); final String queryPlaceholder = ofNullable(ratingsNode.get("query_placeholder")).map(JsonNode::asText).orElse("$query"); @@ -194,8 +204,8 @@ public Evaluation evaluate(final Map configuration) { query(queryNode, sharedTemplate, version), fields, Math.max(10, relevantDocuments.size())); - queryEvaluation.setTotalHits(response.totalHits(), version); - response.hits().forEach(hit -> queryEvaluation.collect(hit, rank.getAndIncrement(), version)); + queryEvaluation.setTotalHits(response.totalHits(), persistVersion(version)); + response.hits().forEach(hit -> queryEvaluation.collect(hit, rank.getAndIncrement(), persistVersion(version))); }); }); }); @@ -307,7 +317,8 @@ private List availableMetrics( return metric; } catch (final Exception exception) { throw new IllegalArgumentException(exception); - }}) + } + }) .collect(toList()); } @@ -358,7 +369,7 @@ private String templateContent(final File file) { * @return the ratings / judgements for this evaluation suite. */ private Stream ratings() { - final File [] ratingsFiles = + final File[] ratingsFiles = requireNonNull( ratingsFolder.listFiles(ONLY_JSON_FILES), "Unable to find the ratings folder."); @@ -390,8 +401,8 @@ private void prepareData(final String indexName, final File data) { final File[] versionFolders = safe(configurationsFolder.listFiles( file -> ONLY_DIRECTORIES.accept(file) - && (include.isEmpty() || include.contains(file.getName()) || include.stream().anyMatch(rule -> file.getName().matches(rule))) - && (exclude.isEmpty() || (!exclude.contains(file.getName()) && exclude.stream().noneMatch(rule -> file.getName().matches(rule)))))); + && (include.isEmpty() || include.contains(file.getName()) || include.stream().anyMatch(rule -> file.getName().matches(rule))) + && (exclude.isEmpty() || (!exclude.contains(file.getName()) && exclude.stream().noneMatch(rule -> file.getName().matches(rule)))))); if (versionFolders == null || versionFolders.length == 0) { throw new IllegalArgumentException("RRE: no target versions available. Check the configuration set folder and include/exclude clauses."); @@ -411,6 +422,15 @@ private void prepareData(final String indexName, final File data) { .map(File::getName) .collect(toList()); + if (persistenceConfiguration.isUseTimestampAsVersion()) { + if (versions.size() == 1) { + versionTimestamp = String.valueOf(System.currentTimeMillis()); + LOGGER.info("Using local system timestamp as version tag : " + versionTimestamp); + } else { + LOGGER.warn("Persistence.useTimestampAsVersion == true, but multiple configurations exist - ignoring"); + } + } + LOGGER.info("RRE: target versions are " + String.join(",", versions)); } @@ -444,4 +464,17 @@ private String query(final JsonNode queryNode, final Optional defaultTem } return query; } + + /** + * Get the version to store when persisting query results. + * + * @param configVersion the configuration set version being evaluated. + * @return the given configVersion, or the version timestamp if and only + * if it is set (eg. there is a single version, and the persistence + * configuration indicates a timestamp should be used to version this + * evaluation data). + */ + private String persistVersion(final String configVersion) { + return ofNullable(versionTimestamp).orElse(configVersion); + } } \ No newline at end of file diff --git a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java index ec0d8063..e18440da 100644 --- a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java +++ b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java @@ -94,7 +94,8 @@ public void execute() throws MojoExecutionException { metrics, fields.split(","), exclude, - include); + include, + persistence); final Map configuration = new HashMap<>(); configuration.put("path.home", "/tmp"); diff --git a/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java b/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java index 5b61a10d..8497082e 100644 --- a/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java +++ b/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.sease.rre.core.Engine; import io.sease.rre.core.domain.Evaluation; +import io.sease.rre.persistence.PersistenceConfiguration; import io.sease.rre.search.api.SearchPlatform; import io.sease.rre.search.api.impl.ApacheSolr; import org.apache.maven.plugin.AbstractMojo; @@ -50,6 +51,9 @@ public class RREvaluateMojo extends AbstractMojo { @Parameter(name = "exclude") private List exclude; + @Parameter(name = "persistence") + private PersistenceConfiguration persistence = PersistenceConfiguration.DEFAULT_CONFIG; + @Override public void execute() throws MojoExecutionException { try (final SearchPlatform platform = new ApacheSolr()) { @@ -62,7 +66,8 @@ public void execute() throws MojoExecutionException { metrics, fields.split(","), exclude, - include); + include, + persistence); final Map configuration = new HashMap<>(); configuration.put("solr.home", "/tmp"); From 8dfa927b253392fe2980f5dc305475fb86dad0d9 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Wed, 21 Nov 2018 10:50:44 +0000 Subject: [PATCH 05/10] Implement JSON persistence handler. --- .../impl/JsonPersistenceHandler.java | 19 +++++++++++++++---- .../impl/JsonPersistenceHandlerTest.java | 13 +++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java b/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java index f206d6be..83ecf1e2 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java @@ -14,6 +14,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -54,16 +56,25 @@ public String getName() { @Override public void beforeStart() throws PersistenceException { // Delete the output file if it already exists - File outFile = new File(outputFilepath); - if (outFile.exists()) { + Path outPath = Paths.get(outputFilepath); + if (Files.exists(outPath)) { try { - Files.delete(outFile.toPath()); + Files.delete(outPath); } catch (IOException e) { throw new PersistenceException("Cannot delete pre-existing output file " + outputFilepath, e); } + } else { + // Make sure the file's parent directory exists + if (outPath.getParent() != null) { + try { + Files.createDirectories(outPath.getParent()); + } catch (IOException e) { + throw new PersistenceException("Cannot create output directory " + outPath.getParent(), e); + } + } } - try (FileOutputStream fos = new FileOutputStream(outFile)) { + try (FileOutputStream fos = new FileOutputStream(outPath.toFile())) { fos.write(new byte[0]); } catch (IOException e) { throw new PersistenceException("Cannot write to output file " + outputFilepath, e); diff --git a/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java b/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java index 48be550b..b240e6c7 100644 --- a/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java +++ b/rre-core/src/test/java/io/sease/rre/persistence/impl/JsonPersistenceHandlerTest.java @@ -67,6 +67,19 @@ public void beforeStartPasses_whenFileExists() throws Exception { handler.beforeStart(); } + @Test + public void beforeStartPasses_whenDestinationDirNotExist() throws Exception { + Map config = new HashMap<>(); + config.put(JsonPersistenceHandler.DESTINATION_FILE_CONFIGKEY, folder.getRoot().getAbsolutePath() + "/target/rre/evaluation.json"); + handler.configure(HANDLER_NAME, config); + + handler.beforeStart(); + + File target = new File(folder.getRoot(), "target/rre/evaluation.json"); + assertTrue(target.exists()); + assertTrue(target.canWrite()); + } + @Test public void beforeStop_handlesEmptyQueryList() throws Exception { File outFile = folder.newFile(); From ac62294e770d775f3295bdd9b7ac7e914d446387 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Wed, 21 Nov 2018 10:51:20 +0000 Subject: [PATCH 06/10] Hook up persistence framework to Engine class. --- .../main/java/io/sease/rre/core/Engine.java | 23 +++++++++++++++++++ .../persistence/PersistenceConfiguration.java | 17 +++++++++++--- .../rre/persistence/PersistenceManager.java | 18 +++++++++------ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/rre-core/src/main/java/io/sease/rre/core/Engine.java b/rre-core/src/main/java/io/sease/rre/core/Engine.java index 181a50d2..e37f99ef 100644 --- a/rre-core/src/main/java/io/sease/rre/core/Engine.java +++ b/rre-core/src/main/java/io/sease/rre/core/Engine.java @@ -8,6 +8,7 @@ import io.sease.rre.core.domain.*; import io.sease.rre.core.domain.metrics.Metric; import io.sease.rre.persistence.PersistenceConfiguration; +import io.sease.rre.persistence.PersistenceHandler; import io.sease.rre.persistence.PersistenceManager; import io.sease.rre.search.api.QueryOrSearchResponse; import io.sease.rre.search.api.SearchPlatform; @@ -105,6 +106,7 @@ public Engine( this.persistenceConfiguration = persistenceConfiguration; this.persistenceManager = new PersistenceManager(); + initialisePersistenceManager(); } public String name(final JsonNode node) { @@ -114,6 +116,19 @@ public String name(final JsonNode node) { .orElse(UNNAMED); } + private void initialisePersistenceManager() { + persistenceConfiguration.getHandlers().forEach((n, h) -> { + try { + // Instantiate the handler + PersistenceHandler handler = (PersistenceHandler) Class.forName(h).newInstance(); + handler.configure(n, persistenceConfiguration.getHandlerConfigurationByName(n)); + persistenceManager.registerHandler(handler); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + LOGGER.error("[" + n + "] Caught exception instantiating persistence handler :: " + e.getMessage()); + } + }); + } + /** * Executes the evaluation process. * @@ -126,11 +141,13 @@ public Evaluation evaluate(final Map configuration) { LOGGER.info("RRE: New evaluation session is starting..."); platform.beforeStart(configuration); + persistenceManager.beforeStart(); LOGGER.info("RRE: Search Platform in use: " + platform.getName()); LOGGER.info("RRE: Starting " + platform.getName() + "..."); platform.start(); + persistenceManager.start(); LOGGER.info("RRE: " + platform.getName() + " Search Platform successfully started."); @@ -207,6 +224,9 @@ public Evaluation evaluate(final Map configuration) { queryEvaluation.setTotalHits(response.totalHits(), persistVersion(version)); response.hits().forEach(hit -> queryEvaluation.collect(hit, rank.getAndIncrement(), persistVersion(version))); }); + + // Persist the query result + persistenceManager.recordQuery(queryEvaluation); }); }); }); @@ -217,7 +237,10 @@ public Evaluation evaluate(final Map configuration) { return evaluation; } finally { platform.beforeStop(); + persistenceManager.beforeStop(); LOGGER.info("RRE: " + platform.getName() + " Search Platform shutdown procedure executed."); + LOGGER.info("RRE: Stopping persistence manager"); + persistenceManager.stop(); } } diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java index 42a526d1..4636a769 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java @@ -26,13 +26,14 @@ public class PersistenceConfiguration { // Supplying type params for nested map breaks Maven initialisation private Map handlerConfiguration; + @SuppressWarnings("unused") public PersistenceConfiguration() { // Do nothing - required for Maven initialisation } - PersistenceConfiguration(boolean useTimestampAsVersion, - Map handlers, - Map handlerConfiguration) { + private PersistenceConfiguration(boolean useTimestampAsVersion, + Map handlers, + Map handlerConfiguration) { this.useTimestampAsVersion = useTimestampAsVersion; this.handlers = handlers; this.handlerConfiguration = handlerConfiguration; @@ -69,6 +70,16 @@ public Map getHandlerConfiguration() { return handlerConfiguration; } + public Map getHandlerConfigurationByName(String name) { + Map configMap = new HashMap<>(); + + if (handlerConfiguration.get(name) != null) { + handlerConfiguration.get(name).forEach((k, v) -> configMap.put(String.valueOf(k), v)); + } + + return configMap; + } + /** * Build a default PersistenceConfiguration, with handlers set to write * to the standard JSON output file. diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java index babbdebf..58053b78 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceManager.java @@ -5,6 +5,7 @@ import org.apache.logging.log4j.Logger; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -28,32 +29,35 @@ public class PersistenceManager { private final List handlers = new ArrayList<>(); public void registerHandler(PersistenceHandler handler) { + LOGGER.info("Registering handler " + handler.getName() + " -> " + handler.getClass().getCanonicalName()); handlers.add(handler); } - public void beforeStart() throws PersistenceException { - handlers.parallelStream().forEach(h -> { + public void beforeStart() { + for (Iterator it = handlers.iterator(); it.hasNext(); ) { + PersistenceHandler h = it.next(); try { h.beforeStart(); } catch (PersistenceException e) { LOGGER.error("beforeStart failed for handler [" + h.getName() + "] :: " + e.getMessage()); - handlers.remove(h); + it.remove(); } - }); + } // Check that there are handlers available checkHandlers(); } public void start() { - handlers.parallelStream().forEach(h -> { + for (Iterator it = handlers.iterator(); it.hasNext(); ) { + PersistenceHandler h = it.next(); try { h.start(); } catch (PersistenceException e) { LOGGER.error("[" + h.getName() + "] failed to start :: " + e.getMessage()); - handlers.remove(h); + it.remove(); } - }); + } checkHandlers(); } From 1691f4c3206505bac81464c726c813ebb468ff10 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Wed, 21 Nov 2018 10:53:40 +0000 Subject: [PATCH 07/10] Modify search mojos to use persistence framework, not write their own output. --- .../plugin/elasticsearch/RREvaluateMojo.java | 22 ++----------------- .../rre/maven/plugin/solr/RREvaluateMojo.java | 19 +--------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java index e18440da..b025b92a 100644 --- a/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java +++ b/rre-maven-plugin/rre-maven-elasticsearch-plugin/src/main/java/io/sease/rre/maven/plugin/elasticsearch/RREvaluateMojo.java @@ -1,8 +1,6 @@ package io.sease.rre.maven.plugin.elasticsearch; -import com.fasterxml.jackson.databind.ObjectMapper; import io.sease.rre.core.Engine; -import io.sease.rre.core.domain.Evaluation; import io.sease.rre.persistence.PersistenceConfiguration; import io.sease.rre.search.api.SearchPlatform; import io.sease.rre.search.api.impl.Elasticsearch; @@ -20,8 +18,6 @@ import java.util.List; import java.util.Map; -import static java.util.Optional.ofNullable; - /** * RREvalutation Mojo (Apache Solr binding). * @@ -102,28 +98,14 @@ public void execute() throws MojoExecutionException { configuration.put("network.host", port); configuration.put("plugins", plugins); - write(engine.evaluate(configuration)); + engine.evaluate(configuration); } catch (final IOException exception) { throw new MojoExecutionException(exception.getMessage(), exception); } } - /** - * Writes out the evaluation result. - * - * @param evaluation the evaluation result. - * @throws IOException in case of I/O failure. - */ - private void write(final Evaluation evaluation) throws IOException { - final File outputFolder = new File("target/rre"); - outputFolder.mkdirs(); - - final ObjectMapper mapper = new ObjectMapper(); - mapper.writerWithDefaultPrettyPrinter().writeValue(new File(outputFolder, "evaluation.json"), evaluation); - } - + // Used by unit test PersistenceConfiguration getPersistence() { return persistence; -// return ofNullable(persistence).orElse(PersistenceConfiguration.defaultConfiguration()); } } \ No newline at end of file diff --git a/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java b/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java index 8497082e..be2b4bd3 100644 --- a/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java +++ b/rre-maven-plugin/rre-maven-solr-plugin/src/main/java/io/sease/rre/maven/plugin/solr/RREvaluateMojo.java @@ -1,8 +1,6 @@ package io.sease.rre.maven.plugin.solr; -import com.fasterxml.jackson.databind.ObjectMapper; import io.sease.rre.core.Engine; -import io.sease.rre.core.domain.Evaluation; import io.sease.rre.persistence.PersistenceConfiguration; import io.sease.rre.search.api.SearchPlatform; import io.sease.rre.search.api.impl.ApacheSolr; @@ -12,7 +10,6 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; -import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; @@ -72,23 +69,9 @@ public void execute() throws MojoExecutionException { final Map configuration = new HashMap<>(); configuration.put("solr.home", "/tmp"); - write(engine.evaluate(configuration)); + engine.evaluate(configuration); } catch (final IOException exception) { throw new MojoExecutionException(exception.getMessage(), exception); } } - - /** - * Writes out the evaluation result. - * - * @param evaluation the evaluation result. - * @throws IOException in case of I/O failure. - */ - private void write(final Evaluation evaluation) throws IOException { - final File outputFolder = new File("target/rre"); - outputFolder.mkdirs(); - - final ObjectMapper mapper = new ObjectMapper(); - mapper.writerWithDefaultPrettyPrinter().writeValue(new File(outputFolder, "evaluation.json"), evaluation); - } } \ No newline at end of file From 424ada7ac516c3db664433946a009b65dfdcdb53 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Wed, 21 Nov 2018 13:40:51 +0000 Subject: [PATCH 08/10] Add specific check for ConcurrentModificationException. --- .../persistence/PersistenceManagerTest.java | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java b/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java index 0f9f1956..ac3521c1 100644 --- a/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java +++ b/rre-core/src/test/java/io/sease/rre/persistence/PersistenceManagerTest.java @@ -5,8 +5,11 @@ import org.junit.Before; import org.junit.Test; +import java.util.ConcurrentModificationException; import java.util.Map; +import static org.junit.Assert.fail; + /** * Unit tests for the PersistenceManager class. * @@ -26,16 +29,28 @@ public void tearDownManager() { this.persistenceManager = null; } - @Test(expected = RuntimeException.class) - public void managerThrowsRuntimeException_whenAllHandlersFailBeforeStart() throws Exception { - persistenceManager.registerHandler(failingHandler); - persistenceManager.beforeStart(); + @Test + public void managerThrowsRuntimeException_whenAllHandlersFailBeforeStart() { + try { + persistenceManager.registerHandler(failingHandler); + persistenceManager.beforeStart(); + } catch (ConcurrentModificationException e) { + fail("Unexpected ConcurrentModificationException: " + e.getMessage()); + } catch (RuntimeException e) { + // Expected + } } - @Test(expected = RuntimeException.class) - public void managerThrowsRuntimeException_whenAllHandlersFailStart() throws Exception { - persistenceManager.registerHandler(failingHandler); - persistenceManager.start(); + @Test + public void managerThrowsRuntimeException_whenAllHandlersFailStart() { + try { + persistenceManager.registerHandler(failingHandler); + persistenceManager.start(); + } catch (ConcurrentModificationException e) { + fail("Unexpected ConcurrentModificationException: " + e.getMessage()); + } catch (RuntimeException e) { + // Expected + } } private PersistenceHandler failingHandler = new PersistenceHandler() { From 880878695ec02f7cf08b712f15cafcccec8d497c Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Wed, 28 Nov 2018 10:03:04 +0000 Subject: [PATCH 09/10] Modify report plugin to allow evaluation output to be set as parameter. --- .../sease/rre/persistence/impl/JsonPersistenceHandler.java | 2 +- .../io/sease/rre/maven/plugin/report/RREMavenReport.java | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java b/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java index 83ecf1e2..bb23d0cf 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/impl/JsonPersistenceHandler.java @@ -31,7 +31,7 @@ public class JsonPersistenceHandler implements PersistenceHandler { static final String DESTINATION_FILE_CONFIGKEY = "destinationFile"; static final String PRETTY_CONFIGKEY = "pretty"; - private static final String DEFAULT_OUTPUT_FILE = "target/rre/evaluation.json"; + public static final String DEFAULT_OUTPUT_FILE = "target/rre/evaluation.json"; private static final Logger LOGGER = LogManager.getLogger(JsonPersistenceHandler.class); diff --git a/rre-maven-plugin/rre-maven-report-plugin/src/main/java/io/sease/rre/maven/plugin/report/RREMavenReport.java b/rre-maven-plugin/rre-maven-report-plugin/src/main/java/io/sease/rre/maven/plugin/report/RREMavenReport.java index 8df4ddbb..23e7d129 100644 --- a/rre-maven-plugin/rre-maven-report-plugin/src/main/java/io/sease/rre/maven/plugin/report/RREMavenReport.java +++ b/rre-maven-plugin/rre-maven-report-plugin/src/main/java/io/sease/rre/maven/plugin/report/RREMavenReport.java @@ -2,11 +2,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.sease.rre.maven.plugin.report.domain.EvaluationMetadata; import io.sease.rre.maven.plugin.report.formats.OutputFormat; import io.sease.rre.maven.plugin.report.formats.impl.RREOutputFormat; import io.sease.rre.maven.plugin.report.formats.impl.SpreadsheetOutputFormat; +import io.sease.rre.persistence.impl.JsonPersistenceHandler; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.reporting.AbstractMavenReport; @@ -35,6 +35,9 @@ public class RREMavenReport extends AbstractMavenReport { @Parameter(name = "endpoint", defaultValue = "http://127.0.0.1:8080") String endpoint; + @Parameter(name="evaluationFile", defaultValue = JsonPersistenceHandler.DEFAULT_OUTPUT_FILE) + String evaluationFile; + private Map formatters = new HashMap<>(); { @@ -131,7 +134,7 @@ private JsonNode evaluationAsJson() { * @return a file reference to the evaluation output. */ File evaluationOutputFile() { - final File file = new File("target/rre/evaluation.json"); + final File file = new File(evaluationFile); if (!file.canRead()) { throw new RuntimeException("Unable to read RRE evaluation output file. Are you sure RRE executed successfully?"); } From d6f03d3b3c8da254ca44f243cb0a7f22561dc5e6 Mon Sep 17 00:00:00 2001 From: Matt Pearce Date: Thu, 29 Nov 2018 11:20:17 +0000 Subject: [PATCH 10/10] Javadocs. --- .../rre/persistence/PersistenceConfiguration.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java index 4636a769..ad3de425 100644 --- a/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java +++ b/rre-core/src/main/java/io/sease/rre/persistence/PersistenceConfiguration.java @@ -70,6 +70,16 @@ public Map getHandlerConfiguration() { return handlerConfiguration; } + /** + * Get the configuration for an individual handler, returning a map of + * configuration items keyed by the String value of their name. If there + * is no configuration, an empty map will be returned. + * + * @param name the name of the handler whose configuration is required. + * @return a String:Object map containing the configuration. Never + * {@code null}. + */ + @SuppressWarnings("unchecked") public Map getHandlerConfigurationByName(String name) { Map configMap = new HashMap<>();