• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DataBiosphere / consent / #5929

19 May 2025 12:36PM UTC coverage: 78.551% (-0.2%) from 78.727%
#5929

push

web-flow
DT-1564, Progress Report DAC flow (#2523)

175 of 228 new or added lines in 17 files covered. (76.75%)

5 existing lines in 3 files now uncovered.

10027 of 12765 relevant lines covered (78.55%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.52
/src/main/java/org/broadinstitute/consent/http/service/VoteService.java
1
package org.broadinstitute.consent.http.service;
2

3
import static java.util.function.Predicate.not;
4

5
import com.google.api.client.http.HttpStatusCodes;
6
import com.google.gson.Gson;
7
import com.google.gson.reflect.TypeToken;
8
import com.google.inject.Inject;
9
import jakarta.ws.rs.NotFoundException;
10
import java.lang.reflect.Type;
11
import java.sql.SQLException;
12
import java.util.ArrayList;
13
import java.util.Collections;
14
import java.util.Date;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Objects;
20
import java.util.Set;
21
import java.util.stream.Collectors;
22
import org.apache.commons.lang3.StringUtils;
23
import org.apache.commons.validator.routines.EmailValidator;
24
import org.broadinstitute.consent.http.db.DarCollectionDAO;
25
import org.broadinstitute.consent.http.db.DataAccessRequestDAO;
26
import org.broadinstitute.consent.http.db.DatasetDAO;
27
import org.broadinstitute.consent.http.db.ElectionDAO;
28
import org.broadinstitute.consent.http.db.UserDAO;
29
import org.broadinstitute.consent.http.db.VoteDAO;
30
import org.broadinstitute.consent.http.enumeration.DataUseTranslationType;
31
import org.broadinstitute.consent.http.enumeration.ElectionStatus;
32
import org.broadinstitute.consent.http.enumeration.ElectionType;
33
import org.broadinstitute.consent.http.enumeration.UserRoles;
34
import org.broadinstitute.consent.http.enumeration.VoteType;
35
import org.broadinstitute.consent.http.models.Dac;
36
import org.broadinstitute.consent.http.models.DarCollection;
37
import org.broadinstitute.consent.http.models.DataAccessRequest;
38
import org.broadinstitute.consent.http.models.DataUse;
39
import org.broadinstitute.consent.http.models.Dataset;
40
import org.broadinstitute.consent.http.models.Election;
41
import org.broadinstitute.consent.http.models.Study;
42
import org.broadinstitute.consent.http.models.StudyProperty;
43
import org.broadinstitute.consent.http.models.User;
44
import org.broadinstitute.consent.http.models.Vote;
45
import org.broadinstitute.consent.http.models.dataset_registration_v1.builder.DatasetRegistrationSchemaV1Builder;
46
import org.broadinstitute.consent.http.models.dto.DatasetMailDTO;
47
import org.broadinstitute.consent.http.service.dao.VoteServiceDAO;
48
import org.broadinstitute.consent.http.util.ComplianceLogger;
49
import org.broadinstitute.consent.http.util.ConsentLogger;
50
import org.broadinstitute.consent.http.util.gson.GsonUtil;
51
import org.glassfish.jersey.server.ContainerRequest;
52

53
public class VoteService implements ConsentLogger {
54

55
  private final UserDAO userDAO;
56
  private final DarCollectionDAO darCollectionDAO;
57
  private final DataAccessRequestDAO dataAccessRequestDAO;
58
  private final DatasetDAO datasetDAO;
59
  private final ElectionDAO electionDAO;
60
  private final EmailService emailService;
61
  private final ElasticSearchService elasticSearchService;
62
  private final UseRestrictionConverter useRestrictionConverter;
63
  private final VoteDAO voteDAO;
64
  private final VoteServiceDAO voteServiceDAO;
65

66
  @Inject
67
  public VoteService(UserDAO userDAO, DarCollectionDAO darCollectionDAO,
68
      DataAccessRequestDAO dataAccessRequestDAO,
69
      DatasetDAO datasetDAO, ElectionDAO electionDAO,
70
      EmailService emailService, ElasticSearchService elasticSearchService,
71
      UseRestrictionConverter useRestrictionConverter,
72
      VoteDAO voteDAO, VoteServiceDAO voteServiceDAO) {
1✔
73
    this.userDAO = userDAO;
1✔
74
    this.darCollectionDAO = darCollectionDAO;
1✔
75
    this.dataAccessRequestDAO = dataAccessRequestDAO;
1✔
76
    this.datasetDAO = datasetDAO;
1✔
77
    this.electionDAO = electionDAO;
1✔
78
    this.emailService = emailService;
1✔
79
    this.elasticSearchService = elasticSearchService;
1✔
80
    this.useRestrictionConverter = useRestrictionConverter;
1✔
81
    this.voteDAO = voteDAO;
1✔
82
    this.voteServiceDAO = voteServiceDAO;
1✔
83
  }
1✔
84

85
  /**
86
   * @param vote Vote to update
87
   * @return The updated Vote
88
   */
89
  public Vote updateVote(Vote vote) {
90
    validateVote(vote);
1✔
91
    Date now = new Date();
1✔
92
    voteDAO.updateVote(
1✔
93
        vote.getVote(),
1✔
94
        vote.getRationale(),
1✔
95
        Objects.isNull(vote.getUpdateDate()) ? now : vote.getUpdateDate(),
1✔
96
        vote.getVoteId(),
1✔
97
        vote.getIsReminderSent(),
1✔
98
        vote.getElectionId(),
1✔
99
        Objects.isNull(vote.getCreateDate()) ? now : vote.getCreateDate(),
1✔
100
        vote.getHasConcerns()
1✔
101
    );
102
    return voteDAO.findVoteById(vote.getVoteId());
1✔
103
  }
104

105

106
  public Vote updateVote(Vote rec, Integer voteId, String referenceId)
107
      throws IllegalArgumentException {
108
    if (voteDAO.checkVoteById(referenceId, voteId) == null) {
1✔
109
      notFoundException(voteId);
×
110
    }
111
    Vote vote = voteDAO.findVoteById(voteId);
1✔
112
    Date updateDate = rec.getVote() == null ? null : new Date();
1✔
113
    String rationale = StringUtils.isNotEmpty(rec.getRationale()) ? rec.getRationale() : null;
1✔
114
    voteDAO.updateVote(rec.getVote(), rationale, updateDate, voteId, false, vote.getElectionId(),
1✔
115
        vote.getCreateDate(), rec.getHasConcerns());
1✔
116
    return voteDAO.findVoteById(voteId);
1✔
117
  }
118

119
  /**
120
   * Create votes for an election
121
   *
122
   * @param election       The Election
123
   * @param electionType   The Election type
124
   * @param isManualReview Is this a manual review election
125
   * @return List of votes
126
   */
127
  @SuppressWarnings("DuplicatedCode")
128
  public List<Vote> createVotes(Election election, ElectionType electionType,
129
      Boolean isManualReview) {
130
    Dac dac = electionDAO.findDacForElection(election.getElectionId());
1✔
131
    Set<User> users;
132
    if (dac != null) {
1✔
133
      users = userDAO.findUsersEnabledToVoteByDAC(dac.getDacId());
×
134
    } else {
135
      users = userDAO.findNonDacUsersEnabledToVote();
1✔
136
    }
137
    List<Vote> votes = new ArrayList<>();
1✔
138
    if (users != null) {
1✔
139
      for (User user : users) {
1✔
140
        votes.addAll(createVotesForUser(user, election, electionType, isManualReview));
1✔
141
      }
1✔
142
    }
143
    return votes;
1✔
144
  }
145

146
  /**
147
   * Create election votes for a user
148
   *
149
   * @param user           DACUser
150
   * @param election       Election
151
   * @param electionType   ElectionType
152
   * @param isManualReview Is election manual review
153
   * @return List of created votes
154
   */
155
  public List<Vote> createVotesForUser(User user, Election election, ElectionType electionType,
156
      Boolean isManualReview) {
157
    Dac dac = electionDAO.findDacForElection(election.getElectionId());
1✔
158
    List<Vote> votes = new ArrayList<>();
1✔
159
    Integer dacVoteId = voteDAO.insertVote(user.getUserId(), election.getElectionId(),
1✔
160
        VoteType.DAC.getValue());
1✔
161
    votes.add(voteDAO.findVoteById(dacVoteId));
1✔
162
    if (isDacChairPerson(dac, user)) {
1✔
163
      Integer chairVoteId = voteDAO.insertVote(user.getUserId(), election.getElectionId(),
1✔
164
          VoteType.CHAIRPERSON.getValue());
1✔
165
      votes.add(voteDAO.findVoteById(chairVoteId));
1✔
166
      // Requires Chairperson role to create a final and agreement vote in the Data Access case
167
      if (electionType.equals(ElectionType.DATA_ACCESS)) {
1✔
168
        Integer finalVoteId = voteDAO.insertVote(user.getUserId(), election.getElectionId(),
1✔
169
            VoteType.FINAL.getValue());
1✔
170
        votes.add(voteDAO.findVoteById(finalVoteId));
1✔
171
        if (!isManualReview) {
1✔
172
          Integer agreementVoteId = voteDAO.insertVote(user.getUserId(), election.getElectionId(),
1✔
173
              VoteType.AGREEMENT.getValue());
1✔
174
          votes.add(voteDAO.findVoteById(agreementVoteId));
1✔
175
        }
176
      }
177
    }
178
    return votes;
1✔
179
  }
180

181
  public List<Vote> findVotesByIds(List<Integer> voteIds) {
182
    if (voteIds.isEmpty()) {
1✔
183
      return Collections.emptyList();
1✔
184
    }
185
    return voteDAO.findVotesByIds(voteIds);
1✔
186
  }
187

188
  /**
189
   * Delete any votes in Open elections for the specified user in the specified Dac.
190
   *
191
   * @param dac  The Dac we are restricting elections to
192
   * @param user The Dac member we are deleting votes for
193
   */
194
  public void deleteOpenDacVotesForUser(Dac dac, User user) {
195
    List<Integer> openElectionIds = electionDAO.findOpenElectionsByDacId(dac.getDacId()).stream().
×
196
        map(Election::getElectionId).
×
197
        collect(Collectors.toList());
×
198
    if (!openElectionIds.isEmpty()) {
×
199
      List<Integer> openUserVoteIds = voteDAO.findVotesByElectionIds(openElectionIds).stream().
×
200
          filter(v -> v.getUserId().equals(user.getUserId())).
×
201
          map(Vote::getVoteId).
×
202
          collect(Collectors.toList());
×
203
      if (!openUserVoteIds.isEmpty()) {
×
204
        voteDAO.removeVotesByIds(openUserVoteIds);
×
205
      }
206
    }
207
  }
×
208

209
  /**
210
   * Update vote values. 'FINAL' votes impact elections so matching elections marked as
211
   * ElectionStatus.CLOSED as well. Approved 'FINAL' votes trigger an approval email to
212
   * researchers.
213
   *
214
   * @param votes     List of Votes to update
215
   * @param voteValue Value to update the votes to
216
   * @param rationale Value to update the rationales to. Only update if non-null.
217
   * @param user      The user making the update
218
   * @return The updated Vote
219
   * @throws IllegalArgumentException when there are non-open, non-rp elections on any of the votes
220
   */
221
  public List<Vote> updateVotesWithValue(List<Vote> votes, boolean voteValue, String rationale, User user)
222
      throws IllegalArgumentException {
223
    validateVotesCanUpdate(votes);
1✔
224
    try {
225
      List<Vote> updatedVotes = voteServiceDAO.updateVotesWithValue(votes, voteValue, rationale);
1✔
226
      if (voteValue) {
1✔
227
        try {
228
          sendDatasetApprovalNotifications(updatedVotes, user);
1✔
229
        } catch (Exception e) {
×
230
          // We can recover from email errors, log it and don't fail the overall process.
231
          String voteIds = votes.stream().map(Vote::getVoteId).map(Object::toString)
×
232
              .collect(Collectors.joining(","));
×
233
          String message =
×
234
              "Error notifying researchers and custodians for votes: [" + voteIds + "]: "
235
                  + e.getMessage();
×
236
          logException(message, e);
×
237
        }
1✔
238
      }
239
      return updatedVotes;
1✔
240
    } catch (SQLException e) {
×
241
      throw new IllegalArgumentException("Unable to update election votes.");
×
242
    }
243
  }
244

245
  /**
246
   * Review all positive, FINAL votes and send a notification to the researcher and data custodians
247
   * describing the approved access to datasets on their Data Access Request.
248
   *
249
   * @param votes List of Vote objects. In practice, this will be a batch of votes for a group of
250
   *              elections for datasets that all have the same data use restriction in a single
251
   *              DarCollection. This method is flexible enough to send email for any number of
252
   *              unrelated elections in various DarCollections.
253
   * @param user  The user sending approval notifications
254
   */
255
  public void sendDatasetApprovalNotifications(List<Vote> votes, User user) {
256

257
    List<Integer> finalElectionIds = votes.stream()
1✔
258
        .filter(Vote::getVote) // Safety check to ensure we're only emailing for approved election
1✔
259
        .filter(v -> VoteType.FINAL.getValue().equalsIgnoreCase(v.getType()))
1✔
260
        .map(Vote::getElectionId)
1✔
261
        .distinct()
1✔
262
        .collect(Collectors.toList());
1✔
263

264
    List<Election> finalElections = electionDAO.findElectionsByIds(finalElectionIds);
1✔
265

266
    List<String> finalElectionReferenceIds = finalElections.stream()
1✔
267
        .map(Election::getReferenceId)
1✔
268
        .distinct()
1✔
269
        .collect(Collectors.toList());
1✔
270

271
    List<DataAccessRequest> dars = dataAccessRequestDAO.findByReferenceIds(finalElectionReferenceIds);
1✔
272

273

274
    List<Integer> datasetIds = finalElections.stream()
1✔
275
        .map(Election::getDatasetId)
1✔
276
        .collect(Collectors.toList());
1✔
277
    List<Dataset> datasets =
278
        datasetIds.isEmpty() ? List.of() : datasetDAO.findDatasetsByIdList(datasetIds);
1✔
279

280
    try {
281
      elasticSearchService.indexDatasets(datasetIds, user);
1✔
282
    } catch (Exception e) {
×
283
      logException("Error indexing datasets for approved DARs: " + e.getMessage(), e);
×
284
    }
1✔
285

286
    // For each dar, email the researcher summarizing the approved datasets in that dar
287
    dars.forEach(dar -> {
1✔
288
      // Get the datasets in this collection that have been approved
289
      List<Dataset> approvedDatasetsInDar = datasets.stream()
1✔
290
          .filter(d -> dar.getDatasetIds().contains(d.getDatasetId()))
1✔
291
          .toList();
1✔
292

293
      if (!approvedDatasetsInDar.isEmpty()) {
1✔
294
        String darCode = dar.getDarCode();
1✔
295
        User researcher = userDAO.findUserById(dar.getUserId());
1✔
296
        Integer researcherId = researcher.getUserId();
1✔
297
        List<DatasetMailDTO> datasetMailDTOs = approvedDatasetsInDar
1✔
298
            .stream()
1✔
299
            .map(d -> new DatasetMailDTO(d.getName(), d.getDatasetIdentifier()))
1✔
300
            .toList();
1✔
301

302
        // Get all Data Use translations, distinctly in the case that there are several with the same
303
        // data use, and then conjoin them for email display.
304
        String translation = approvedDatasetsInDar.stream()
1✔
305
            .map(dataset -> useRestrictionConverter.translateDataUse(dataset.getDataUse(), DataUseTranslationType.DATASET))
1✔
306
            .distinct()
1✔
307
            .collect(Collectors.joining(";"));
1✔
308

309
        try {
310
          if(dar.getProgressReport()) {
1✔
311
            emailService.sendResearcherProgressReportApproved(darCode, researcherId, datasetMailDTOs,
1✔
312
                translation);
313
          } else {
314
            emailService.sendResearcherDarApproved(darCode, researcherId, datasetMailDTOs,
1✔
315
                translation);
316
          }
317
        } catch (Exception e) {
×
318
          logException("Error sending researcher dar approved email: " + e.getMessage(), e);
×
319
        }
1✔
320
        try {
NEW
321
          notifyCustodiansOfApprovedDatasets(approvedDatasetsInDar, researcher, darCode);
×
322
        } catch (Exception e) {
1✔
323
          logException("Error notifying custodians of dar approved email: " + e.getMessage(), e);
1✔
324
        }
×
325
      }
326
    });
1✔
327
  }
1✔
328

329
  /**
330
   * Notify all data submitters, custodians, depositors, and owners of a dataset approval.
331
   *
332
   * @param datasets   Requested datasets
333
   * @param researcher The approved researcher
334
   * @param darCode    The DAR Collection Code
335
   * @throws IllegalArgumentException when there are no custodians or depositors to notify
336
   */
337
  protected void notifyCustodiansOfApprovedDatasets(List<Dataset> datasets, User researcher,
338
      String darCode) throws IllegalArgumentException {
339
    Map<User, HashSet<Dataset>> custodianMap = new HashMap<>();
1✔
340

341
    // Find all the data custodians and submitters to notify for each dataset
342
    datasets.forEach(d -> {
1✔
343
      if (Objects.nonNull(d.getStudy())) {
1✔
344
        Study study = d.getStudy();
1✔
345

346
        // Data Submitter (study)
347
        if (Objects.nonNull(study.getCreateUserId())) {
1✔
348
          User submitter = userDAO.findUserById(study.getCreateUserId());
1✔
349
          if (Objects.nonNull(submitter)) {
1✔
350
            custodianMap.putIfAbsent(submitter, new HashSet<>());
1✔
351
            custodianMap.get(submitter).add(d);
1✔
352
          }
353
        }
354

355
        // Data Custodian (study)
356
        if (Objects.nonNull(study.getProperties())) {
1✔
357
          Type listOfStringType = new TypeToken<ArrayList<String>>() {}.getType();
1✔
358
          Gson gson = GsonUtil.gsonBuilderWithAdapters().create();
1✔
359
          Set<StudyProperty> props = study.getProperties();
1✔
360
          List<String> custodianEmails = new ArrayList<>();
1✔
361
          props.stream()
1✔
362
              .filter(p -> p.getKey().equals(DatasetRegistrationSchemaV1Builder.dataCustodianEmail))
1✔
363
              .forEach(p -> {
1✔
364
                String propValue = p.getValue().toString();
1✔
365
                try {
366
                  custodianEmails.addAll(gson.fromJson(propValue, listOfStringType));
1✔
367
                } catch (Exception e) {
×
368
                  logException("Error finding data custodians for study: " + study.getStudyId(), e);
×
369
                }
1✔
370
              });
1✔
371
          if (!custodianEmails.isEmpty()) {
1✔
372
            List<User> custodianUsers = userDAO.findUsersByEmailList(custodianEmails);
1✔
373
            custodianUsers.forEach(s -> {
1✔
374
              custodianMap.putIfAbsent(s, new HashSet<>());
1✔
375
              custodianMap.get(s).add(d);
1✔
376
            });
1✔
377
          }
378
        }
379
      }
380

381
      // Data Submitter (dataset)
382
      if (Objects.nonNull(d.getCreateUserId())) {
1✔
383
        User submitter = userDAO.findUserById(d.getCreateUserId());
1✔
384
        if (Objects.nonNull(submitter)) {
1✔
385
          custodianMap.putIfAbsent(submitter, new HashSet<>());
1✔
386
          custodianMap.get(submitter).add(d);
1✔
387
        }
388
      }
389
    });
1✔
390

391
    // Filter out invalid emails in custodian map
392
    EmailValidator emailValidator = EmailValidator.getInstance();
1✔
393
    Map<User, HashSet<Dataset>> validCustodians = custodianMap.entrySet().stream()
1✔
394
        .filter(e -> e.getKey().getEmail() != null && emailValidator.isValid(e.getKey().getEmail()))
1✔
395
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, HashMap::new));
1✔
396

397
    if (validCustodians.isEmpty()) {
1✔
398
      String identifiers = datasets.stream().map(Dataset::getDatasetIdentifier)
1✔
399
          .collect(Collectors.joining(", "));
1✔
400
      throw new IllegalArgumentException(
1✔
401
          "No submitters, custodians, owners, or depositors found for provided dataset identifiers: "
402
              + identifiers);
403
    }
404
    // For each custodian, notify them of their approved datasets
405
    for (Map.Entry<User, HashSet<Dataset>> entry : validCustodians.entrySet()) {
1✔
406
      List<DatasetMailDTO> datasetMailDTOs = entry.getValue()
1✔
407
          .stream()
1✔
408
          .map(d -> new DatasetMailDTO(d.getName(), d.getDatasetIdentifier()))
1✔
409
          .toList();
1✔
410
      try {
411
        emailService.sendDataCustodianApprovalMessage(
1✔
412
            entry.getKey(),
1✔
413
            darCode,
414
            datasetMailDTOs,
415
            entry.getKey().getDisplayName(),
1✔
416
            researcher.getEmail());
1✔
417
      } catch (Exception e) {
×
418
        logException("Error sending custodian approval email: " + e.getMessage(), e);
×
419
      }
1✔
420
    }
1✔
421
  }
1✔
422

423
  /**
424
   * The Rationale for RP Votes can be updated for any election status. The Rationale for DataAccess
425
   * Votes can only be updated for OPEN elections. Votes for elections of other types are not
426
   * updatable through this method.
427
   *
428
   * @param voteIds   List of vote ids for DataAccess and RP elections
429
   * @param rationale The rationale to update
430
   * @return List of updated votes
431
   * @throws IllegalArgumentException when there are non-open, non-rp elections on any of the votes
432
   */
433
  public List<Vote> updateRationaleByVoteIds(List<Integer> voteIds, String rationale)
434
      throws IllegalArgumentException {
435
    List<Vote> votes = voteDAO.findVotesByIds(voteIds);
1✔
436
    validateVotesCanUpdate(votes);
1✔
437
    voteDAO.updateRationaleByVoteIds(voteIds, rationale);
1✔
438
    return findVotesByIds(voteIds);
1✔
439
  }
440

441
  private void validateVotesCanUpdate(List<Vote> votes) throws IllegalArgumentException {
442
    List<Election> elections = electionDAO.findElectionsByIds(votes.stream()
1✔
443
        .map(Vote::getElectionId)
1✔
444
        .collect(Collectors.toList()));
1✔
445

446
    // If there are any DataAccess elections in a non-open state, throw an error
447
    List<Election> nonOpenAccessElections = elections.stream()
1✔
448
        .filter(election -> election.getElectionType().equals(ElectionType.DATA_ACCESS.getValue()))
1✔
449
        .filter(election -> !election.getStatus().equals(ElectionStatus.OPEN.getValue()))
1✔
450
        .toList();
1✔
451
    if (!nonOpenAccessElections.isEmpty()) {
1✔
452
      throw new IllegalArgumentException(
1✔
453
          "There are non-open Data Access elections for provided votes");
454
    }
455

456
    // If there are non-DataAccess or non-RP elections, throw an error
457
    List<Election> disallowedElections = elections.stream()
1✔
458
        .filter(election -> !election.getElectionType().equals(ElectionType.DATA_ACCESS.getValue()))
1✔
459
        .filter(election -> !election.getElectionType().equals(ElectionType.RP.getValue()))
1✔
460
        .toList();
1✔
461
    if (!disallowedElections.isEmpty()) {
1✔
462
      throw new IllegalArgumentException(
1✔
463
          "There are non-Data Access/RP elections for provided votes");
464
    }
465
  }
1✔
466

467
  private boolean isDacChairPerson(Dac dac, User user) {
468
    if (dac != null) {
1✔
469
      return user.getRoles().
×
470
          stream().
×
471
          anyMatch(userRole -> Objects.nonNull(userRole.getRoleId()) &&
×
472
              Objects.nonNull(userRole.getDacId()) &&
×
473
              userRole.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId()) &&
×
474
              userRole.getDacId().equals(dac.getDacId()));
×
475
    }
476
    return user.getRoles().
1✔
477
        stream().
1✔
478
        anyMatch(userRole -> Objects.nonNull(userRole.getRoleId()) &&
1✔
479
            userRole.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId()));
