Skip to content

Commit

Permalink
Merge pull request #68 from mattflax/rre_external_file
Browse files Browse the repository at this point in the history
Make server evaluation handling more generic
  • Loading branch information
agazzarini authored Dec 13, 2018
2 parents c4d23f0 + 00f9a5c commit b62879f
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 110 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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.maven.plugin.report.formats.impl.UrlRREOutputFormat;
import io.sease.rre.persistence.impl.JsonPersistenceHandler;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
Expand Down Expand Up @@ -43,6 +44,7 @@ public class RREMavenReport extends AbstractMavenReport {
{
formatters.put("spreadsheet", new SpreadsheetOutputFormat());
formatters.put("rre-server", new RREOutputFormat());
formatters.put("url-rre-server", new UrlRREOutputFormat());
}

private final ObjectMapper mapper = new ObjectMapper();
Expand Down Expand Up @@ -105,6 +107,10 @@ public String getDescription(final Locale locale) {
return "N.A.";
}

public String getEvaluationFile() {
return evaluationFile;
}

/**
* Returns the endpoint of a running RRE server.
* Note that this is supposed to be used only in conjunction with the corresponding output format.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.sease.rre.maven.plugin.report.formats.impl;

import com.fasterxml.jackson.databind.JsonNode;
import io.sease.rre.maven.plugin.report.RREMavenReport;
import io.sease.rre.maven.plugin.report.domain.EvaluationMetadata;
import io.sease.rre.maven.plugin.report.formats.OutputFormat;
import okhttp3.*;

import java.io.File;
import java.util.Locale;

import static java.util.Objects.requireNonNull;

/**
* RRE server implementation of OutputFormat that sends a file URL to the
* server, rather than bundling the whole evaluation output in one request.
*
* @author Matt Pearce ([email protected])
*/
public class UrlRREOutputFormat implements OutputFormat {

@Override
public void writeReport(JsonNode data, EvaluationMetadata metadata, Locale locale, RREMavenReport plugin) {
try {
Request request = new Request.Builder()
.url(requireNonNull(HttpUrl.parse(plugin.getEndpoint() + "/evaluation")))
.post(RequestBody.create(MediaType.parse("application/json"),
"{ \"url\": \"" + new File(plugin.getEvaluationFile()).toURI().toURL() + "\" }"))
.build();

try (final Response response = new OkHttpClient().newCall(request).execute()) {
if (response.code() != 200) {
plugin.getLog().error("Exception while communicating with RREServer. Return code was: " + response.code());
} else {
plugin.getLog().info("Evaluation data has been correctly sent to RRE Server located at " + plugin.getEndpoint());
}
}
} catch (final Exception exception) {
plugin.getLog().error("RRE: Unable to connect to RRE Server. See below for further details.", exception);
}
}
}
2 changes: 2 additions & 0 deletions rre-server/src/main/java/io/sease/rre/server/RREServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;

/**
* RRE Server main entry point.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,96 +2,32 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.sease.rre.core.domain.*;
import io.sease.rre.core.domain.Evaluation;
import io.sease.rre.server.domain.EvaluationMetadata;
import io.sease.rre.server.domain.StaticMetric;
import io.sease.rre.server.services.EvaluationHandlerService;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static java.util.stream.StreamSupport.stream;

@RestController
public class RREController {
private Evaluation evaluation = new Evaluation();
private EvaluationMetadata metadata = new EvaluationMetadata(Collections.emptyList(), Collections.emptyList());
private ObjectMapper mapper = new ObjectMapper();

@PostMapping("/evaluation")
public void updateEvaluationData(@RequestBody final JsonNode evaluation) {
this.evaluation = make(evaluation);

metadata = evaluationMetadata(this.evaluation);
}

/**
* Creates an evaluation object from the input JSON data.
*
* @param data the JSON payload.
* @return a session evaluation instance.
*/
private Evaluation make(final JsonNode data) {
final Evaluation evaluation = new Evaluation();
evaluation.setName(data.get("name").asText());

metrics(data, evaluation);

data.get("corpora").iterator().forEachRemaining(corpusNode -> {
final String cname = corpusNode.get("name").asText();
final Corpus corpus = evaluation.findOrCreate(cname, Corpus::new);

metrics(corpusNode, corpus);

corpusNode.get("topics").iterator().forEachRemaining(topicNode -> {
final String tname = topicNode.get("name").asText();
final Topic topic = corpus.findOrCreate(tname, Topic::new);
metrics(topicNode, topic);

topicNode.get("query-groups").iterator().forEachRemaining(groupNode -> {
final String gname = groupNode.get("name").asText();
final QueryGroup group = topic.findOrCreate(gname, QueryGroup::new);
metrics(groupNode, group);

groupNode.get("query-evaluations").iterator().forEachRemaining(queryNode -> {
final String qename = queryNode.get("query").asText();
final Query q = group.findOrCreate(qename, Query::new);
metrics(queryNode, q);

@Autowired
private EvaluationHandlerService evaluationHandler;

queryNode.get("results").fields().forEachRemaining(resultsEntry -> {
final MutableQueryOrSearchResponse versionedResponse =
q.getResults().computeIfAbsent(
resultsEntry.getKey(),
version -> new MutableQueryOrSearchResponse());

JsonNode content = resultsEntry.getValue();
versionedResponse.setTotalHits(content.get("total-hits").asLong(), null);

stream(content.get("hits").spliterator(), false)
.map(hit -> mapper.convertValue(hit, Map.class))
.forEach(hit -> versionedResponse.collect(hit, -1, null));
});
});
});
});
});

return evaluation;
@PostMapping("/evaluation")
public void updateEvaluationData(@RequestBody final JsonNode requestBody) throws Exception {
evaluationHandler.processEvaluationRequest(requestBody);
}

public EvaluationMetadata getMetadata() {
return metadata;
return evaluationHandler.getEvaluationMetadata();
}

@ApiOperation(value = "Returns the evaluation data.")
Expand All @@ -101,39 +37,9 @@ public EvaluationMetadata getMetadata() {
@ApiResponse(code = 414, message = "Request-URI Too Long"),
@ApiResponse(code = 500, message = "System internal failure occurred.")
})
@GetMapping("/evaluation")
public Evaluation getEvaluationData() throws Exception {
return evaluation;
}

/**
* Creates the evaluation metadata.
*
* @param evaluation the evaluation data.
* @return the evaluation metadata.
*/
private EvaluationMetadata evaluationMetadata(final Evaluation evaluation) {
final List<String> metrics = new ArrayList<>(
evaluation.getChildren()
.iterator().next()
.getMetrics().keySet());

final List<String> versions = new ArrayList<>(
evaluation.getChildren()
.iterator().next()
.getMetrics().values().iterator().next().getVersions().keySet());

return new EvaluationMetadata(versions, metrics);
}

private void metrics(final JsonNode data, final DomainMember parent) {
data.get("metrics").fields().forEachRemaining(entry -> {
final StaticMetric metric = new StaticMetric(entry.getKey());

entry.getValue().get("versions").fields().forEachRemaining(vEntry -> {
metric.collect(vEntry.getKey(), new BigDecimal(vEntry.getValue().get("value").asDouble()).setScale(4, RoundingMode.CEILING));
});
parent.getMetrics().put(metric.getName(), metric);
});
@GetMapping(value = "/evaluation", produces = { "application/json" })
@ResponseBody
public Evaluation getEvaluationData() {
return evaluationHandler.getEvaluation();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.sease.rre.server.services;

/**
* Exception thrown during evaluation handling.
*
* @author Matt Pearce ([email protected])
*/
public class EvaluationHandlerException extends Exception {

public EvaluationHandlerException() {
}

public EvaluationHandlerException(String message) {
super(message);
}

public EvaluationHandlerException(String message, Throwable cause) {
super(message, cause);
}

public EvaluationHandlerException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.sease.rre.server.services;

import com.fasterxml.jackson.databind.JsonNode;
import io.sease.rre.core.domain.Evaluation;
import io.sease.rre.server.domain.EvaluationMetadata;
import org.springframework.stereotype.Service;

/**
* An EvaluationHandlerService can be used to process an incoming evaluation
* update request. It should extract the relevant details from the request,
* and use them to build an Evaluation object that can be used to populate
* the dashboard.
*
* The {@link #processEvaluationRequest(JsonNode)} method should ideally
* return as quickly as possible, to avoid blocking the sender of the incoming
* request. The evaluation data can then be retrieved using {@link #getEvaluation()}
* where the evaluation contains the most recently processed data.
*
* @author Matt Pearce ([email protected])
*/
@Service
public interface EvaluationHandlerService {

/**
* Update the currently held evaluation data. This may be done
* asynchronously - the method should return as quickly as possible.
*
* @param requestData incoming data giving details of evaluation.
* @throws EvaluationHandlerException if the data cannot be processed.
*/
void processEvaluationRequest(final JsonNode requestData) throws EvaluationHandlerException;

/**
* Get the current evaluation data.
*
* @return the Evaluation.
*/
Evaluation getEvaluation();

/**
* Get the current evaluation metadata.
*
* @return the evaluation metadata.
*/
EvaluationMetadata getEvaluationMetadata();
}
Loading

0 comments on commit b62879f

Please sign in to comment.