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

DataBiosphere / consent / #4455

pending completion
#4455

push

web-flow
[DUOS-2527][risk=no] Make CG Name non-modifiable in PUT (#2118)

* add docs; make cg name non-modifiable

* fix test data

1 of 1 new or added line in 1 file covered. (100.0%)

9607 of 12554 relevant lines covered (76.53%)

0.77 hits per line

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

89.25
/src/main/java/org/broadinstitute/consent/http/resources/DatasetResource.java
1
package org.broadinstitute.consent.http.resources;
2

3
import com.google.gson.Gson;
4
import com.google.gson.JsonSyntaxException;
5
import com.google.inject.Inject;
6
import com.networknt.schema.ValidationMessage;
7
import io.dropwizard.auth.Auth;
8
import jakarta.ws.rs.BadRequestException;
9
import jakarta.ws.rs.ClientErrorException;
10
import jakarta.ws.rs.Consumes;
11
import jakarta.ws.rs.DELETE;
12
import jakarta.ws.rs.DefaultValue;
13
import jakarta.ws.rs.GET;
14
import jakarta.ws.rs.NotFoundException;
15
import jakarta.ws.rs.POST;
16
import jakarta.ws.rs.PUT;
17
import jakarta.ws.rs.Path;
18
import jakarta.ws.rs.PathParam;
19
import jakarta.ws.rs.Produces;
20
import jakarta.ws.rs.QueryParam;
21
import jakarta.ws.rs.core.Context;
22
import jakarta.ws.rs.core.HttpHeaders;
23
import jakarta.ws.rs.core.MediaType;
24
import jakarta.ws.rs.core.Response;
25
import jakarta.ws.rs.core.Response.Status;
26
import jakarta.ws.rs.core.UriBuilder;
27
import jakarta.ws.rs.core.UriInfo;
28
import java.io.ByteArrayInputStream;
29
import java.io.InputStream;
30
import java.net.URI;
31
import java.util.ArrayList;
32
import java.util.Collection;
33
import java.util.HashMap;
34
import java.util.HashSet;
35
import java.util.List;
36
import java.util.Map;
37
import java.util.Objects;
38
import java.util.Optional;
39
import java.util.Set;
40
import java.util.stream.Collectors;
41
import javax.annotation.security.PermitAll;
42
import javax.annotation.security.RolesAllowed;
43
import org.apache.commons.collections.CollectionUtils;
44
import org.broadinstitute.consent.http.enumeration.UserRoles;
45
import org.broadinstitute.consent.http.models.AuthUser;
46
import org.broadinstitute.consent.http.models.DataUse;
47
import org.broadinstitute.consent.http.models.Dataset;
48
import org.broadinstitute.consent.http.models.DatasetUpdate;
49
import org.broadinstitute.consent.http.models.Dictionary;
50
import org.broadinstitute.consent.http.models.Study;
51
import org.broadinstitute.consent.http.models.User;
52
import org.broadinstitute.consent.http.models.UserRole;
53
import org.broadinstitute.consent.http.models.dataset_registration_v1.ConsentGroup;
54
import org.broadinstitute.consent.http.models.dataset_registration_v1.DatasetRegistrationSchemaV1;
55
import org.broadinstitute.consent.http.models.dto.DatasetDTO;
56
import org.broadinstitute.consent.http.models.dto.DatasetPropertyDTO;
57
import org.broadinstitute.consent.http.service.DataAccessRequestService;
58
import org.broadinstitute.consent.http.service.DatasetRegistrationService;
59
import org.broadinstitute.consent.http.service.DatasetService;
60
import org.broadinstitute.consent.http.service.ElasticSearchService;
61
import org.broadinstitute.consent.http.service.UserService;
62
import org.broadinstitute.consent.http.util.JsonSchemaUtil;
63
import org.broadinstitute.consent.http.util.gson.GsonUtil;
64
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
65
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
66
import org.glassfish.jersey.media.multipart.FormDataParam;
67

68

69
@Path("api/dataset")
70
public class DatasetResource extends Resource {
71

72
  private final String END_OF_LINE = System.lineSeparator();
1✔
73
  private final DatasetService datasetService;
74
  private final DatasetRegistrationService datasetRegistrationService;
75
  private final UserService userService;
76
  private final DataAccessRequestService darService;
77
  private final ElasticSearchService elasticSearchService;
78

79
  private final JsonSchemaUtil jsonSchemaUtil;
80

81
  private final String defaultDataSetSampleFileName = "DataSetSample.tsv";
1✔
82
  private final String defaultDataSetSampleContent =
1✔
83
      "Dataset Name\tData Type\tSpecies\tPhenotype/Indication\t# of participants\tDescription\tdbGAP\tData Depositor\tPrincipal Investigator(PI)\tSample Collection ID\tConsent ID"
84
          + "\n(Bucienne Monco) - Muc-1 Kidney Disease\tDNA, whole genome\thuman\tmuc-1, kidney disease\t31\tmuc-1 patients that developed cancer , 5 weeks after treatment\thttp://....\tJohn Doe\tMark Smith\tSC-20658\t1";
85

86
  private String dataSetSampleFileName;
87
  private String dataSetSampleContent;
88

89
  void resetDataSetSampleFileName() {
90
    dataSetSampleFileName = defaultDataSetSampleFileName;
1✔
91
  }
1✔
92

93
  void resetDataSetSampleContent() {
94
    dataSetSampleContent = defaultDataSetSampleContent;
1✔
95
  }
1✔
96

97
  @Inject
98
  public DatasetResource(DatasetService datasetService, UserService userService,
99
      DataAccessRequestService darService, DatasetRegistrationService datasetRegistrationService,
100
      ElasticSearchService elasticSearchService) {
1✔
101
    this.datasetService = datasetService;
1✔
102
    this.userService = userService;
1✔
103
    this.darService = darService;
1✔
104
    this.datasetRegistrationService = datasetRegistrationService;
1✔
105
    this.elasticSearchService = elasticSearchService;
1✔
106
    this.jsonSchemaUtil = new JsonSchemaUtil();
1✔
107
    resetDataSetSampleFileName();
1✔
108
    resetDataSetSampleContent();
1✔
109
  }
1✔
110

111
  @Deprecated
112
  @POST
113
  @Consumes("application/json")
114
  @Produces("application/json")
115
  @Path("/v2")
116
  @RolesAllowed({ADMIN, CHAIRPERSON})
117
  public Response createDataset(@Auth AuthUser authUser, @Context UriInfo info, String json) {
118
    DatasetDTO inputDataset = new Gson().fromJson(json, DatasetDTO.class);
1✔
119
    if (Objects.isNull(inputDataset)) {
1✔
120
      throw new BadRequestException("Dataset is required");
1✔
121
    }
122
    if (Objects.isNull(inputDataset.getProperties()) || inputDataset.getProperties().isEmpty()) {
1✔
123
      throw new BadRequestException("Dataset must contain required properties");
1✔
124
    }
125
    List<DatasetPropertyDTO> invalidProperties = datasetService.findInvalidProperties(
1✔
126
        inputDataset.getProperties());
1✔
127
    if (invalidProperties.size() > 0) {
1✔
128
      List<String> invalidKeys = invalidProperties.stream()
1✔
129
          .map(DatasetPropertyDTO::getPropertyName)
1✔
130
          .collect(Collectors.toList());
1✔
131
      throw new BadRequestException(
1✔
132
          "Dataset contains invalid properties that could not be recognized or associated with a key: "
133
              + invalidKeys.toString());
1✔
134
    }
135
    List<DatasetPropertyDTO> duplicateProperties = datasetService.findDuplicateProperties(
1✔
136
        inputDataset.getProperties());
1✔
137
    if (duplicateProperties.size() > 0) {
1✔
138
      throw new BadRequestException("Dataset contains multiple values for the same property.");
1✔
139
    }
140
    String name = "";
1✔
141
    try {
142
      name = inputDataset.getPropertyValue("Dataset Name");
1✔
143
    } catch (IndexOutOfBoundsException e) {
1✔
144
      throw new BadRequestException("Dataset name is required");
1✔
145
    }
1✔
146
    if (Objects.isNull(name) || name.isBlank()) {
1✔
147
      throw new BadRequestException("Dataset name is required");
1✔
148
    }
149
    Dataset datasetNameAlreadyUsed = datasetService.getDatasetByName(name);
1✔
150
    if (Objects.nonNull(datasetNameAlreadyUsed)) {
1✔
151
      throw new ClientErrorException("Dataset name: " + name + " is already in use",
1✔
152
          Status.CONFLICT);
153
    }
154
    User dacUser = userService.findUserByEmail(authUser.getGenericUser().getEmail());
1✔
155
    Integer userId = dacUser.getUserId();
1✔
156
    try {
157
      DatasetDTO createdDatasetWithConsent = datasetService.createDatasetWithConsent(inputDataset,
1✔
158
          name, userId);
159
      URI uri = info.getRequestUriBuilder().replacePath("api/dataset/{datasetId}")
1✔
160
          .build(createdDatasetWithConsent.getDataSetId());
1✔
161
      return Response.created(uri).entity(createdDatasetWithConsent).build();
1✔
162
    } catch (Exception e) {
1✔
163
      return createExceptionResponse(e);
1✔
164
    }
165
  }
166

167
  @POST
168
  @Consumes({MediaType.MULTIPART_FORM_DATA})
169
  @Produces({MediaType.APPLICATION_JSON})
170
  @Path("/v3")
171
  @RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER})