1✔
480
  }
481

482
  /**
483
   * Convenience method to ensure Vote non-nullable values are populated
484
   *
485
   * @param vote The Vote to validate
486
   */
487
  private void validateVote(Vote vote) {
488
    if (Objects.isNull(vote) ||
1✔
489
        Objects.isNull(vote.getVoteId()) ||
1✔
490
        Objects.isNull(vote.getUserId()) ||
1✔
491
        Objects.isNull(vote.getElectionId())) {
1✔
492
      throw new IllegalArgumentException("Invalid vote: " + vote);
×
493
    }
494
    if (Objects.isNull(voteDAO.findVoteById(vote.getVoteId()))) {
1✔
495
      throw new IllegalArgumentException("No vote exists with the id of " + vote.getVoteId());
×
496
    }
497
  }
1✔
498

499
  private void notFoundException(Integer voteId) {
500
    throw new NotFoundException("Could not find vote for specified id. Vote id: " + voteId);
1✔
501
  }
502

503
  public void logDARApprovalOrRejection(User user, List<Vote> updatedVotes,
504
      ContainerRequest request) {
505
    List<Integer> approvedElectionIds = updatedVotes.stream()
1✔
506
        .filter(v -> v.getType().equals(VoteType.FINAL.getValue()))
1✔
507
        .filter(Vote::getVote)
1✔
508
        .map(Vote::getElectionId)
1✔
509
        .toList();
1✔
510
    List<Integer> approvedDatasetIds = electionDAO.findElectionsByIds(approvedElectionIds).stream()
1✔
511
        .map(Election::getDatasetId).toList();
1✔
512
    List<Dataset> approvedDatasets = datasetDAO.findDatasetsByIdList(approvedDatasetIds);
1✔
513
    ComplianceLogger.logDARApproval(user, approvedDatasets, request,
1✔
514
        HttpStatusCodes.STATUS_CODE_OK);
515

516
    List<Integer> rejectedElectionIds = updatedVotes.stream()
1✔
517
        .filter(v -> v.getType().equals(VoteType.FINAL.getValue()))
1✔
518
        .filter(not(Vote::getVote))
1✔
519
        .map(Vote::getElectionId)
1✔
520
        .toList();
1✔
521
    List<Integer> rejectedDatasetIds = electionDAO.findElectionsByIds(rejectedElectionIds).stream()
1✔
522
        .map(Election::getDatasetId).toList();
1✔
523
    List<Dataset> rejectedDatasets = datasetDAO.findDatasetsByIdList(rejectedDatasetIds);
1✔
524
    ComplianceLogger.logDARRejection(user, rejectedDatasets, request, HttpStatusCodes.STATUS_CODE_OK);
1✔
525
  }
1✔
526

527
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc