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

DataBiosphere / consent / #5238

31 Jul 2024 10:12AM UTC coverage: 77.089% (-0.007%) from 77.096%
#5238

push

web-flow
DCJ-539: New API for returning dataset + study summaries (#2368)

Co-authored-by: Olivia Kotsopoulos <okotsopo@broadinstitute.org>

13 of 16 new or added lines in 4 files covered. (81.25%)

1 existing line in 1 file now uncovered.

10030 of 13011 relevant lines covered (77.09%)

0.77 hits per line

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

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

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

71

72
@Path("api/dataset")
73
public class DatasetResource extends Resource {
74

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

82
  private final JsonSchemaUtil jsonSchemaUtil;
83

84
  @Inject
85
  public DatasetResource(DatasetService datasetService, UserService userService,
86
      DataAccessRequestService darService, DatasetRegistrationService datasetRegistrationService,
87
      ElasticSearchService elasticSearchService) {
1✔
88
    this.datasetService = datasetService;
1✔
89
    this.userService = userService;
1✔
90
    this.darService = darService;
1✔
91
    this.datasetRegistrationService = datasetRegistrationService;
1✔
92
    this.elasticSearchService = elasticSearchService;
1✔
93
    this.jsonSchemaUtil = new JsonSchemaUtil();
1✔
94
  }
1✔
95

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

153
  @POST
154
  @Consumes({MediaType.MULTIPART_FORM_DATA})
155
  @Produces({MediaType.APPLICATION_JSON})
156
  @Path("/v3")
157
  @RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER})
158
  /*
159
   * This endpoint accepts a json instance of a dataset-registration-schema_v1.json schema.
160
   * With that object, we can fully create datasets from the provided values.
161
   */
162
  public Response createDatasetRegistration(
163
      @Auth AuthUser authUser,
164
      FormDataMultiPart multipart,
165
      @FormDataParam("dataset") String json) {
166
    try {
167
      Set<ValidationMessage> errors = jsonSchemaUtil.validateSchema_v1(json);
1✔
168
      if (!errors.isEmpty()) {
1✔
169
        throw new BadRequestException(
1✔
170
            "Invalid schema:\n"
171
                + String.join("\n", errors.stream().map(ValidationMessage::getMessage).toList()));
1✔
172
      }
173

174
      DatasetRegistrationSchemaV1 registration = jsonSchemaUtil.deserializeDatasetRegistration(
1✔
175
          json);
176
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
177

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

181
      // Generate datasets from registration
182
      List<Dataset> datasets = datasetRegistrationService.createDatasetsFromRegistration(
1✔
183
          registration,
184
          user,
185
          files);
186
      Study study = datasets.get(0).getStudy();
1✔
187
      DatasetRegistrationSchemaV1Builder builder = new DatasetRegistrationSchemaV1Builder();
1✔
188
      DatasetRegistrationSchemaV1 createdRegistration = builder.build(study, datasets);
1✔
189
      URI uri = UriBuilder.fromPath(String.format("/api/dataset/study/%s", study.getStudyId()))
1✔
190
          .build();
1✔
191
      String entity = GsonUtil.buildGsonNullSerializer().toJson(createdRegistration);
1✔
192
      return Response.created(uri).entity(entity).build();
1✔
193
    } catch (Exception e) {
1✔
194
      return createExceptionResponse(e);
1✔
195
    }
196
  }
197

198
  /**
199
   * Finds and validates all the files uploaded to the multipart.
200
   *
201
   * @param multipart Form data
202
   * @return Map of file body parts, where the key is the name of the field and the value is the
203
   * body part including the file(s).
204
   */