172
  /*
173
   * This endpoint accepts a json instance of a dataset-registration-schema_v1.json schema.
174
   * With that object, we can fully create datasets from the provided values.
175
   */
176
  public Response createDatasetRegistration(
177
      @Auth AuthUser authUser,
178
      FormDataMultiPart multipart,
179
      @FormDataParam("dataset") String json) {
180
    try {
181
      Set<ValidationMessage> errors = jsonSchemaUtil.validateSchema_v1(json);
1✔
182
      if (!errors.isEmpty()) {
1✔
183
        throw new BadRequestException(
1✔
184
            "Invalid schema:\n"
185
                + String.join("\n", errors.stream().map(ValidationMessage::getMessage).toList()));
1✔
186
      }
187

188
      DatasetRegistrationSchemaV1 registration = jsonSchemaUtil.deserializeDatasetRegistration(
1✔
189
          json);
190
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
191

192
      // key: field name (not file name), value: file body part
193
      Map<String, FormDataBodyPart> files = extractFilesFromMultiPart(multipart);
1✔
194

195
      // Generate datasets from registration
196
      List<Dataset> datasets = datasetRegistrationService.createDatasetsFromRegistration(
1✔
197
          registration,
198
          user,
199
          files);
200

201
      URI uri = UriBuilder.fromPath("/api/dataset/v2").build();
1✔
202
      return Response.created(uri).entity(datasets).build();
1✔
203
    } catch (Exception e) {
1✔
204
      return createExceptionResponse(e);
1✔
205
    }
206
  }
