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

DataBiosphere / consent / #5933

19 May 2025 02:06PM UTC coverage: 78.602% (+0.05%) from 78.551%
#5933

push

web-flow
[DT-1606] Enforce institutional parity (#2529)

45 of 47 new or added lines in 2 files covered. (95.74%)

10065 of 12805 relevant lines covered (78.6%)

0.79 hits per line

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

96.64
/src/main/java/org/broadinstitute/consent/http/service/DataAccessRequestService.java
1
package org.broadinstitute.consent.http.service;
2

3
import com.google.common.annotations.VisibleForTesting;
4
import com.google.inject.Inject;
5
import freemarker.template.TemplateException;
6
import jakarta.ws.rs.BadRequestException;
7
import jakarta.ws.rs.NotAcceptableException;
8
import jakarta.ws.rs.NotFoundException;
9
import java.io.IOException;
10
import java.util.ArrayList;
11
import java.sql.SQLException;
12
import java.sql.Timestamp;
13
import java.time.Instant;
14
import java.time.LocalDate;
15
import java.time.LocalTime;
16
import java.time.ZoneOffset;
17
import java.util.Collection;
18
import java.util.Date;
19
import java.util.List;
20
import java.util.Objects;
21
import java.util.Set;
22
import java.util.UUID;
23
import org.apache.commons.validator.routines.EmailValidator;
24
import org.broadinstitute.consent.http.configurations.ConsentConfiguration;
25
import org.broadinstitute.consent.http.db.DAOContainer;
26
import org.broadinstitute.consent.http.db.DarCollectionDAO;
27
import org.broadinstitute.consent.http.db.DataAccessRequestDAO;
28
import org.broadinstitute.consent.http.db.ElectionDAO;
29
import org.broadinstitute.consent.http.db.MatchDAO;
30
import org.broadinstitute.consent.http.db.UserDAO;
31
import org.broadinstitute.consent.http.db.VoteDAO;
32
import org.broadinstitute.consent.http.enumeration.EmailType;
33
import org.broadinstitute.consent.http.enumeration.UserRoles;
34
import org.broadinstitute.consent.http.exceptions.InvalidEmailAddressException;
35
import org.broadinstitute.consent.http.exceptions.LibraryCardRequiredException;
36
import org.broadinstitute.consent.http.exceptions.NIHComplianceRuleException;
37
import org.broadinstitute.consent.http.exceptions.SubmittedDARCannotBeEditedException;
38
import org.broadinstitute.consent.http.mail.message.ReminderMessage;
39
import org.broadinstitute.consent.http.models.Collaborator;
40
import org.broadinstitute.consent.http.models.DarCollection;
41
import org.broadinstitute.consent.http.models.DarDataset;
42
import org.broadinstitute.consent.http.models.DataAccessRequest;
43
import org.broadinstitute.consent.http.models.DataAccessRequestData;
44
import org.broadinstitute.consent.http.models.Dataset;
45
import org.broadinstitute.consent.http.models.Election;
46
import org.broadinstitute.consent.http.models.Institution;
47
import org.broadinstitute.consent.http.models.LibraryCard;
48
import org.broadinstitute.consent.http.models.User;
49
import org.broadinstitute.consent.http.models.Vote;
50
import org.broadinstitute.consent.http.service.dao.DataAccessRequestServiceDAO;
51
import org.broadinstitute.consent.http.util.ConsentLogger;
52
import org.jdbi.v3.core.statement.UnableToExecuteStatementException;
53

54
public class DataAccessRequestService implements ConsentLogger {
55
  public static final String EXPIRE_WARN_INTERVAL = "11 months";
56
  public static final String EXPIRE_NOTICE_INTERVAL = "1 year";
57
  protected static final Timestamp MINIMUM_SUBMITTED_DATE_FOR_DAR_EXPIRATIONS = Timestamp.from(
1✔
58
      Instant.ofEpochSecond(
1✔
59
          LocalDate.of(2024, 9, 30).toEpochSecond(LocalTime.of(0, 0, 0, 0), ZoneOffset.UTC)));
1✔
60
  private final CounterService counterService;
61
  private final DataAccessRequestDAO dataAccessRequestDAO;
62
  private final DarCollectionDAO darCollectionDAO;
63
  private final ElectionDAO electionDAO;
64
  private final InstitutionService institutionService;
65
  private final EmailService emailService;
66
  private final MatchDAO matchDAO;
67
  private final VoteDAO voteDAO;
68
  private final UserDAO userDAO;
69
  private final UserService userService;
70
  private final DataAccessRequestServiceDAO dataAccessRequestServiceDAO;
71

72
  private final DacService dacService;
73
  private final String serverUrl;
74

75
  @Inject
76
  public DataAccessRequestService(CounterService counterService, DAOContainer container,
77
      DacService dacService, DataAccessRequestServiceDAO dataAccessRequestServiceDAO, UserService userService, InstitutionService institutionService, EmailService emailService, ConsentConfiguration config) {
1✔
78
    this.counterService = counterService;
1✔
79
    this.dataAccessRequestDAO = container.getDataAccessRequestDAO();
1✔
80
    this.darCollectionDAO = container.getDarCollectionDAO();
1✔
81
    this.electionDAO = container.getElectionDAO();
1✔
82
    this.matchDAO = container.getMatchDAO();
1✔
83
    this.voteDAO = container.getVoteDAO();
1✔
84
    this.userDAO = container.getUserDAO();
1✔
85
    this.dacService = dacService;
1✔
86
    this.dataAccessRequestServiceDAO = dataAccessRequestServiceDAO;
1✔
87
    this.userService = userService;
1✔
88
    this.institutionService = institutionService;
1✔
89
    this.emailService = emailService;
1✔
90
    this.serverUrl = config.getServicesConfiguration().getLocalURL();
1✔
91
  }
1✔
92

93
  public List<DataAccessRequest> findAllDraftDataAccessRequests() {
94
    return dataAccessRequestDAO.findAllDraftDataAccessRequests();
1✔
95
  }
96

97
  public List<DataAccessRequest> findAllDraftDataAccessRequestsByUser(Integer userId) {
98
    return dataAccessRequestDAO.findAllDraftsByUserId(userId);
1✔
99
  }
100

101
  public void deleteByReferenceId(User user, String referenceId) throws NotAcceptableException {
102
    List<Election> elections = electionDAO.findElectionsByReferenceId(referenceId);
1✔
103
    if (!elections.isEmpty()) {
1✔
104
      // If the user is an admin, delete all votes and elections
105
      if (user.hasUserRole(UserRoles.ADMIN)) {
1✔
106
        voteDAO.deleteVotesByReferenceId(referenceId);
1✔
107
        List<Integer> electionIds = elections.stream().map(Election::getElectionId).toList();
1✔
108
        electionDAO.deleteElectionsByIds(electionIds);
1✔
109
      } else {
1✔
110
        String message = String.format(
1✔
111
            "Unable to delete DAR: '%s', there are existing elections that reference it.",
112
            referenceId);
113
        logWarn(message);
1✔
114
        throw new NotAcceptableException(message);
1✔
115
      }
116
    }
117
    matchDAO.deleteRationalesByPurposeIds(List.of(referenceId));
1✔
118
    matchDAO.deleteMatchesByPurposeId(referenceId);
1✔
119
    dataAccessRequestDAO.deleteDARDatasetRelationByReferenceId(referenceId);
1✔
120
    dataAccessRequestDAO.deleteByReferenceId(referenceId);
1✔
121
  }
1✔
122

123
  public DataAccessRequest findByReferenceId(String referencedId) {
124
    DataAccessRequest dar = dataAccessRequestDAO.findByReferenceId(referencedId);
1✔
125
    if (Objects.isNull(dar)) {
1✔
126
      throw new NotFoundException("There does not exist a DAR with the given reference Id");
×
127
    }
128
    return dar;
1✔
129
  }
130

131
  //NOTE: rewrite method into new servicedao method on another ticket
132
  public DataAccessRequest insertDraftDataAccessRequest(User user, DataAccessRequest dar) {
133
    if (Objects.isNull(user) || Objects.isNull(dar) || Objects.isNull(
1✔
134
        dar.getReferenceId()) || Objects.isNull(dar.getData())) {
1✔
135
      throw new IllegalArgumentException("User and DataAccessRequest are required");
1✔
136
    }
137

138
    if (user.getLibraryCards().isEmpty()) {
1✔
139
      throw new LibraryCardRequiredException();
×
140
    }
141

142
    Date now = new Date();
1✔
143
    dataAccessRequestDAO.insertDraftDataAccessRequest(
1✔
144
        dar.getReferenceId(),
1✔
145
        user.getUserId(),
1✔
146
        now,
147
        now,
148
        now,
149
        dar.getData()
1✔
150
    );
151
    syncDataAccessRequestDatasets(dar.getDatasetIds(), dar.getReferenceId());
1✔
152

153
    return findByReferenceId(dar.getReferenceId());
1✔
154
  }
155

156
  /**
157
   * First delete any rows with the current reference id. This will allow us to keep (referenceId,
158
   * dataset_id) unique Takes in a list of datasetIds and a referenceId and adds them to the
159
   * dar_dataset collection
160
   *
161
   * @param datasetIds  List of Integers that represent the datasetIds
162
   * @param referenceId ReferenceId of the corresponding DAR
163
   */
164
  private void syncDataAccessRequestDatasets(List<Integer> datasetIds, String referenceId) {
165
    List<DarDataset> darDatasets = datasetIds.stream()
1✔
166
        .map(datasetId -> new DarDataset(referenceId, datasetId))
1✔
167
        .toList();
1✔
168
    dataAccessRequestDAO.deleteDARDatasetRelationByReferenceId(referenceId);
1✔
169

170
    if (!darDatasets.isEmpty()) {
1✔
171
      dataAccessRequestDAO.insertAllDarDatasets(darDatasets);
1✔
172
    }
173
  }
1✔
174

175
  /**
176
   * @param user User
177
   * @return List<DataAccessRequest>
178
   */
179
  public List<DataAccessRequest> getDataAccessRequestsByUserRole(User user) {
180
    List<DataAccessRequest> dars = dataAccessRequestDAO.findAllDataAccessRequests();
1✔
181
    return dacService.filterDataAccessRequestsByDac(dars, user);
1✔
182
  }
183

184
  /**
185
   * Generate a DataAccessRequest from the provided DAR. The provided DAR may or may not exist in
186
   * draft form, so it covers both cases of converting an existing draft to submitted and creating a
187
   * brand new DAR from scratch.
188
   *
189
   * @param user              The create User
190
   * @param dataAccessRequest DataAccessRequest with populated DAR data
191
   * @return The created DAR.
192
   */
193
  public DataAccessRequest createDataAccessRequest(User user, DataAccessRequest dataAccessRequest) {
194
    validateDar(user, dataAccessRequest);
1✔
195

196
    Date now = new Date();
1✔
197
    DataAccessRequestData darData = dataAccessRequest.getData();
1✔
198

199
    DataAccessRequest existingDar = dataAccessRequestDAO.findByReferenceId(
1✔
200
        dataAccessRequest.getReferenceId());
1✔
201
    if (existingDar != null && !existingDar.getDraft()) {
1✔
202
      throw new SubmittedDARCannotBeEditedException();
1✔
203
    }
204
    Integer collectionId;
205
    // Only create a new DarCollection if we haven't done so already
206
    if (Objects.nonNull(existingDar) && Objects.nonNull(existingDar.getCollectionId())) {
1✔
207
      collectionId = existingDar.getCollectionId();
×
208
    } else {
209
      String darCodeSequence = "DAR-" + counterService.getNextDarSequence();
1✔
210
      collectionId = darCollectionDAO.insertDarCollection(darCodeSequence, user.getUserId(), now);
1✔
211
    }
212
    String referenceId;
213
    List<Integer> datasetIds = dataAccessRequest.getDatasetIds();
1✔
214
    if (Objects.nonNull(existingDar)) {
1✔
215
      referenceId = dataAccessRequest.getReferenceId();
1✔
216
      dataAccessRequestDAO.updateDraftToSubmittedForCollection(collectionId,
1✔
217
          referenceId);
218
      dataAccessRequestDAO.updateDataByReferenceId(
1✔
219
          referenceId,
220
          user.getUserId(),
1✔
221
          now,
222
          now,
223
          now,
224
          darData,
225
          user.getEraCommonsId());
1✔
226
    } else {
227
      referenceId = UUID.randomUUID().toString();
1✔
228
      dataAccessRequestDAO.insertDataAccessRequest(
1✔
229
          collectionId,
230
          referenceId,
231
          user.getUserId(),
1✔
232
          now,
233
          now,
234
          now,
235
          now,
236
          darData,
237
          user.getEraCommonsId());
1✔
238
    }
239
    syncDataAccessRequestDatasets(datasetIds, referenceId);
1✔
240
    return findByReferenceId(referenceId);
1✔
241
  }
242

243
  /**
244
   * Create a progress report for the given DataAccessRequest.
245
   * The parent DAR is just passed in for validation purposes.
246
   *
247
   * @param user              The User
248
   * @param progressReport    The DataAccessRequest
249
   * @param parentDar         The parent DataAccessRequest
250
   * @return The created progress report.
251
   */
252
  public DataAccessRequest createProgressReport(User user, DataAccessRequest progressReport, DataAccessRequest parentDar) {
253
    validateProgressReport(user, progressReport, parentDar);
1✔
254

255
    String referenceId = progressReport.getReferenceId();
1✔
256
    List<Integer> progressReportDatasetIds = progressReport.getDatasetIds();
1✔
257
    Set<Integer> darDatasetIds = dataAccessRequestDAO.findDatasetApprovalsByDars(List.of(parentDar.getReferenceId()));
1✔
258
    if (!darDatasetIds.containsAll(progressReportDatasetIds)) {
1✔
259
      throw new BadRequestException("Progress report can only be created for approved datasets in the parent DAR");
1✔
260
    }
261
    dataAccessRequestDAO.insertProgressReport(
1✔
262
          progressReport.getParentId(),
1✔
263
          progressReport.getCollectionId(),
1✔
264
          referenceId,
265
          user.getUserId(),
1✔
266
          progressReport.getData());
1✔
267
    syncDataAccessRequestDatasets(progressReportDatasetIds, referenceId);
1✔
268
    return findByReferenceId(referenceId);
1✔
269
  }
270

271
  public void validateProgressReport(User user, DataAccessRequest progressReport, DataAccessRequest parentDar) {
272
    validateDar(user, progressReport);
1✔
273
    if (parentDar.getDraft()) {
1✔
274
      throw new BadRequestException(
1✔
275
          "Cannot create a progress report for a draft Data Access Request");
276
    }
277
    if (progressReport.getDatasetIds() == null || progressReport.getDatasetIds().isEmpty() ) {
1✔
278
      throw new BadRequestException("At least one dataset is required");
1✔
279
    }
280
    if (!parentDar.getDatasetIds().containsAll(progressReport.getDatasetIds())) {
1✔
281
      throw new BadRequestException("Progress report can only be created for datasets in the parent DAR");
1✔
282
    }
283
    if (progressReport.getData().getProgressReportSummary() == null ||
1✔
284
        progressReport.getData().getProgressReportSummary().isEmpty()) {
1✔
285
      throw new BadRequestException("Progress report summary is required");
1✔
286
    }
287
    if (progressReport.getData().getIntellectualPropertySummary() == null ||
1✔
288
        progressReport.getData().getIntellectualPropertySummary().isEmpty()) {
1✔
289
      throw new BadRequestException("Intellectual Property Summary is required");
1✔
290
    }
291
  }
1✔
292

293
  public void validateDar(User user, DataAccessRequest dar) {
294
    if (Objects.isNull(user) || Objects.isNull(dar) || Objects.isNull(
1✔
295
        dar.getReferenceId()) || Objects.isNull(dar.getData())) {
1✔
296
      throw new IllegalArgumentException("User and DataAccessRequest are required");
1✔
297
    }
298

299
    if (user.getLibraryCards().isEmpty()) {
1✔
300
      throw new NIHComplianceRuleException();
1✔
301
    }
302

303
    userService.hasValidActiveERACredentials(user);
1✔
304

305
    validateInternalCollaborators(dar);
1✔
306
    validateNoKeyPersonnelDuplicates(dar.getData());
1✔
307
    validatePersonnelInSameInstitution(user, dar.getData());
1✔
308
  }
1✔
309

310
  @VisibleForTesting
311
  public void validateInternalCollaborators(DataAccessRequest payload) {
312
    List<Collaborator> internalCollaborators = payload.getData().getInternalCollaborators();
1✔
313
    for (Collaborator collaborator : internalCollaborators) {
1✔
314
      User collabUser = userDAO.findUserByEmail(collaborator.getEmail());
1✔
315
      if (collabUser == null) {
1✔
316
        throw new NotFoundException(
1✔
317
            "Unable to find User with the provided email: " + collaborator.getEmail());
1✔
318
      }
319
      List<LibraryCard> libraryCards = collabUser.getLibraryCards();
1✔
320
      if (libraryCards.isEmpty()) {
1✔
321
        throw new BadRequestException(
1✔
322
            "Collaborator " + collaborator.getEmail() + " does not have a library card.");
1✔
323
      }
324
    }
1✔
325
  }
1✔
326

327
  /**
328
   * Update an existing DataAccessRequest. Replaces DataAccessRequestData.
329
   *
330
   * @param user The User
331
   * @param dar  The DataAccessRequest
332
   * @return The updated DataAccessRequest
333
   */
334
  public DataAccessRequest updateByReferenceId(User user, DataAccessRequest dar) {
335
    if (!dar.getDraft()) {
1✔
336
      throw new SubmittedDARCannotBeEditedException();
1✔
337
    }
338
    try {
339
      return dataAccessRequestServiceDAO.updateByReferenceId(user, dar);
1✔
340
    } catch (SQLException e) {
×
341
      // If I simply rethrow the error then I'll have to redefine any method that
342
      // calls this function to "throw SQLException"
343
      //Instead I'm going to throw an UnableToExecuteStatementException
344
      //Response class will catch it, log it, and throw a 500 through the "unableToExecuteExceptionHandler"
345
      //on the Resource class, just like it would with a SQLException
346
      throw new UnableToExecuteStatementException(e.getMessage());
×
347
    }
348
  }
349

350
  /**
351
   * Validates that PI email is not duplicated with SO or IT Director emails
352
   *
353
   * @param darData The data access request data to validate
354
   * @throws IllegalArgumentException if duplicate emails are found
355
   */
356
  public void validateNoKeyPersonnelDuplicates(DataAccessRequestData darData) {
357
    EmailValidator emailValidator = EmailValidator.getInstance();
1✔
358

359
    String piEmail = darData.getPiEmail();
1✔
360
    String soEmail = darData.getSigningOfficialEmail();
1✔
361
    String itEmail = darData.getItDirectorEmail();
1✔
362

363
    if (!emailValidator.isValid(piEmail) || !emailValidator.isValid(soEmail)
1✔
364
        || !emailValidator.isValid(itEmail)) {
1✔
365
      throw new IllegalArgumentException(
1✔
366
          "Principal Investigator, Signing Official, and IT Director emails must be valid");
367
    }
368

369
    if (piEmail.equalsIgnoreCase(soEmail)) {
1✔
370
      throw new IllegalArgumentException(
1✔
371
          "Principal Investigator email cannot be the same as Signing Official email");
372
    }
373

374
    if (piEmail.equalsIgnoreCase(itEmail)) {
1✔
375
      throw new IllegalArgumentException(
1✔
376
          "Principal Investigator email cannot be the same as IT Director email");
377
    }
378
  }
1✔
379

380
  @VisibleForTesting
381
  protected void validatePersonnelInSameInstitution(User user, DataAccessRequestData darData) {
382
    Institution submitterInstitution = user.getInstitution();
1✔
383
    String piEmail = darData.getPiEmail();
1✔
384
    String soEmail = darData.getSigningOfficialEmail();
1✔
385
    String itEmail = darData.getItDirectorEmail();
1✔
386
    List<String> collaboratorsEmails =
1✔
387
        darData.getInternalCollaborators().stream().map(Collaborator::getEmail).toList();
1✔
388
    List<String> labStaffEmails =
1✔
389
        darData.getLabCollaborators().stream().map(Collaborator::getEmail).toList();
1✔
390

391
    List<String> invalidMembers = new ArrayList<>();
1✔
392

393
    verifyInstitution(submitterInstitution, piEmail, "Principal Investigator", invalidMembers);
1✔
394
    verifyInstitution(submitterInstitution, soEmail, "Signing Official", invalidMembers);
1✔
395
    verifyInstitution(submitterInstitution, itEmail, "IT Director", invalidMembers);
1✔
396

397
    getErrorSummary(
1✔
398
            collaboratorsEmails,
399
            submitterInstitution,
400
            "Internal Collaborator member: ",
401
            "Internal Collaborator members: ", invalidMembers);
402

403
    getErrorSummary(
1✔
404
            labStaffEmails, submitterInstitution, "Lab staff member: ", "Lab staff members: ", invalidMembers);
405

406
    if (!invalidMembers.isEmpty()) {
1✔
407
      throw new IllegalArgumentException(
1✔
408
          "All listed personnel must share the same institutional affiliation.  The following list of roles and members must have email addresses associated with your institution: "
409
              + String.join(", ", invalidMembers));
1✔
410
    }
411
  }
1✔
412

413
  private void verifyInstitution(Institution submitterInstitution, String email, String role, List<String> invalidMembers) {
414
    if (emailDoesNotMatchInstitution(submitterInstitution, email)) {
1✔
415
      invalidMembers.add(role + ": " + email);
1✔
416
    }
417
  }
1✔
418

419
  private void getErrorSummary(
420
      List<String> emails,
421
      Institution institution,
422
      String categorySingular,
423
      String categoryPlural,
424
      List<String> invalidMembers) {
425
    List<String> errors = findEmailAddressesNotInInstitution(emails, institution);
1✔
426
    if (!errors.isEmpty()) {
1✔
427
      invalidMembers.add(buildSingleErrorFromErrorList(errors, categorySingular, categoryPlural));
1✔
428
    }
429
  }
1✔
430

431
  private List<String> findEmailAddressesNotInInstitution(
432
      List<String> emailAddresses, Institution institution) {
433
    ArrayList<String> errors = new ArrayList<>();
1✔
434
    emailAddresses.forEach(
1✔
435
        collaborator -> {
436
          if (emailDoesNotMatchInstitution(institution, collaborator)) {
1✔
437
            errors.add(collaborator);
1✔
438
          }
439
        });
1✔
440
    return errors;
1✔
441
  }
442

443
  private String buildSingleErrorFromErrorList(
444
      List<String> errors, String categorySingular, String categoryPlural) {
445
    StringBuilder msg = new StringBuilder();
1✔
446
    if (errors.size() == 1) {
1✔
447
      msg.append(categorySingular);
1✔
NEW
448
    } else if (errors.size() > 1) {
×
NEW
449
      msg.append(categoryPlural);
×
450
    }
451
    msg.append(String.join(", ", errors));
1✔
452
    return msg.toString();
1✔
453
  }
454

455
  private boolean emailDoesNotMatchInstitution(Institution institution, String email) {
456
    Institution foundInstitution = institutionService.findInstitutionForEmail(email);
1✔
457
    if (foundInstitution == null || institution == null) {
1✔
458
      return true;
1✔
459
    }
460
    return !institution.equals(foundInstitution);
1✔
461
  }
462

463
  public Collection<DataAccessRequest> getApprovedDARsForDataset(Dataset dataset) {
464
    return dataAccessRequestDAO.findApprovedDARsByDatasetId(dataset.getDatasetId());
1✔
465
  }
466

467
  public void sendExpirationNotices() {
468
    sendDARExpirationReminderNotices();
1✔
469
    sendDARExpirationNotices();
1✔
470
  }
1✔
471

472
  private void sendDARExpirationNotices() {
473
    EmailType emailType = EmailType.DAR_EXPIRED;
1✔
474
    sendDARMessageToList(emailType, EXPIRE_NOTICE_INTERVAL);
1✔
475
  }
1✔
476

477
  private void sendDARExpirationReminderNotices() {
478
    EmailType emailType = EmailType.DAR_EXPIRATION_REMINDER;
1✔
479
    sendDARMessageToList(emailType, EXPIRE_WARN_INTERVAL);
1✔
480
  }
1✔
481

482
  private void sendDARMessageToList(EmailType type, String interval) {
483
    List<DataAccessRequest> expiredDars =
1✔
484
        dataAccessRequestDAO.findAgedDARsByEmailTypeOlderThanInterval(
1✔
485
            type.getTypeInt(), interval, MINIMUM_SUBMITTED_DATE_FOR_DAR_EXPIRATIONS);
1✔
486
    expiredDars.forEach(
1✔
487
        expiredDar -> {
488
          try {
489
            String referenceId = expiredDar.getReferenceId();
1✔
490
            User user = userDAO.findUserById(expiredDar.getUserId());
1✔
491
            String darCode = expiredDar.getDarCode();
1✔
492
            String userName = user.getDisplayName();
1✔
493
            if (user.getEmail() == null) {
1✔
494
              throw new InvalidEmailAddressException(
1✔
495
                  String.format(
1✔
496
                      "Email address for user %d (%s) not found for expiring warning.  DAR reference id: %s",
497
                      expiredDar.getUserId(), userName, referenceId));
1✔
498
            }
499
            switch (type) {
1✔
500
              case DAR_EXPIRATION_REMINDER:
501
                emailService.sendDarExpirationReminderMessage(
1✔
502
                    user, darCode, user.getUserId(), referenceId);
1✔
503
                break;
1✔
504
              case DAR_EXPIRED:
505
                emailService.sendDarExpiredMessage(user, darCode, user.getUserId(), referenceId);
1✔
506
                break;
1✔
507
              default:
508
                break;
509
            }
510
          } catch (Exception e) {
1✔
511
            logException(e);
1✔
512
          }
1✔
513
        });
1✔
514
  }
1✔
515

516
  public void sendReminderMessage(Integer voteId) throws IOException, TemplateException {
517
    Vote vote = voteDAO.findVoteById(voteId);
1✔
518
    Election election = electionDAO.findElectionWithFinalVoteById(vote.getElectionId());
1✔
519
    DarCollection collection = darCollectionDAO.findDARCollectionByReferenceId(
1✔
520
        election.getReferenceId());
1✔
521
    User user = findUserById(vote.getUserId());
1✔
522
    String voteUrl = serverUrl + "dar_collection/%d".formatted(collection.getDarCollectionId());
1✔
523
    emailService.sendReminderMessage(user, vote, collection.getDarCode(), election.getElectionType(), voteUrl);
1✔
524
    voteDAO.updateVoteReminderFlag(voteId, true);
1✔
525
  }
1✔
526

527
  private User findUserById(Integer id) throws IllegalArgumentException {
528
    User user = userDAO.findUserById(id);
1✔
529
    if (user == null) {
1✔
530
      throw new NotFoundException("Could not find dacUser for specified id : " + id);
×
531
    }
532
    return user;
1✔
533
  }
534

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

© 2026 Coveralls, Inc