205
  private Map<String, FormDataBodyPart> extractFilesFromMultiPart(FormDataMultiPart multipart) {
206
    if (Objects.isNull(multipart)) {
1✔
207
      return Map.of();
1✔
208
    }
209

210
    Map<String, FormDataBodyPart> files = new HashMap<>();
1✔
211
    for (List<FormDataBodyPart> parts : multipart.getFields().values()) {
1✔
212
      for (FormDataBodyPart part : parts) {
1✔
213
        if (Objects.nonNull(part.getContentDisposition().getFileName())) {
1✔
214
          validateFileDetails(part.getContentDisposition());
1✔
215
          files.put(part.getName(), part);
1✔
216
        }
217
      }
1✔
218
    }
1✔
219

220
    return files;
1✔
221
  }
222

223
  /**
224
   * This endpoint updates the dataset.
225
   */
226
  @PUT
227
  @Consumes({MediaType.MULTIPART_FORM_DATA})
228
  @Produces({MediaType.APPLICATION_JSON})
229
  @Path("/v3/{datasetId}")
230
  @RolesAllowed({ADMIN, CHAIRPERSON})
231
  public Response updateByDatasetUpdate(
232
      @Auth AuthUser authUser,
233
      @PathParam("datasetId") Integer datasetId,
234
      FormDataMultiPart multipart,
235
      @FormDataParam("dataset") String json) {
236

237
    try {
238
      if (json == null || json.isEmpty()) {
1✔
239
        throw new BadRequestException("Dataset is required");
1✔
240
      }
241
      DatasetUpdate update = new DatasetUpdate(json);
1✔
242

243
      Dataset datasetExists = datasetService.findDatasetById(datasetId);
1✔
244
      if (Objects.isNull(datasetExists)) {
1✔
245
        throw new NotFoundException("Could not find the dataset with id: " + datasetId);
1✔
246
      }
247

248
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
249

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

253
      Dataset updatedDataset = datasetRegistrationService.updateDataset(datasetId, user, update,
1✔
254
          files);
255
      return Response.ok().entity(updatedDataset).build();
1✔
256
    } catch (Exception e) {
1✔
257
      return createExceptionResponse(e);
1✔
258
    }
259
  }
260

261
  @PUT
262
  @Consumes("application/json")
263
  @Produces("application/json")
264
  @Path("/{datasetId}")
265
  @RolesAllowed({ADMIN, CHAIRPERSON})
266
  public Response updateDataset(@Auth AuthUser authUser, @Context UriInfo info,
267
      @PathParam("datasetId") Integer datasetId, String json) {
268
    try {
269
      DatasetDTO inputDataset = new Gson().fromJson(json, DatasetDTO.class);
1✔
270
      if (Objects.isNull(inputDataset)) {
1✔
271
        throw new BadRequestException("Dataset is required");
1✔
272
      }
273
      if (Objects.isNull(inputDataset.getProperties()) || inputDataset.getProperties().isEmpty()) {
1✔
274
        throw new BadRequestException("Dataset must contain required properties");
1✔
275
      }
276
      Dataset datasetExists = datasetService.findDatasetById(datasetId);
1✔
277
      if (Objects.isNull(datasetExists)) {
1✔
278
        throw new NotFoundException("Could not find the dataset with id: " + datasetId);
1✔
279
      }
280
      List<DatasetPropertyDTO> invalidProperties = datasetService.findInvalidProperties(
1✔
281
          inputDataset.getProperties());
1✔
282
      if (invalidProperties.size() > 0) {
1✔
283
        List<String> invalidKeys = invalidProperties.stream()
1✔
284
            .map(DatasetPropertyDTO::getPropertyName)
1✔
285
            .collect(Collectors.toList());
1✔
286
        throw new BadRequestException(
1✔
287
            "Dataset contains invalid properties that could not be recognized or associated with a key: "
288
                + invalidKeys.toString());
1✔
289
      }
290
      List<DatasetPropertyDTO> duplicateProperties = datasetService.findDuplicateProperties(
1✔
291
          inputDataset.getProperties());
1✔
292
      if (duplicateProperties.size() > 0) {
1✔
293
        throw new BadRequestException("Dataset contains multiple values for the same property.");
1✔
294
      }
295
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
296
      // Validate that the admin/chairperson has edit access to this dataset
297
      validateDatasetDacAccess(user, datasetExists);
1✔
298
      Integer userId = user.getUserId();
1✔
299
      Optional<Dataset> updatedDataset = datasetService.updateDataset(inputDataset, datasetId,
1✔
300
          userId);
301
      if (updatedDataset.isPresent()) {
1✔
302
        URI uri = info.getRequestUriBuilder().replacePath("api/dataset/{datasetId}")
1✔
303
            .build(updatedDataset.get().getDataSetId());
1✔
304
        return Response.ok(uri).entity(updatedDataset.get()).build();
1✔
305
      } else {
306
        return Response.noContent().build();
1✔
307
      }
308
    } catch (Exception e) {
1✔
309
      return createExceptionResponse(e);
1✔
310
    }
311
  }
