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

DataBiosphere / consent / #5676

17 Apr 2025 12:57PM UTC coverage: 79.034% (-0.04%) from 79.075%
#5676

push

web-flow
[DT-1502] Enforce Library card rule on all DAR creation; return specific Error text when this rule is being enforced. (#2481)

82 of 92 new or added lines in 8 files covered. (89.13%)

1 existing line in 1 file now uncovered.

10261 of 12983 relevant lines covered (79.03%)

0.79 hits per line

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

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

3
import com.google.gson.Gson;
4
import com.google.inject.Inject;
5
import jakarta.ws.rs.NotAcceptableException;
6
import jakarta.ws.rs.NotFoundException;
7
import java.sql.SQLException;
8
import java.util.ArrayList;
9
import java.util.Collection;
10
import java.util.Date;
11
import java.util.List;
12
import java.util.Objects;
13
import java.util.UUID;
14
import org.broadinstitute.consent.http.db.DAOContainer;
15
import org.broadinstitute.consent.http.db.DarCollectionDAO;
16
import org.broadinstitute.consent.http.db.DataAccessRequestDAO;
17
import org.broadinstitute.consent.http.db.DatasetDAO;
18
import org.broadinstitute.consent.http.db.ElectionDAO;
19
import org.broadinstitute.consent.http.db.InstitutionDAO;
20
import org.broadinstitute.consent.http.db.MatchDAO;
21
import org.broadinstitute.consent.http.db.UserDAO;
22
import org.broadinstitute.consent.http.db.VoteDAO;
23
import org.broadinstitute.consent.http.enumeration.DarStatus;
24
import org.broadinstitute.consent.http.enumeration.UserRoles;
25
import org.broadinstitute.consent.http.exceptions.LibraryCardRequiredException;
26
import org.broadinstitute.consent.http.exceptions.NIHComplianceRuleException;
27
import org.broadinstitute.consent.http.models.DarCollection;
28
import org.broadinstitute.consent.http.models.DarDataset;
29
import org.broadinstitute.consent.http.models.DataAccessRequest;
30
import org.broadinstitute.consent.http.models.DataAccessRequestData;
31
import org.broadinstitute.consent.http.models.Dataset;
32
import org.broadinstitute.consent.http.models.Election;
33
import org.broadinstitute.consent.http.models.User;
34
import org.broadinstitute.consent.http.service.dao.DataAccessRequestServiceDAO;
35
import org.broadinstitute.consent.http.util.ConsentLogger;
36
import org.jdbi.v3.core.statement.UnableToExecuteStatementException;
37

38
public class DataAccessRequestService implements ConsentLogger {
39

40
  private final CounterService counterService;
41
  private final DatasetDAO datasetDAO;
42
  private final DataAccessRequestDAO dataAccessRequestDAO;
43
  private final DarCollectionDAO darCollectionDAO;
44
  private final ElectionDAO electionDAO;
45
  private final MatchDAO matchDAO;
46
  private final UserDAO userDAO;
47
  private final VoteDAO voteDAO;
48
  private final InstitutionDAO institutionDAO;
49
  private final DataAccessRequestServiceDAO dataAccessRequestServiceDAO;
50

51
  private final DacService dacService;
52
  private final DataAccessReportsParser dataAccessReportsParser;
53

54
  @Inject
55
  public DataAccessRequestService(CounterService counterService, DAOContainer container,
56
      DacService dacService, DataAccessRequestServiceDAO dataAccessRequestServiceDAO,
57
      UseRestrictionConverter useRestrictionConverter) {
1✔
58
    this.counterService = counterService;
1✔
59
    this.dataAccessRequestDAO = container.getDataAccessRequestDAO();
1✔
60
    this.darCollectionDAO = container.getDarCollectionDAO();
1✔
61
    this.datasetDAO = container.getDatasetDAO();
1✔
62
    this.electionDAO = container.getElectionDAO();
1✔
63
    this.matchDAO = container.getMatchDAO();
1✔
64
    this.userDAO = container.getUserDAO();
1✔
65
    this.voteDAO = container.getVoteDAO();
1✔
66
    this.institutionDAO = container.getInstitutionDAO();
1✔
67
    this.dacService = dacService;
1✔
68
    this.dataAccessReportsParser = new DataAccessReportsParser(datasetDAO, useRestrictionConverter);
1✔
69
    this.dataAccessRequestServiceDAO = dataAccessRequestServiceDAO;
1✔
70
  }
1✔
71

72
  public List<DataAccessRequest> findAllDraftDataAccessRequests() {
73
    return dataAccessRequestDAO.findAllDraftDataAccessRequests();
1✔
74
  }
75

76
  public List<DataAccessRequest> findAllDraftDataAccessRequestsByUser(Integer userId) {
77
    return dataAccessRequestDAO.findAllDraftsByUserId(userId);
1✔
78
  }
79

80
  public void deleteByReferenceId(User user, String referenceId) throws NotAcceptableException {
81
    List<Election> elections = electionDAO.findElectionsByReferenceId(referenceId);
1✔
82
    if (!elections.isEmpty()) {
1✔
83
      // If the user is an admin, delete all votes and elections
84
      if (user.hasUserRole(UserRoles.ADMIN)) {
1✔
85
        voteDAO.deleteVotesByReferenceId(referenceId);
1✔
86
        List<Integer> electionIds = elections.stream().map(Election::getElectionId).toList();
1✔
87
        electionDAO.deleteElectionsByIds(electionIds);
1✔
88
      } else {
1✔
89
        String message = String.format(
1✔
90
            "Unable to delete DAR: '%s', there are existing elections that reference it.",
91
            referenceId);
92
        logWarn(message);
1✔
93
        throw new NotAcceptableException(message);
1✔
94
      }
95
    }
96
    matchDAO.deleteRationalesByPurposeIds(List.of(referenceId));
1✔
97
    matchDAO.deleteMatchesByPurposeId(referenceId);
1✔
98
    dataAccessRequestDAO.deleteDARDatasetRelationByReferenceId(referenceId);
1✔
99
    dataAccessRequestDAO.deleteByReferenceId(referenceId);
1✔
100
  }
1✔
101

102
  public DataAccessRequest findByReferenceId(String referencedId) {
103
    DataAccessRequest dar = dataAccessRequestDAO.findByReferenceId(referencedId);
1✔
104
    if (Objects.isNull(dar)) {
1✔
105
      throw new NotFoundException("There does not exist a DAR with the given reference Id");
×
106
    }
107
    return dar;
1✔
108
  }
109

110
  //NOTE: rewrite method into new servicedao method on another ticket
111
  public DataAccessRequest insertDraftDataAccessRequest(User user, DataAccessRequest dar) {
112
    if (Objects.isNull(user) || Objects.isNull(dar) || Objects.isNull(
1✔
113
        dar.getReferenceId()) || Objects.isNull(dar.getData())) {
1✔
114
      throw new IllegalArgumentException("User and DataAccessRequest are required");
1✔
115
    }
116

117
    if (user.getLibraryCards().isEmpty()) {
1✔
NEW
118
      throw new LibraryCardRequiredException();
×
119
    }
120

121
    Date now = new Date();
1✔
122
    dataAccessRequestDAO.insertDraftDataAccessRequest(
1✔
123
        dar.getReferenceId(),
1✔
124
        user.getUserId(),
1✔
125
        now,
126
        now,
127
        null,
128
        now,
129
        dar.getData()
1✔
130
    );
131
    syncDataAccessRequestDatasets(dar.getDatasetIds(), dar.getReferenceId());
1✔
132

133
    return findByReferenceId(dar.getReferenceId());
1✔
134
  }
135

136
  /**
137
   * First delete any rows with the current reference id. This will allow us to keep (referenceId,
138
   * dataset_id) unique Takes in a list of datasetIds and a referenceId and adds them to the
139
   * dar_dataset collection
140
   *
141
   * @param datasetIds  List of Integers that represent the datasetIds
142
   * @param referenceId ReferenceId of the corresponding DAR
143
   */
144
  private void syncDataAccessRequestDatasets(List<Integer> datasetIds, String referenceId) {
145
    List<DarDataset> darDatasets = datasetIds.stream()
1✔
146
        .map(datasetId -> new DarDataset(referenceId, datasetId))
1✔
147
        .toList();
1✔
148
    dataAccessRequestDAO.deleteDARDatasetRelationByReferenceId(referenceId);
1✔
149

150
    if (!darDatasets.isEmpty()) {
1✔
151
      dataAccessRequestDAO.insertAllDarDatasets(darDatasets);
1✔
152
    }
153
  }
1✔
154

155
  /**
156
   * Create a new Draft DAR from the canceled DARs present in source DarCollection.
157
   *
158
   * @param user             The User
159
   * @param sourceCollection The source DarCollection
160
   * @return New DataAccessRequest in draft status
161
   */
162
  public DataAccessRequest createDraftDarFromCanceledCollection(User user,
163
      DarCollection sourceCollection) {
164
    if (Objects.isNull(sourceCollection.getDars()) || sourceCollection.getDars().isEmpty()) {
1✔
165
      throw new IllegalArgumentException("Source Collection must contain at least a single DAR");
1✔
166
    }
167
    if (user.getLibraryCards().isEmpty()) {
1✔
168
      throw new LibraryCardRequiredException();
1✔
169
    }
170
    DataAccessRequest sourceDar = new ArrayList<>(sourceCollection.getDars().values()).get(0);
1✔
171
    DataAccessRequestData sourceData = sourceDar.getData();
1✔
172
    if (Objects.isNull(sourceData)) {
1✔
173
      throw new IllegalArgumentException(
1✔
174
          "Source Collection must contain at least a single DAR with a populated data");
175
    }
176

177
    // Find all dataset ids for canceled DARs in the collection
178
    List<Integer> datasetIds = sourceCollection
1✔
179
        .getDars().values().stream()
1✔
180
        .filter(d -> DarStatus.CANCELED.getValue().equalsIgnoreCase(d.getData().getStatus()))
1✔
181
        .map(DataAccessRequest::getDatasetIds)
1✔
182
        .flatMap(Collection::stream)
1✔
183
        .toList();
1✔
184
    if (datasetIds.isEmpty()) {
1✔
185
      throw new IllegalArgumentException(
1✔
186
          "Source Collection must contain references to at least a single canceled DAR's dataset");
187
    }
188

189
    List<String> canceledReferenceIds = sourceCollection
1✔
190
        .getDars().values().stream()
1✔
191
        .filter(d -> DarStatus.CANCELED.getValue().equalsIgnoreCase(d.getData().getStatus()))
1✔
192
        .map(DataAccessRequest::getReferenceId)
1✔
193
        .toList();
1✔
194
    List<Integer> electionIds = electionDAO.getElectionIdsByReferenceIds(canceledReferenceIds);
1✔
195
    if (!electionIds.isEmpty()) {
1✔
196
      String errorMessage = "Found 'Open' elections for canceled DARs in collection id: "
1✔
197
          + sourceCollection.getDarCollectionId();
1✔
198
      logWarn(errorMessage);
1✔
199
      throw new IllegalArgumentException(errorMessage);
1✔
200
    }
201

202
    List<String> sourceReferenceIds = sourceCollection
1✔
203
        .getDars().values().stream()
1✔
204
        .map(DataAccessRequest::getReferenceId)
1✔
205
        .toList();
1✔
206
    dataAccessRequestDAO.archiveByReferenceIds(sourceReferenceIds);
1✔
207

208
    String referenceId = UUID.randomUUID().toString();
1✔
209
    Date now = new Date();
1✔
210
    // Clone the dar's data object and reset values that need to be updated for the clone
211
    DataAccessRequestData newData = new Gson().fromJson(sourceData.toString(),
1✔
212
        DataAccessRequestData.class);
213
    newData.setDarCode(null);
1✔
214
    newData.setStatus(null);
1✔
215
    newData.setReferenceId(referenceId);
1✔
216
    newData.setCreateDate(now.getTime());
1✔
217
    newData.setSortDate(now.getTime());
1✔
218
    dataAccessRequestDAO.insertDraftDataAccessRequest(
1✔
219
        referenceId,
220
        user.getUserId(),
1✔
221
        now,
222
        now,
223
        null,
224
        now,
225
        newData
226
    );
227
    syncDataAccessRequestDatasets(datasetIds, referenceId);
1✔
228

229
    return findByReferenceId(referenceId);
1✔
230
  }
231

232
  /**
233
   * @param user User
234
   * @return List<DataAccessRequest>
235
   */
236
  public List<DataAccessRequest> getDataAccessRequestsByUserRole(User user) {
237
    List<DataAccessRequest> dars = dataAccessRequestDAO.findAllDataAccessRequests();
1✔
238
    return dacService.filterDataAccessRequestsByDac(dars, user);
1✔
239
  }
240

241
  /**
242
   * Generate a DataAccessRequest from the provided DAR. The provided DAR may or may not exist in
243
   * draft form, so it covers both cases of converting an existing draft to submitted and creating a
244
   * brand new DAR from scratch.
245
   *
246
   * @param user              The create User
247
   * @param dataAccessRequest DataAccessRequest with populated DAR data
248
   * @return The created DAR.
249
   */
250
  public DataAccessRequest createDataAccessRequest(User user, DataAccessRequest dataAccessRequest) {
251
    if (Objects.isNull(user) || Objects.isNull(dataAccessRequest) || Objects.isNull(
1✔
252
        dataAccessRequest.getReferenceId()) || Objects.isNull(dataAccessRequest.getData())) {
1✔
253
      throw new IllegalArgumentException("User and DataAccessRequest are required");
×
254
    }
255

256
    if (user.getLibraryCards().isEmpty()) {
1✔
257
      throw new NIHComplianceRuleException();
1✔
258
    }
259

260
    Date now = new Date();
1✔
261
    long nowTime = now.getTime();
1✔
262
    DataAccessRequestData darData = dataAccessRequest.getData();
1✔
263
    if (Objects.isNull(darData.getCreateDate())) {
1✔
264
      darData.setCreateDate(nowTime);
1✔
265
    }
266
    darData.setSortDate(nowTime);
1✔
267
    DataAccessRequest existingDar = dataAccessRequestDAO.findByReferenceId(
1✔
268
        dataAccessRequest.getReferenceId());
1✔
269
    Integer collectionId;
270
    // Only create a new DarCollection if we haven't done so already
271
    if (Objects.nonNull(existingDar) && Objects.nonNull(existingDar.getCollectionId())) {
1✔
272
      collectionId = existingDar.getCollectionId();
×
273
    } else {
274
      String darCodeSequence = "DAR-" + counterService.getNextDarSequence();
1✔
275
      collectionId = darCollectionDAO.insertDarCollection(darCodeSequence, user.getUserId(), now);
1✔
276
      darData.setDarCode(darCodeSequence);
1✔
277
    }
278
    String referenceId;
279
    List<Integer> datasetIds = dataAccessRequest.getDatasetIds();
1✔
280
    if (Objects.nonNull(existingDar)) {
1✔
281
      referenceId = dataAccessRequest.getReferenceId();
1✔
282
      dataAccessRequestDAO.updateDraftForCollection(collectionId,
1✔
283
          referenceId);
284
      dataAccessRequestDAO.updateDataByReferenceId(
1✔
285
          referenceId,
286
          user.getUserId(),
1✔
287
          new Date(darData.getSortDate()),
1✔
288
          now,
289
          now,
290
          darData);
291
    } else {
292
      referenceId = UUID.randomUUID().toString();
1✔
293
      dataAccessRequestDAO.insertDataAccessRequest(
1✔
294
          collectionId,
295
          referenceId,
296
          user.getUserId(),
1✔
297
          new Date(darData.getCreateDate()),
1✔
298
          new Date(darData.getSortDate()),
1✔
299
          now,
300
          now,
301
          darData);
302
    }
303
    syncDataAccessRequestDatasets(datasetIds, referenceId);
1✔
304
    return findByReferenceId(referenceId);
1✔
305
  }
306

307
  /**
308
   * Update an existing DataAccessRequest. Replaces DataAccessRequestData.
309
   *
310
   * @param user The User
311
   * @param dar  The DataAccessRequest
312
   * @return The updated DataAccessRequest
313
   */
314
  public DataAccessRequest updateByReferenceId(User user, DataAccessRequest dar) {
315
    try {
316
      return dataAccessRequestServiceDAO.updateByReferenceId(user, dar);
1✔
317
    } catch (SQLException e) {
×
318
      // If I simply rethrow the error then I'll have to redefine any method that
319
      // calls this function to "throw SQLException"
320
      //Instead I'm going to throw an UnableToExecuteStatementException
321
      //Response class will catch it, log it, and throw a 500 through the "unableToExecuteExceptionHandler"
322
      //on the Resource class, just like it would with a SQLException
323
      throw new UnableToExecuteStatementException(e.getMessage());
×
324
    }
325
  }
326

327
  public Collection<DataAccessRequest> getApprovedDARsForDataset(Dataset dataset) {
328
    return dataAccessRequestDAO.findApprovedDARsByDatasetId(dataset.getDatasetId());
1✔
329
  }
330

331
}
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