207

208
  @PUT
209
  @Consumes({MediaType.MULTIPART_FORM_DATA})
210
  @Produces({MediaType.APPLICATION_JSON})
211
  @Path("/study/{studyId}")
212
  @RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER})
213
  /*
214
   * This endpoint accepts a json instance of a dataset-registration-schema_v1.json schema.
215
   * With that object, we can fully update the study/datasets from the provided values.
216
   */
217
  public Response updateStudyByRegistration(
218
      @Auth AuthUser authUser,
219
      FormDataMultiPart multipart,
220
      @PathParam("studyId") Integer studyId,
221
      @FormDataParam("dataset") String json) {
222
    try {
223
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
224
      Study existingStudy = datasetRegistrationService.findStudyById(studyId);
1✔
225

226
      // Manually validate the schema from an editing context. Validation with the schema tools
227
      // enforces it in a creation context but doesn't work for editing purposes.
228
      Gson gson = GsonUtil.gsonBuilderWithAdapters().create();
1✔
229
      DatasetRegistrationSchemaV1 registration = gson.fromJson(json, DatasetRegistrationSchemaV1.class);
1✔
230

231
      // Not modifiable: Study Name, Data Submitter Name/Email, Primary Data Use,
232
      // Secondary Data Use
233
      if (Objects.nonNull(registration.getStudyName()) && !registration.getStudyName().equals(existingStudy.getName())) {
1✔
234
        throw new BadRequestException("Invalid change to Study Name");
1✔
235
      }
236
      if (Objects.nonNull(registration.getDataSubmitterUserId()) &&!registration.getDataSubmitterUserId().equals(existingStudy.getCreateUserId())) {
1✔
237
        throw new BadRequestException("Invalid change to Data Submitter");
1✔
238
      }
239
      // Data use and name changes are not allowed for existing datasets
240
      List<ConsentGroup> invalidConsentGroups = registration.getConsentGroups()
1✔
241
          .stream()
1✔
242
          .filter(cg -> Objects.nonNull(cg.getDatasetId()))
1✔
243
          .filter(ConsentGroup::isInvalidForUpdate)
1✔
244
          .toList();
1✔
245
      if (!invalidConsentGroups.isEmpty()) {
1✔
246
        throw new BadRequestException("Invalid Data Use changes to existing Consent Groups");
1✔
247
      }
248

249
      // Validate that we're not trying to delete any datasets in the registration payload.
250
      // The list of non-null dataset ids in the consent groups MUST be the same as the list of
251
      // existing dataset ids.
252
      HashSet<Integer> existingDatasetIds = new HashSet<>(existingStudy.getDatasetIds());
1✔
253
      HashSet<Integer> consentGroupDatasetIds = new HashSet<>(registration.getConsentGroups()
1✔
254
          .stream()
1✔
255
          .map(ConsentGroup::getDatasetId)
1✔
256
          .filter(Objects::nonNull)
1✔
257
          .toList());
1✔
258
      if (!consentGroupDatasetIds.containsAll(existingDatasetIds)) {
1✔
259
        throw new BadRequestException("Invalid removal of Consent Groups");
×
260
      }
261

262
      // Update study from registration
263
      Map<String, FormDataBodyPart> files = extractFilesFromMultiPart(multipart);
1✔
264
      Study updatedStudy = datasetRegistrationService.updateStudyFromRegistration(
1✔
265
          studyId,
266
          registration,
267
          user,
268
          files);
269
      return Response.ok(updatedStudy).build();
1✔
270
    } catch (Exception e) {
1✔
271
      return createExceptionResponse(e);
1✔
272
    }
273
  }