312

313
  @GET
314
  @Produces("application/json")
315
  @PermitAll
316
  @Path("/v2")
317
  @Deprecated
318
  public Response findAllDatasetsAvailableToUser(@Auth AuthUser authUser,
319
      @QueryParam("asCustodian") Boolean asCustodian) {
320
    try {
321
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
322
      List<Dataset> datasets = (Objects.nonNull(asCustodian) && asCustodian) ?
1✔
323
          datasetService.findDatasetsByCustodian(user) :
1✔
324
          datasetService.findAllDatasetsByUser(user);
1✔
325
      return Response.ok(datasets).build();
1✔
326
    } catch (Exception e) {
×
327
      return createExceptionResponse(e);
×
328
    }
329
  }
330

331
  @GET
332
  @Produces("application/json")
333
  @PermitAll
334
  @Path("/v3")
335
  public Response findAllDatasetStudySummaries(@Auth AuthUser authUser) {
336
    try {
337
      userService.findUserByEmail(authUser.getEmail());
1✔
338
      List<DatasetStudySummary> summaries = datasetService.findAllDatasetStudySummaries();
1✔
339
      return Response.ok(summaries).build();
1✔
NEW
340
    } catch (Exception e) {
×
NEW
341
      return createExceptionResponse(e);
×
342
    }
343
  }
344

345
  @GET
346
  @Produces("application/json")
347
  @PermitAll
348
  @Path("/role/{roleName}")
349
  public Response findDatasetsAccordingToRole(
350
      @Auth AuthUser authUser,
351
      @PathParam("roleName") String roleName) {
352
    try {
353
      User user = userService.findUserByEmail(authUser.getEmail());
×
354
      validateUserHasRoleName(user, roleName);
×
355
      UserRoles role = UserRoles.getUserRoleFromName(roleName);
×
356
      if (Objects.isNull(role)) {
×
357
        throw new BadRequestException("Invalid role selection: " + roleName);
×
358
      }
359
      List<Dataset> datasets = switch (role) {
×
360
        case ADMIN -> datasetService.findAllDatasets();
×
361
        case CHAIRPERSON -> datasetService.findDatasetsForChairperson(user);
×
362
        case DATASUBMITTER -> datasetService.findDatasetsForDataSubmitter(user);
×
363
        default -> datasetService.findPublicDatasets();
×
364
      };
365
      return Response.ok(datasets).build();
×
366
    } catch (Exception e) {
×
367
      return createExceptionResponse(e);
×
368
    }
369
  }
370

371
  @GET
372
  @Path("/v2/{datasetId}")
373
  @Produces("application/json")
374
  @PermitAll
375
  public Response getDataset(@PathParam("datasetId") Integer datasetId) {
376
    try {
377
      Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
378
      if (Objects.isNull(dataset)) {
1✔
379
        throw new NotFoundException("Could not find the dataset with id: " + datasetId.toString());
1✔
380
      }
381
      return Response.ok(dataset).build();
1✔
382
    } catch (Exception e) {
1✔
383
      return createExceptionResponse(e);
1✔
384
    }
385
  }
