Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated - and greatly simplified - GenerateAssertionsResponse. Now ju… #175

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,29 @@

package au.org.democracydevelopers.corla.communication.responseFromRaire;

import java.util.Objects;

/**
* The success response when a ContestRequest is sent to raire's generate-assertions endpoint. This
* simply returns the winner, as calculated by raire, along with the name of the contest for which
* the initial request was made.
* This record is identical to the record of the same name in raire-service. Used for
* The response when a ContestRequest is sent to raire's generate-assertions endpoint.
* This class is identical to the record of the same name in raire-service. Used for
* deserialization.
* All four states of the two booleans are possible - for example, generation may succeed, but
* receive a TIME_OUT_TRIMMING_ASSERTIONS warning, in which case retry will be true.
*/
public final class GenerateAssertionsResponse {
public String contestName;
public String winner;
public boolean succeeded;
public boolean retry;

/**
* @param contestName The name of the contest.
* @param winner The winner of the contest, as calculated by raire.
*/
public GenerateAssertionsResponse(String contestName, String winner) {

/**
* All args constructor.
* @param contestName The name of the contest.
* @param succeeded Whether assertion generation succeeded.
* @param retry Whether it is worth retrying assertion generation.
*/
public GenerateAssertionsResponse(String contestName, boolean succeeded, boolean retry) {
this.contestName = contestName;
this.winner = winner;
this.succeeded = succeeded;
this.retry = retry;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@

import au.org.democracydevelopers.corla.communication.requestToRaire.GenerateAssertionsRequest;
import au.org.democracydevelopers.corla.communication.responseFromRaire.GenerateAssertionsResponse;
import au.org.democracydevelopers.corla.communication.responseFromRaire.RaireServiceErrors;
import au.org.democracydevelopers.corla.communication.responseToColoradoRla.GenerateAssertionsResponseWithErrors;
import com.google.gson.JsonSyntaxException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
Expand Down Expand Up @@ -120,7 +118,7 @@ public String endpointBody(final Request the_request, final Response the_respons
final String prefix = "[endpointBody]";
LOGGER.debug(String.format("%s %s.", prefix, "Received Generate Assertions request"));

final List<GenerateAssertionsResponseWithErrors> responseData;
final List<GenerateAssertionsResponse> responseData;

final String raireUrl = Main.properties().getProperty(RAIRE_URL, "") + RAIRE_ENDPOINT;

Expand Down Expand Up @@ -177,22 +175,20 @@ public String endpointBody(final Request the_request, final Response the_respons
* - Gather all the IRVContestResults
* - For each IRV contest, make a request to the raire-service get-assertions endpoint of the right format type
* - Collate all the results into a list.
*
* @param IRVContestResults the collection of all IRV ContestResults.
* @param timeLimitSeconds the time limit for raire assertion generation, per contest.
* @param raireUrl the url where the raire-service is running.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing these input arguments can be final?

*/
protected List<GenerateAssertionsResponseWithErrors> generateAllAssertions(List<ContestResult> IRVContestResults,
double timeLimitSeconds, String raireUrl) {
protected List<GenerateAssertionsResponse> generateAllAssertions(final List<ContestResult> IRVContestResults,
final double timeLimitSeconds, final String raireUrl) {
final String prefix = "[generateAllAssertions]";
LOGGER.debug(String.format("%s %s.", prefix, "Generating assertions for all IRV contests"));

final List<GenerateAssertionsResponseWithErrors> responseData = new ArrayList<>();

// Iterate through all IRV Contests, sending a request to the raire-service for each one's assertions and
for (final ContestResult cr : IRVContestResults) {
GenerateAssertionsResponseWithErrors response = generateAssertionsUpdateWinners(IRVContestResults, cr.getContestName(), timeLimitSeconds, raireUrl);
responseData.add(response);
}
// Iterate through all IRV Contests, sending a request to the raire-service for each one's assertions
final List<GenerateAssertionsResponse> responseData = IRVContestResults.stream().map(
r -> generateAssertionsUpdateWinners(IRVContestResults, r.getContestName(), timeLimitSeconds, raireUrl)
).toList();

LOGGER.debug(String.format("%s %s.", prefix, "Completed assertion generation for all IRV contests"));
return responseData;
Expand All @@ -202,21 +198,23 @@ protected List<GenerateAssertionsResponseWithErrors> generateAllAssertions(List<
* The main work of this endpoint - sends the appropriate request for a single contest, and
* updates stored data with the result. There are two expected kinds of responses from raire:
* - a success response with a winner, or
* - an INTERNAL_SERVER_ERROR response with a reason, e.g. TIED_WINNERS or NO_VOTES_PRESENT.
* These are expected to happen occasionally because of the data.
* - an error response with a reason, e.g. TIED_WINNERS or NO_VOTES_PRESENT.
* These are expected to happen occasionally because of the data, and are saved in the
* GenerateAssertionsSummary table.
* Other errors, such as a BAD_REQUEST response or a failure to parse raire's response, indicate
* programming or configuration errors. These are logged.
* programming or configuration errors. These are logged and an exception is thrown.
*
* @param IRVContestResults The list of all ContestResults for IRV contests. Note that these do
* not have the correct winners or losers for IRV.
* @param contestName The name of the contest.
* @param timeLimitSeconds The time limit allowed for raire to compute the assertions (not
* counting time taken to retrieve vote data from the database).
* @param raireUrl The url of the raire service.
* @return The GenerateAssertionsResponseWithErrors, which usually contains a
* winner but may instead be UNKNOWN_WINNER and an error message.
* @return The GenerateAssertionsResponseWithErrors, which usually contains a
* winner but may instead be UNKNOWN_WINNER and an error message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check input arguments for what can be final.

*/
protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(List<ContestResult> IRVContestResults,
String contestName, double timeLimitSeconds, String raireUrl) {
protected GenerateAssertionsResponse generateAssertionsUpdateWinners(final List<ContestResult> IRVContestResults,
final String contestName, final double timeLimitSeconds, final String raireUrl) {
final String prefix = "[generateAssertionsUpdateWinners]";
LOGGER.debug(String.format("%s %s %s.", prefix, "Generating assertions for contest ", contestName));

Expand Down Expand Up @@ -248,30 +246,17 @@ protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(L

// Interpret the response.
final int statusCode = raireResponse.getStatusLine().getStatusCode();
final boolean gotRaireError = raireResponse.containsHeader(RaireServiceErrors.ERROR_CODE_KEY);

if (statusCode == HttpStatus.SC_OK && !gotRaireError) {
// OK response. Return the winner.

LOGGER.debug(String.format("%s %s %s.", prefix, "OK response received from RAIRE for",
contestName));
GenerateAssertionsResponse responseFromRaire = Main.GSON.fromJson(EntityUtils.toString(raireResponse.getEntity()),
GenerateAssertionsResponse.class);

LOGGER.debug(String.format("%s %s %s.", prefix,
"Completed assertion generation for contest", contestName));
return new GenerateAssertionsResponseWithErrors(contestName, responseFromRaire.winner, "");

} else if (statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR && gotRaireError) {
// Error response about a specific contest, e.g. "TIED_WINNERS". Return the error.
if (statusCode == HttpStatus.SC_OK) {

final String code = raireResponse.getFirstHeader(RaireServiceErrors.ERROR_CODE_KEY).getValue();
LOGGER.debug(String.format("%s %s %s.", prefix, "Error response " + code,
"received from RAIRE for " + contestName));
// OK response, which may indicate either that assertion generation succeeded, or that it
// failed and raire generated a useful error. Return raire's response.
final GenerateAssertionsResponse responseFromRaire
= Main.GSON.fromJson(EntityUtils.toString(raireResponse.getEntity()), GenerateAssertionsResponse.class);

LOGGER.debug(String.format("%s %s %s.", prefix,
"Error response for assertion generation for contest ", contestName));
return new GenerateAssertionsResponseWithErrors(cr.getContestName(), UNKNOWN_WINNER, code);
LOGGER.debug(String.format("%s %s %s %s.", prefix, responseFromRaire.succeeded ? "Success" : "Failure",
"response for raire assertion generation for contest", contestName));
return responseFromRaire;

} else {
// Something went wrong with the connection, e.g. 404 or a Bad Request. Cannot continue.
Expand All @@ -280,12 +265,12 @@ protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(L
LOGGER.error(String.format("%s %s", prefix, msg));
throw new RuntimeException(msg);
}
} catch (URISyntaxException | MalformedURLException e) {
} catch (final URISyntaxException | MalformedURLException e) {
// The raire service url is malformed, probably a config error.
final String msg = "Bad configuration of Raire service url: " + raireUrl + ". Check your config file.";
LOGGER.error(String.format("%s %s %s", prefix, msg, e.getMessage()));
throw new RuntimeException(msg);
} catch (NoSuchElementException e ) {
} catch (final NoSuchElementException e) {
// This happens if the contest name is not in the IRVContestResults, or if the Contest Result
// does not actually contain any contests (the latter should never happen).
LOGGER.error(String.format("%s %s %s.", prefix, e.getMessage(), contestName));
Expand All @@ -296,25 +281,25 @@ protected GenerateAssertionsResponseWithErrors generateAssertionsUpdateWinners(L
final String msg = "Error interpreting Raire response for contest ";
LOGGER.error(String.format("%s %s %s %s", prefix, msg, contestName, e.getMessage()));
throw new RuntimeException(msg + contestName);
} catch (UnsupportedEncodingException e) {
} catch (final UnsupportedEncodingException e) {
// This really shouldn't happen, but would happen if the effort to make the
// generateAssertionsRequest as json failed.
final String msg = "Error generating request to Raire for contest ";
LOGGER.error(String.format("%s %s %s %s", prefix, msg, contestName, e.getMessage()));
throw new RuntimeException(msg + contestName + e.getMessage());
} catch (ClientProtocolException e) {
} catch (final ClientProtocolException e) {
// This also really shouldn't happen, but would happen if the effort to use the httpClient
// to send a message threw an exception.
final String msg = "Error sending request to Raire for contest ";
LOGGER.error(String.format("%s %s %s %s", prefix, msg, contestName, e.getMessage()));
throw new RuntimeException(msg + contestName + e.getMessage());
} catch (NullPointerException e) {
} catch (final NullPointerException e) {
// This also shouldn't happen - it would indicate an unexpected problem such as the httpClient
// returning a null response.
final String msg = "Error requesting or receiving assertions for contest ";
LOGGER.error(String.format("%s %s %s.", prefix, msg, contestName));
throw new RuntimeException(msg + contestName);
} catch (IOException e) {
} catch (final IOException e) {
// Generic error that can be thrown by the httpClient if the connection attempt fails.
final String msg = "I/O error during generate assertions attempt for contest ";
LOGGER.error(String.format("%s %s %s %s", prefix, msg, contestName, e.getMessage()));
Expand All @@ -338,11 +323,11 @@ protected boolean validateParameters(final Request the_request) {
if (timeLimit != null && Double.parseDouble(timeLimit) <= 0) {
return false;
}
} catch (NumberFormatException e) {
} catch (final NumberFormatException e) {
return false;
}

// An absent contest name is fine, but a present null or blank one is invalid.
// An absent contest name is OK, but a present null or blank one is invalid.
final String contestName = the_request.queryParams(CONTEST_NAME);
return contestName == null || !contestName.isEmpty();
}
Expand Down
Loading
Loading