274

275
  /**
276
   * Finds and validates all the files uploaded to the multipart.
277
   *
278
   * @param multipart Form data
279
   * @return Map of file body parts, where the key is the name of the field and the value is the
280
   * body part including the file(s).
281
   */
282
  private Map<String, FormDataBodyPart> extractFilesFromMultiPart(FormDataMultiPart multipart) {
283
    if (Objects.isNull(multipart)) {
1✔
284
      return Map.of();
1✔
285
    }
286

287
    Map<String, FormDataBodyPart> files = new HashMap<>();
1✔
288
    for (List<FormDataBodyPart> parts : multipart.getFields().values()) {
1✔
289
      for (FormDataBodyPart part : parts) {
1✔
290
        if (Objects.nonNull(part.getContentDisposition().getFileName())) {
1✔
291
          validateFileDetails(part.getContentDisposition());
1✔
292
          files.put(part.getName(), part);
1✔
293
        }
294
      }
1✔
295
    }
1✔
296

297
    return files;
1✔
298
  }
299

300
  /**
301
   * This endpoint updates the dataset.
302
   */
303
  @PUT
304
  @Consumes({MediaType.MULTIPART_FORM_DATA})
305
  @Produces({MediaType.APPLICATION_JSON})
306
  @Path("/v3/{datasetId}")
307
  @RolesAllowed({ADMIN, CHAIRPERSON})
308
  public Response updateByDatasetUpdate(
309
      @Auth AuthUser authUser,
310
      @PathParam("datasetId") Integer datasetId,
311
      FormDataMultiPart multipart,
312
      @FormDataParam("dataset") String json) {
313

314
    try {
315

316
      DatasetUpdate update = new Gson().fromJson(json, DatasetUpdate.class);
1✔
317

318
      if (Objects.isNull(update)) {
1✔
319
        throw new BadRequestException("Dataset is required");
1✔
320
      }
321

322
      Dataset datasetExists = datasetService.findDatasetById(datasetId);
1✔
323
      if (Objects.isNull(datasetExists)) {
1✔
324
        throw new NotFoundException("Could not find the dataset with id: " + datasetId);
1✔
325
      }
326

327
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
328

329
      // key: field name (not file name), value: file body part
330
      Map<String, FormDataBodyPart> files = extractFilesFromMultiPart(multipart);
1✔
331

332
      Dataset updatedDataset = datasetRegistrationService.updateDataset(datasetId, user, update,
1✔
333
          files);
334
      return Response.ok().entity(updatedDataset).build();
1✔
335
    } catch (Exception e) {
1✔
336
      return createExceptionResponse(e);
1✔
337
    }
338
  }
339

340
  @PUT
341
  @Consumes("application/json")
342
  @Produces("application/json")
343
  @Path("/{datasetId}")
344
  @RolesAllowed({ADMIN, CHAIRPERSON})
345
  public Response updateDataset(@Auth AuthUser authUser, @Context UriInfo info,
346
      @PathParam("datasetId") Integer datasetId, String json) {
347
    try {
348
      DatasetDTO inputDataset = new Gson().fromJson(json, DatasetDTO.class);
1✔
349
      if (Objects.isNull(inputDataset)) {
1✔
350
        throw new BadRequestException("Dataset is required");
1✔
351
      }
352
      if (Objects.isNull(inputDataset.getProperties()) || inputDataset.getProperties().isEmpty()) {
1✔
353
        throw new BadRequestException("Dataset must contain required properties");
1✔
354
      }
355
      Dataset datasetExists = datasetService.findDatasetById(datasetId);
1✔
356
      if (Objects.isNull(datasetExists)) {
1✔
357
        throw new NotFoundException("Could not find the dataset with id: " + datasetId);
1✔
358
      }
359
      List<DatasetPropertyDTO> invalidProperties = datasetService.findInvalidProperties(
1✔
360
          inputDataset.getProperties());
1✔
361
      if (invalidProperties.size() > 0) {
1✔
362
        List<String> invalidKeys = invalidProperties.stream()
1✔
363
            .map(DatasetPropertyDTO::getPropertyName)
1✔
364
            .collect(Collectors.toList());
1✔
365
        throw new BadRequestException(
1✔
366
            "Dataset contains invalid properties that could not be recognized or associated with a key: "
367
                + invalidKeys.toString());
1✔
368
      }
369
      List<DatasetPropertyDTO> duplicateProperties = datasetService.findDuplicateProperties(
1✔
370
          inputDataset.getProperties());
1✔
371
      if (duplicateProperties.size() > 0) {
1✔
372
        throw new BadRequestException("Dataset contains multiple values for the same property.");
1✔
373
      }
374
      User user = userService.findUserByEmail(authUser.getGenericUser().getEmail());
1✔
375
      // Validate that the admin/chairperson has edit access to this dataset
376
      validateDatasetDacAccess(user, datasetExists);
1✔
377
      Integer userId = user.getUserId();
1✔
378
      Optional<Dataset> updatedDataset = datasetService.updateDataset(inputDataset, datasetId,
1✔
379
          userId);
380
      if (updatedDataset.isPresent()) {
1✔
381
        URI uri = info.getRequestUriBuilder().replacePath("api/dataset/{datasetId}")
1✔
382
            .build(updatedDataset.get().getDataSetId());
1✔
383
        return Response.ok(uri).entity(updatedDataset.get()).build();
1✔
384
      } else {
385
        return Response.noContent().build();
1✔
386
      }
387
    } catch (Exception e) {
1✔
388
      return createExceptionResponse(e);
1✔
389
    }
390
  }
391

392
  @GET
393
  @Produces("application/json")
394
  @PermitAll
395
  public Response describeDataSets(@Auth AuthUser authUser) {
396
    try {
397
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
398
      Collection<DatasetDTO> dataSetList = datasetService.describeDatasets(user.getUserId());
1✔
399
      return Response.ok(dataSetList, MediaType.APPLICATION_JSON).build();
1✔
400
    } catch (Exception e) {
1✔
401
      return createExceptionResponse(e);
1✔
402
    }
403
  }
404

405
  @GET
406
  @Produces("application/json")
407
  @PermitAll
408
  @Path("/v2")
409
  public Response findAllDatasetsAvailableToUser(@Auth AuthUser authUser) {
410
    try {
411
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
412
      List<Dataset> datasets = datasetService.findAllDatasetsByUser(user);
1✔
413
      return Response.ok(datasets).build();
1✔
414
    } catch (Exception e) {
×
415
      return createExceptionResponse(e);
×
416
    }
417
  }
418

419
  @GET
420
  @Produces("application/json")
421
  @PermitAll
422
  @Path("/role/{roleName}")
423
  public Response findDatasetsAccordingToRole(
424
      @Auth AuthUser authUser,
425
      @PathParam("roleName") String roleName) {
426
    try {
427
      User user = userService.findUserByEmail(authUser.getEmail());
×
428
      validateUserHasRoleName(user, roleName);
×
429
      UserRoles role = UserRoles.getUserRoleFromName(roleName);
×
430
      if (Objects.isNull(role)) {
×
431
        throw new BadRequestException("Invalid role selection: " + roleName);
×
432
      }
433
      List<Dataset> datasets = switch (role) {
×
434
        case ADMIN -> datasetService.findAllDatasets();
×
435
        case CHAIRPERSON -> datasetService.findDatasetsForChairperson(user);
×
436
        case DATASUBMITTER -> datasetService.findDatasetsForDataSubmitter(user);
×
437
        default -> datasetService.findPublicDatasets();
×
438
      };
439
      return Response.ok(datasets).build();
×
440
    } catch (Exception e) {
×
441
      return createExceptionResponse(e);
×
442
    }
443
  }
444

445
  @GET
446
  @Deprecated // Use /v2/{datasetId}
447
  @Path("/{datasetId}")
448
  @Produces("application/json")
449
  @PermitAll
450
  public Response describeDataSet(@PathParam("datasetId") Integer datasetId) {
451
    try {
452
      DatasetDTO datasetDTO = datasetService.getDatasetDTO(datasetId);
1✔
453
      return Response.ok(datasetDTO, MediaType.APPLICATION_JSON).build();
1✔
454
    } catch (Exception e) {
1✔
455
      return createExceptionResponse(e);
1✔
456
    }
457
  }
458

459
  @GET
460
  @Path("/v2/{datasetId}")
461
  @Produces("application/json")
462
  @PermitAll
463
  public Response getDataset(@PathParam("datasetId") Integer datasetId) {
464
    try {
465
      Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
466
      if (Objects.isNull(dataset)) {
1✔
467
        throw new NotFoundException("Could not find the dataset with id: " + datasetId.toString());
1✔
468
      }
469
      return Response.ok(dataset).build();
1✔
470
    } catch (Exception e) {
1✔
471
      return createExceptionResponse(e);
1✔
472
    }
473
  }
474

475
  @GET
476
  @Path("/study/{studyId}")
477
  @Produces(MediaType.APPLICATION_JSON)
478
  @RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER})
479
  public Response getStudyById(@PathParam("studyId") Integer studyId) {
480
    try {
481
      Study study = datasetService.getStudyWithDatasetsById(studyId);
1✔
482
      return Response.ok(study).build();
1✔
483
    } catch (Exception e) {
1✔
484
      return createExceptionResponse(e);
1✔
485
    }
486
  }
487

488
  @GET
489
  @Path("/batch")
490
  @Produces("application/json")
491
  @PermitAll
492
  public Response getDatasets(@QueryParam("ids") List<Integer> datasetIds) {
493
    try {
494
      List<Dataset> datasets = datasetService.findDatasetsByIds(datasetIds);
1✔
495

496
      Set<Integer> foundIds = datasets.stream().map(Dataset::getDataSetId)
1✔
497
          .collect(Collectors.toSet());
1✔
498
      if (!foundIds.containsAll(datasetIds)) {
1✔
499
        // find the differences
500
        List<Integer> differences = new ArrayList<>(datasetIds);
1✔
501
        differences.removeAll(foundIds);
1✔
502
        throw new NotFoundException(
1✔
503
            "Could not find datasets with ids: "
504
                + String.join(",",
1✔
505
                differences.stream().map((i) -> i.toString()).collect(Collectors.toSet())));
1✔
506

507
      }
508
      return Response.ok(datasets).build();
1✔
509
    } catch (Exception e) {
1✔
510
      return createExceptionResponse(e);
1✔
511
    }
512
  }
513

514
  @GET
515
  @Consumes("application/json")
516
  @Produces("application/json")
517
  @Path("/validate")
518
  @PermitAll
519
  public Response validateDatasetName(@QueryParam("name") String name) {
520
    try {
521
      Dataset datasetWithName = datasetService.getDatasetByName(name);
1✔
522
      return Response.ok().entity(datasetWithName.getDataSetId()).build();
1✔
523
    } catch (Exception e) {
1✔
524
      throw new NotFoundException("Could not find the dataset with name: " + name);
1✔
525
    }
526
  }
527

528
  @GET
529
  @Consumes("application/json")
530
  @Produces("application/json")
531
  @Path("/studyNames")
532
  @PermitAll
533
  public Response findAllStudyNames() {
534
    try {
535
      Set<String> studyNames = datasetService.findAllStudyNames();
1✔
536
      return Response.ok(studyNames).build();
1✔
537
    } catch (Exception e) {
1✔
538
      return createExceptionResponse(e);
1✔
539
    }
540
  }
541

542
  @GET
543
  @Path("/sample")
544
  @PermitAll
545
  public Response getDataSetSample() {
546
    String msg = "GETting Data Set Sample";
1✔
547
    logDebug(msg);
1✔
548
    InputStream inputStream = null;
1✔
549
    try {
550
      inputStream = new ByteArrayInputStream(dataSetSampleContent.getBytes());
1✔
551
    } catch (Exception e) {
×
552
      logException("Error when GETting dataset sample.", e);
×
553
      return createExceptionResponse(e);
×
554
    }
1✔
555
    return Response.ok(inputStream)
1✔
556
        .header("Content-Disposition", "attachment; filename=" + dataSetSampleFileName).build();
1✔
557
  }
558

559
  @POST
560
  @Path("/download")
561
  @Consumes(MediaType.APPLICATION_JSON)
562
  @Produces(MediaType.APPLICATION_JSON)
563
  @PermitAll
564
  public Response downloadDataSets(List<Integer> idList) {
565
    try {
566
      String msg = "GETing DataSets to download";
1✔
567
      logDebug(msg);
1✔
568

569
      Gson gson = new Gson();
1✔
570
      HashMap<String, String> datasets = new HashMap<>();
1✔
571

572
      Collection<Dictionary> headers = datasetService.describeDictionaryByReceiveOrder();
1✔
573

574
      StringBuilder sb = new StringBuilder();
1✔
575
      String TSV_DELIMITER = "\t";
1✔
576
      for (Dictionary header : headers) {
1✔
577
        if (sb.length() > 0) {
×
578
          sb.append(TSV_DELIMITER);
×
579
        }
580
        sb.append(header.getKey());
×
581
      }
×
582
      sb.append(END_OF_LINE);
1✔
583

584
      if (CollectionUtils.isEmpty(idList)) {
1✔
585
        datasets.put("datasets", sb.toString());
1✔
586
        return Response.ok(gson.toJson(datasets), MediaType.APPLICATION_JSON).build();
1✔
587
      }
588

589
      Collection<DatasetDTO> rows = datasetService.describeDataSetsByReceiveOrder(idList);
1✔
590

591
      for (DatasetDTO row : rows) {
1✔
592
        StringBuilder sbr = new StringBuilder();
1✔
593
        DatasetPropertyDTO property = new DatasetPropertyDTO("Consent ID", row.getConsentId());
1✔
594
        List<DatasetPropertyDTO> props = row.getProperties();
1✔
595
        props.add(property);
1✔
596
        for (DatasetPropertyDTO prop : props) {
1✔
597
          if (sbr.length() > 0) {
1✔
598
            sbr.append(TSV_DELIMITER);
1✔
599
          }
600
          sbr.append(prop.getPropertyValue());
1✔
601
        }
1✔
602
        sbr.append(END_OF_LINE);
1✔
603
        sb.append(sbr);
1✔
604
      }
1✔
605
      String tsv = sb.toString();
1✔
606

607
      datasets.put("datasets", tsv);
1✔
608
      return Response.ok(gson.toJson(datasets), MediaType.APPLICATION_JSON).build();
1✔
609
    } catch (Exception e) {
1✔
610
      return createExceptionResponse(e);
1✔
611
    }
612
  }
613

614
  @DELETE
615
  @Produces(MediaType.APPLICATION_JSON)
616
  @Path("/{datasetId}")
617
  @RolesAllowed({ADMIN, CHAIRPERSON})
618
  public Response delete(@Auth AuthUser authUser, @PathParam("datasetId") Integer datasetId,
619
      @Context UriInfo info) {
620
    try {
621
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
622
      Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
623
      // Validate that the admin/chairperson has edit/delete access to this dataset
624
      validateDatasetDacAccess(user, dataset);
1✔
625
      datasetService.deleteDataset(datasetId, user.getUserId());
1✔
626
      return Response.ok().build();
1✔
627
    } catch (Exception e) {
1✔
628
      return createExceptionResponse(e);
1✔
629
    }
630
  }
631

632
  @DELETE
633
  @Produces(MediaType.APPLICATION_JSON)
634
  @Path("/disable/{datasetId}/{active}")
635
  @RolesAllowed({ADMIN, CHAIRPERSON})
636
  public Response disableDataSet(@Auth AuthUser authUser, @PathParam("datasetId") Integer datasetId,
637
      @PathParam("active") Boolean active, @Context UriInfo info) {
638
    try {
639
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
640
      Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
641
      // Validate that the admin/chairperson has edit access to this dataset
642
      validateDatasetDacAccess(user, dataset);
1✔
643
      datasetService.disableDataset(datasetId, active);
1✔
644
      return Response.ok().build();
1✔
645
    } catch (Exception e) {
1✔
646
      return createExceptionResponse(e);
1✔
647
    }
648
  }
649

650
  @GET
651
  @Path("/dictionary")
652
  @Produces("application/json")
653
  @PermitAll
654
  public Response describeDictionary() {
655
    try {
656
      Collection<Dictionary> dictionaries = datasetService.describeDictionaryByDisplayOrder();
1✔
657
      return Response.ok(dictionaries).build();
1✔
658
    } catch (Exception e) {
1✔
659
      return createExceptionResponse(e);
1✔
660
    }
661
  }
662

663
  @GET
664
  @Path("/autocomplete/{partial}")
665
  @Produces("application/json")
666
  @PermitAll
667
  @Deprecated
668
  public Response datasetAutocomplete(@Auth AuthUser authUser,
669
      @PathParam("partial") String partial) {
670
    try {
671
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
672
      Integer userId = user.getUserId();
1✔
673
      List<Map<String, String>> datasets = datasetService.autoCompleteDatasets(partial, userId);
1✔
674
      return Response.ok(datasets, MediaType.APPLICATION_JSON).build();
1✔
675
    } catch (Exception e) {
1✔
676
      return createExceptionResponse(e);
1✔
677
    }
678
  }
679

680
  @POST
681
  @Path("/index")
682
  @RolesAllowed(ADMIN)
683
  public Response indexDatasets() {
684
    try {
685
      var datasets = datasetService.findAllDatasets();
1✔
686
      return elasticSearchService.indexDatasets(datasets);
1✔
687
    } catch (Exception e) {
×
688
      return createExceptionResponse(e);
×
689
    }
690
  }
691

692
  @POST
693
  @Path("/index/{datasetId}")
694
  @RolesAllowed(ADMIN)
695
  public Response indexDataset(@PathParam("datasetId") Integer datasetId) {
696
    try {
697
      var dataset = datasetService.findDatasetById(datasetId);
1✔
698
      return elasticSearchService.indexDataset(dataset);
1✔
699
    } catch (Exception e) {
×
700
      return createExceptionResponse(e);
×
701
    }
702
  }
703

704
  @DELETE
705
  @Path("/index/{datasetId}")
706
  @RolesAllowed(ADMIN)
707
  public Response deleteDatasetIndex(@PathParam("datasetId") Integer datasetId) {
708
    try {
709
      return elasticSearchService.deleteIndex(datasetId);
1✔
710
    } catch (Exception e) {
×
711
      return createExceptionResponse(e);
×
712
    }
713
  }
714

715
  @GET
716
  @Path("/search")
717
  @Produces("application/json")
718
  @PermitAll
719
  public Response searchDatasets(
720
      @Auth AuthUser authUser,
721
      @QueryParam("query") String query,
722
      @QueryParam("open") @DefaultValue("false") boolean openAccess) {
723
    try {
724
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
725
      List<Dataset> datasets = datasetService.searchDatasets(query, openAccess, user);
1✔
726
      return Response.ok().entity(unmarshal(datasets)).build();
1✔
727
    } catch (Exception e) {
×
728
      return createExceptionResponse(e);
×
729
    }
730
  }