386

387
  @GET
388
  @Path("/registration/{datasetIdentifier}")
389
  @Produces(MediaType.APPLICATION_JSON)
390
  @PermitAll
391
  public Response getRegistrationFromDatasetIdentifier(@Auth AuthUser authUser,
392
      @PathParam("datasetIdentifier") String datasetIdentifier) {
393
    try {
394
      Dataset dataset = datasetService.findDatasetByIdentifier(datasetIdentifier);
1✔
395
      if (Objects.isNull(dataset)) {
1✔
396
        throw new NotFoundException(
1✔
397
            "No dataset exists for dataset identifier: " + datasetIdentifier);
398
      }
399
      Study study;
400
      if (dataset.getStudy() != null && dataset.getStudy().getStudyId() != null) {
1✔
401
        study = datasetService.findStudyById(dataset.getStudy().getStudyId());
1✔
402
      } else {
403
        throw new NotFoundException("No study exists for dataset identifier: " + datasetIdentifier);
×
404
      }
405
      DatasetRegistrationSchemaV1 registration = new DatasetRegistrationSchemaV1Builder().build(
1✔
406
          study, List.of(dataset));
1✔
407
      String entity = GsonUtil.buildGsonNullSerializer().toJson(registration);
1✔
408
      return Response.ok().entity(entity).build();
1✔
409
    } catch (Exception e) {
1✔
410
      return createExceptionResponse(e);
1✔
411
    }
412
  }
413

414
  @GET
415
  @Path("/batch")
416
  @Produces("application/json")
417
  @PermitAll
418
  public Response getDatasets(@QueryParam("ids") List<Integer> datasetIds) {
419
    try {
420
      List<Dataset> datasets = datasetService.findDatasetsByIds(datasetIds);
1✔
421

422
      Set<Integer> foundIds = datasets.stream().map(Dataset::getDataSetId)
1✔
423
          .collect(Collectors.toSet());
1✔
424
      if (!foundIds.containsAll(datasetIds)) {
1✔
425
        // find the differences
426
        List<Integer> differences = new ArrayList<>(datasetIds)
1✔
427
          .stream()
1✔
428
          .filter(Objects::nonNull)
1✔
429
          .filter(Predicate.not(foundIds::contains))
1✔
430
          .toList();
1✔
431
        throw new NotFoundException(
1✔
432
            "Could not find datasets with ids: "
433
                + String.join(",",
1✔
434
                differences.stream().map(Object::toString).collect(Collectors.toSet())));
1✔
435

436
      }
437
      return Response.ok(datasets).build();
1✔
438
    } catch (Exception e) {
1✔
439
      return createExceptionResponse(e);
1✔
440
    }
441
  }
442

443
  @GET
444
  @Consumes("application/json")
445
  @Produces("application/json")
446
  @Path("/validate")
447
  @PermitAll
448
  public Response validateDatasetName(@QueryParam("name") String name) {
449
    try {
450
      Dataset datasetWithName = datasetService.getDatasetByName(name);
1✔
451
      return Response.ok().entity(datasetWithName.getDataSetId()).build();
1✔
452
    } catch (Exception e) {
1✔
453
      throw new NotFoundException("Could not find the dataset with name: " + name);
1✔
454
    }
455
  }
456

457
  @GET
458
  @Consumes("application/json")
459
  @Produces("application/json")
460
  @Path("/studyNames")
461
  @PermitAll
462
  public Response findAllStudyNames() {
463
    try {
464
      Set<String> studyNames = datasetService.findAllStudyNames();
1✔
465
      return Response.ok(studyNames).build();
1✔
466
    } catch (Exception e) {
1✔
467
      return createExceptionResponse(e);
1✔
468
    }
469
  }
470

471
  @GET
472
  @Consumes("application/json")
473
  @Produces("application/json")
474
  @Path("/datasetNames")
475
  @PermitAll
476
  public Response findAllDatasetNames() {
477
    try {
478
      List<String> datasetNames = datasetService.findAllDatasetNames();
×
479
      return Response.ok(datasetNames).build();
×
480
    } catch (Exception e) {
×
481
      return createExceptionResponse(e);
×
482
    }
483
  }
484

485
  @POST
486
  @Path("/download")
487
  @Consumes(MediaType.APPLICATION_JSON)
488
  @Produces(MediaType.APPLICATION_JSON)
489
  @PermitAll
490
  public Response downloadDataSets(List<Integer> idList) {
491
    try {
492
      String msg = "GETing DataSets to download";
1✔
493
      logDebug(msg);
1✔
494

495
      Gson gson = new Gson();
1✔
496
      HashMap<String, String> datasets = new HashMap<>();
1✔
497

498
      Collection<Dictionary> headers = datasetService.describeDictionaryByReceiveOrder();
1✔
499

500
      StringBuilder sb = new StringBuilder();
1✔
501
      String TSV_DELIMITER = "\t";
1✔
502
      for (Dictionary header : headers) {
1✔
503
        if (sb.length() > 0) {
×
504
          sb.append(TSV_DELIMITER);
×
505
        }
506
        sb.append(header.getKey());
×
507
      }
×
508
      sb.append(END_OF_LINE);
1✔
509

510
      if (CollectionUtils.isEmpty(idList)) {
1✔
511
        datasets.put("datasets", sb.toString());
1✔
512
        return Response.ok(gson.toJson(datasets), MediaType.APPLICATION_JSON).build();
1✔
513
      }
514

515
      Collection<DatasetDTO> rows = datasetService.describeDataSetsByReceiveOrder(idList);
1✔
516

517
      for (DatasetDTO row : rows) {
1✔
518
        StringBuilder sbr = new StringBuilder();
1✔
519
        DatasetPropertyDTO property = new DatasetPropertyDTO("Consent ID", row.getConsentId());
1✔
520
        List<DatasetPropertyDTO> props = row.getProperties();
1✔
521
        props.add(property);
1✔
522
        for (DatasetPropertyDTO prop : props) {
1✔
523
          if (sbr.length() > 0) {
1✔
524
            sbr.append(TSV_DELIMITER);
1✔
525
          }
526
          sbr.append(prop.getPropertyValue());
1✔
527
        }
1✔
528
        sbr.append(END_OF_LINE);
1✔
529
        sb.append(sbr);
1✔
530
      }
1✔
531
      String tsv = sb.toString();
1✔
532

533
      datasets.put("datasets", tsv);
1✔
534
      return Response.ok(gson.toJson(datasets), MediaType.APPLICATION_JSON).build();
1✔
535
    } catch (Exception e) {
1✔
536
      return createExceptionResponse(e);
1✔
537
    }
538
  }
539

540
  @DELETE
541
  @Produces(MediaType.APPLICATION_JSON)
542
  @Path("/{datasetId}")
543
  @RolesAllowed({ADMIN, CHAIRPERSON, DATASUBMITTER})
544
  public Response delete(@Auth AuthUser authUser, @PathParam("datasetId") Integer datasetId,
545
      @Context UriInfo info) {
546
    try {
547
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
548
      Dataset dataset = datasetService.findDatasetById(datasetId);
1✔
549
      if (Objects.nonNull(dataset.getDeletable()) && !dataset.getDeletable()) {
1✔
550
        throw new BadRequestException("Dataset is in use and cannot be deleted.");
×
551
      }
552
      // Validate that the admin/chairperson/data submitter has edit/delete access to this dataset
553
      validateDatasetDacAccess(user, dataset);
1✔
554
      try {
555
        datasetService.deleteDataset(datasetId, user.getUserId());
1✔
556
      } catch (Exception e) {
×
557
        logException(e);
×
558
        return createExceptionResponse(e);
×
559
      }
1✔
560
      try {
561
        elasticSearchService.deleteIndex(datasetId);
1✔
562
      } catch (IOException e) {
×
563
        logException(e);
×
564
        return createExceptionResponse(e);
×
565
      }
1✔
566
      return Response.ok().build();
1✔
567
    } catch (Exception e) {
1✔
568
      return createExceptionResponse(e);
1✔
569
    }
570
  }
571

572
  @POST
573
  @Path("/index")
574
  @RolesAllowed(ADMIN)
575
  public Response indexDatasets() {
576
    try {
577
      var datasets = datasetService.findAllDatasets();
1✔
578
      return elasticSearchService.indexDatasets(datasets);
1✔
579
    } catch (Exception e) {
×
580
      return createExceptionResponse(e);
×
581
    }
582
  }
583

584
  @POST
585
  @Path("/index/{datasetId}")
586
  @RolesAllowed(ADMIN)
587
  public Response indexDataset(@PathParam("datasetId") Integer datasetId) {
588
    try {
589
      var dataset = datasetService.findDatasetById(datasetId);
1✔
590
      return elasticSearchService.indexDataset(dataset);
1✔
591
    } catch (Exception e) {
×
592
      return createExceptionResponse(e);
×
593
    }
594
  }
595

596
  @DELETE
597
  @Path("/index/{datasetId}")
598
  @RolesAllowed(ADMIN)
599
  public Response deleteDatasetIndex(@PathParam("datasetId") Integer datasetId) {
600
    try {
601
      return elasticSearchService.deleteIndex(datasetId);
1✔
602
    } catch (Exception e) {
×
603
      return createExceptionResponse(e);
×
604
    }
605
  }
606

607
  @GET
608
  @Path("/search")
609
  @Produces("application/json")
610
  @PermitAll
611
  public Response searchDatasets(
612
      @Auth AuthUser authUser,
613
      @QueryParam("query") String query,
614
      @QueryParam("access") @DefaultValue("controlled") String accessString) {
615
    try {
616
      AccessManagement accessManagement = AccessManagement.fromValue(accessString.toLowerCase());
1✔
617
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
618
      List<Dataset> datasets = datasetService.searchDatasets(query, accessManagement, user);
1✔
619
      return Response.ok().entity(unmarshal(datasets)).build();
1✔
620
    } catch (Exception e) {
×
621
      return createExceptionResponse(e);
×
622
    }
623
  }
624

625
  @GET
626
  @Produces("application/json")
627
  @Path("/autocomplete")
628
  @PermitAll
629
  @Timed
630
  public Response autocompleteDatasets(
631
      @Auth AuthUser authUser,
632
      @QueryParam("query") String query) {
633
    try {
634
      userService.findUserByEmail(authUser.getEmail());
1✔
635
      List<DatasetSummary> datasets = datasetService.searchDatasetSummaries(query);
1✔
636
      return Response.ok(datasets).build();
1✔
637
    } catch (Exception e) {
×
638
      return createExceptionResponse(e);
×
639
    }
640
  }
641

642
  @POST
643
  @Path("/search/index")
644
  @Consumes("application/json")
645
  @Produces("application/json")
646
  @PermitAll
647
  @Timed
648
  public Response searchDatasetIndex(@Auth AuthUser authUser, String query) {
649
    try {
650
      userService.findUserByEmail(authUser.getEmail());
1✔
651
      return elasticSearchService.searchDatasets(query);
1✔
652
    } catch (Exception e) {
×
653
      return createExceptionResponse(e);
×
654
    }
655
  }
656

657
  @PUT
658
  @Produces("application/json")
659
  @RolesAllowed(ADMIN)
660
  @Path("/{id}/datause")
661
  public Response updateDatasetDataUse(@Auth AuthUser authUser, @PathParam("id") Integer id,
662
      String dataUseJson) {
663
    try {
664
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
665
      Gson gson = new Gson();
1✔
666
      DataUse dataUse = gson.fromJson(dataUseJson, DataUse.class);
1✔
667
      Dataset originalDataset = datasetService.findDatasetById(id);
1✔
668
      if (Objects.isNull(originalDataset)) {
1✔
669
        throw new NotFoundException("Dataset not found: " + id);
×
670
      }
671
      if (Objects.equals(dataUse, originalDataset.getDataUse())) {
1✔
672
        return Response.notModified().entity(originalDataset).build();
1✔
673
      }
674
      Dataset dataset = datasetService.updateDatasetDataUse(user, id, dataUse);
1✔
675
      return Response.ok().entity(dataset).build();
1✔
676
    } catch (JsonSyntaxException jse) {
1✔
677
      return createExceptionResponse(
1✔
678
          new BadRequestException("Invalid JSON Syntax: " + dataUseJson));
679
    } catch (Exception e) {
1✔
680
      return createExceptionResponse(e);
1✔
681
    }
682
  }
683

684
  @PUT
685
  @Produces("application/json")
686
  @RolesAllowed(ADMIN)
687
  @Path("/{id}/reprocess/datause")
688
  public Response syncDataUseTranslation(@Auth AuthUser authUser, @PathParam("id") Integer id) {
689
    try {
690
      Dataset ds = datasetService.syncDatasetDataUseTranslation(id);
1✔
691
      return Response.ok(ds).build();
1✔
692
    } catch (Exception e) {
1✔
693
      return createExceptionResponse(e);
1✔
694
    }
695
  }
696

697
  @GET
698
  @Produces(MediaType.APPLICATION_OCTET_STREAM)
699
  @PermitAll
700
  @Path("/{datasetId}/approved/users")
701
  public Response downloadDatasetApprovedUsers(@Auth AuthUser authUser,
702
      @PathParam("datasetId") Integer datasetId) {
703
    try {
704
      String content = darService.getDatasetApprovedUsersContent(authUser, datasetId);
1✔
705
      return Response.ok(content)
1✔
706
          .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=DatasetApprovedUsers.tsv")
1✔
707
          .build();
1✔
708
    } catch (Exception e) {
1✔
709
      return createExceptionResponse(e);
1✔
710
    }
711
  }
712

713
  private void validateDatasetDacAccess(User user, Dataset dataset) {
714
    if (user.hasUserRole(UserRoles.ADMIN)) {
1✔
715
      return;
1✔
716
    }
717
    if (user.hasUserRole(UserRoles.DATASUBMITTER)) {
1✔
718
      if (dataset.getCreateUserId().equals(user.getUserId())) {
×
719
        return;
×
720
      }
721
      // If the user doesn't have any other appropriate role, we can return an error here,
722
      // otherwise, continue checking if the user has chair permissions
723
      if (!user.hasUserRole(UserRoles.CHAIRPERSON)) {
×
724
        logWarn("User does not have permission to delete dataset: " + user.getEmail());
×
725
        throw new NotFoundException();
×
726
      }
727
    }
728
    List<Integer> dacIds = user.getRoles().stream()
1✔
729
        .filter(r -> r.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId()))
1✔
730
        .map(UserRole::getDacId)
1✔
731
        .toList();
1✔
732
    if (dacIds.isEmpty()) {
1✔
733
      // Something went very wrong here. A chairperson with no dac ids is an error
734
      logWarn("Unable to find dac ids for chairperson user: " + user.getEmail());
×
735
      throw new NotFoundException();
×
736
    } else {
737
      if (Objects.isNull(dataset) || Objects.isNull(dataset.getDacId())) {
1✔
738
        logWarn("Cannot find a valid dac id for dataset: " + dataset.getDataSetId());
1✔
739
        throw new NotFoundException();
1✔
740
      } else {
741
        if (!dacIds.contains(dataset.getDacId())) {
1✔
742
          throw new NotFoundException();
1✔
743
        }
744
      }
745
    }
746
  }
1✔
747
}
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