731

732
  @POST
733
  @Path("/search/index")
734
  @Consumes("application/json")
735
  @Produces("application/json")
736
  @PermitAll
737
  public Response searchDatasetIndex(@Auth AuthUser authUser, String query) {
738
    try {
739
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
740
      return elasticSearchService.searchDatasets(query);
1✔
741
    } catch (Exception e) {
×
742
      return createExceptionResponse(e);
×
743
    }
744
  }
745

746
  @PUT
747
  @Produces("application/json")
748
  @RolesAllowed(ADMIN)
749
  public Response updateNeedsReviewDataSets(@QueryParam("dataSetId") Integer dataSetId,
750
      @QueryParam("needsApproval") Boolean needsApproval) {
751
    try {
752
      Dataset dataset = datasetService.updateNeedsReviewDatasets(dataSetId, needsApproval);
1✔
753
      return Response.ok().entity(unmarshal(dataset)).build();
1✔
754
    } catch (Exception e) {
1✔
755
      return createExceptionResponse(e);
1✔
756
    }
757
  }
758

759
  @PUT
760
  @Produces("application/json")
761
  @RolesAllowed(ADMIN)
762
  @Path("/{id}/datause")
763
  public Response updateDatasetDataUse(@Auth AuthUser authUser, @PathParam("id") Integer id,
764
      String dataUseJson) {
765
    try {
766
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
767
      Gson gson = new Gson();
1✔
768
      DataUse dataUse = gson.fromJson(dataUseJson, DataUse.class);
1✔
769
      Dataset originalDataset = datasetService.findDatasetById(id);
1✔
770
      if (Objects.isNull(originalDataset)) {
1✔
771
        throw new NotFoundException("Dataset not found: " + id);
×
772
      }
773
      if (Objects.equals(dataUse, originalDataset.getDataUse())) {
1✔
774
        return Response.notModified().entity(originalDataset).build();
1✔
775
      }
776
      Dataset dataset = datasetService.updateDatasetDataUse(user, id, dataUse);
1✔
777
      return Response.ok().entity(dataset).build();
1✔
778
    } catch (JsonSyntaxException jse) {
1✔
779
      return createExceptionResponse(
1✔
780
          new BadRequestException("Invalid JSON Syntax: " + dataUseJson));
781
    } catch (Exception e) {
1✔
782
      return createExceptionResponse(e);
1✔
783
    }
784
  }
785

786
  @PUT
787
  @Produces("application/json")
788
  @RolesAllowed(ADMIN)
789
  @Path("/{id}/reprocess/datause")
790
  public Response syncDataUseTranslation(@Auth AuthUser authUser, @PathParam("id") Integer id) {
791
    try {
792
      Dataset ds = datasetService.syncDatasetDataUseTranslation(id);
1✔
793
      return Response.ok(ds).build();
1✔
794
    } catch (Exception e) {
1✔
795
      return createExceptionResponse(e);
1✔
796
    }
797
  }
798

799
  @GET
800
  @Produces(MediaType.APPLICATION_OCTET_STREAM)
801
  @PermitAll
802
  @Path("/{datasetId}/approved/users")
803
  public Response downloadDatasetApprovedUsers(@Auth AuthUser authUser,
804
      @PathParam("datasetId") Integer datasetId) {
805
    try {
806
      String content = darService.getDatasetApprovedUsersContent(authUser, datasetId);
1✔
807
      return Response.ok(content)
1✔
808
          .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=DatasetApprovedUsers.tsv")
1✔
809
          .build();
1✔
810
    } catch (Exception e) {
1✔
811
      return createExceptionResponse(e);
1✔
812
    }
813
  }
814

815
  private void validateDatasetDacAccess(User user, Dataset dataset) {
816
    if (user.hasUserRole(UserRoles.ADMIN)) {
1✔
817
      return;
1✔
818
    }
819
    List<Integer> dacIds = user.getRoles().stream()
1✔
820
        .filter(r -> r.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId()))
1✔
821
        .map(UserRole::getDacId)
1✔
822
        .toList();
1✔
823
    if (dacIds.isEmpty()) {
1✔
824
      // Something went very wrong here. A chairperson with no dac ids is an error
825
      logWarn("Unable to find dac ids for chairperson user: " + user.getEmail());
×
826
      throw new NotFoundException();
×
827
    } else {
828
      if (Objects.isNull(dataset) || Objects.isNull(dataset.getDacId())) {
1✔
829
        logWarn("Cannot find a valid dac id for dataset: " + dataset.getDataSetId());
1✔
830
        throw new NotFoundException();
1✔
831
      } else {
832
        if (!dacIds.contains(dataset.getDacId())) {
1✔
833
          throw new NotFoundException();
1✔
834
        }
835
      }
836
    }
837
  }
1✔
838
}
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