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

IQSS / dataverse / #22775

25 Jul 2024 10:51PM UTC coverage: 20.663% (+0.01%) from 20.651%
#22775

push

github

web-flow
Merge pull request #10644 from IQSS/10633-create-collection-api-ext

addDataverse API endpoint extension for input levels and facet list setup

28 of 106 new or added lines in 7 files covered. (26.42%)

4 existing lines in 2 files now uncovered.

17385 of 84134 relevant lines covered (20.66%)

0.21 hits per line

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

5.69
/src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
1
package edu.harvard.iq.dataverse.api;
2

3
import edu.harvard.iq.dataverse.*;
4
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
5
import edu.harvard.iq.dataverse.api.datadeposit.SwordServiceBean;
6
import edu.harvard.iq.dataverse.api.dto.DataverseMetadataBlockFacetDTO;
7
import edu.harvard.iq.dataverse.authorization.DataverseRole;
8

9
import edu.harvard.iq.dataverse.api.dto.ExplicitGroupDTO;
10
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
11
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
12
import edu.harvard.iq.dataverse.api.imports.ImportException;
13
import edu.harvard.iq.dataverse.api.imports.ImportServiceBean;
14
import edu.harvard.iq.dataverse.authorization.Permission;
15
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
16
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroup;
17
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupProvider;
18
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean;
19
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
20
import edu.harvard.iq.dataverse.authorization.users.User;
21
import edu.harvard.iq.dataverse.dataverse.DataverseUtil;
22
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
23
import edu.harvard.iq.dataverse.engine.command.impl.*;
24
import edu.harvard.iq.dataverse.pidproviders.PidProvider;
25
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
26
import edu.harvard.iq.dataverse.settings.JvmSettings;
27
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
28
import edu.harvard.iq.dataverse.util.BundleUtil;
29
import edu.harvard.iq.dataverse.util.ConstraintViolationUtil;
30
import edu.harvard.iq.dataverse.util.StringUtil;
31
import static edu.harvard.iq.dataverse.util.StringUtil.nonEmpty;
32
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.*;
33

34
import edu.harvard.iq.dataverse.util.json.JSONLDUtil;
35
import edu.harvard.iq.dataverse.util.json.JsonParseException;
36
import edu.harvard.iq.dataverse.util.json.JsonPrinter;
37
import edu.harvard.iq.dataverse.util.json.JsonUtil;
38

39
import java.io.StringReader;
40
import java.util.*;
41
import java.util.logging.Level;
42
import java.util.logging.Logger;
43
import jakarta.ejb.EJB;
44
import jakarta.ejb.EJBException;
45
import jakarta.ejb.Stateless;
46
import jakarta.json.*;
47
import jakarta.json.JsonValue.ValueType;
48
import jakarta.json.stream.JsonParsingException;
49
import jakarta.validation.ConstraintViolationException;
50
import jakarta.ws.rs.BadRequestException;
51
import jakarta.ws.rs.Consumes;
52
import jakarta.ws.rs.DELETE;
53
import jakarta.ws.rs.GET;
54
import jakarta.ws.rs.POST;
55
import jakarta.ws.rs.PUT;
56
import jakarta.ws.rs.Path;
57
import jakarta.ws.rs.PathParam;
58
import jakarta.ws.rs.Produces;
59
import jakarta.ws.rs.QueryParam;
60
import jakarta.ws.rs.container.ContainerRequestContext;
61
import jakarta.ws.rs.core.MediaType;
62
import jakarta.ws.rs.core.Response;
63
import jakarta.ws.rs.core.Response.Status;
64

65
import java.io.IOException;
66
import java.io.OutputStream;
67
import java.text.MessageFormat;
68
import java.text.SimpleDateFormat;
69
import java.util.stream.Collectors;
70
import jakarta.servlet.http.HttpServletResponse;
71
import jakarta.ws.rs.WebApplicationException;
72
import jakarta.ws.rs.core.Context;
73
import jakarta.ws.rs.core.StreamingOutput;
74
import javax.xml.stream.XMLStreamException;
75

76
/**
77
 * A REST API for dataverses.
78
 *
79
 * @author michael
80
 */
81
@Stateless
82
@Path("dataverses")
83
public class Dataverses extends AbstractApiBean {
1✔
84

85
    private static final Logger logger = Logger.getLogger(Dataverses.class.getCanonicalName());
1✔
86
    private static final SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss");
1✔
87

88
    @EJB
89
    ExplicitGroupServiceBean explicitGroupSvc;
90

91
    @EJB
92
    ImportServiceBean importService;
93
    
94
    @EJB
95
    SettingsServiceBean settingsService;
96
    
97
    @EJB
98
    GuestbookResponseServiceBean guestbookResponseService;
99
    
100
    @EJB
101
    GuestbookServiceBean guestbookService;
102
    
103
    @EJB
104
    DataverseServiceBean dataverseService;
105

106
    @EJB
107
    DataverseLinkingServiceBean linkingService;
108

109
    @EJB
110
    FeaturedDataverseServiceBean featuredDataverseService;
111

112
    @EJB
113
    SwordServiceBean swordService;
114
    
115
    @POST
116
    @AuthRequired
117
    public Response addRoot(@Context ContainerRequestContext crc, String body) {
118
        logger.info("Creating root dataverse");
×
119
        return addDataverse(crc, body, "");
×
120
    }
121

122
    @POST
123
    @AuthRequired
124
    @Path("{identifier}")
125
    public Response addDataverse(@Context ContainerRequestContext crc, String body, @PathParam("identifier") String parentIdtf) {
126
        Dataverse newDataverse;
127
        JsonObject newDataverseJson;
128
        try {
NEW
129
            newDataverseJson = JsonUtil.getJsonObject(body);
×
NEW
130
            newDataverse = jsonParser().parseDataverse(newDataverseJson);
×
131
        } catch (JsonParsingException jpe) {
×
132
            logger.log(Level.SEVERE, "Json: {0}", body);
×
NEW
133
            return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.create.error.jsonparse"), jpe.getMessage()));
×
134
        } catch (JsonParseException ex) {
×
135
            logger.log(Level.SEVERE, "Error parsing dataverse from json: " + ex.getMessage(), ex);
×
NEW
136
            return error(Status.BAD_REQUEST, MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.create.error.jsonparsetodataverse"), ex.getMessage()));
×
137
        }
×
138

139
        try {
NEW
140
            JsonObject metadataBlocksJson = newDataverseJson.getJsonObject("metadataBlocks");
×
NEW
141
            List<DataverseFieldTypeInputLevel> inputLevels = null;
×
NEW
142
            List<MetadataBlock> metadataBlocks = null;
×
NEW
143
            if (metadataBlocksJson != null) {
×
NEW
144
                JsonArray inputLevelsArray = metadataBlocksJson.getJsonArray("inputLevels");
×
NEW
145
                inputLevels = inputLevelsArray != null ? parseInputLevels(inputLevelsArray, newDataverse) : null;
×
146

NEW
147
                JsonArray metadataBlockNamesArray = metadataBlocksJson.getJsonArray("metadataBlockNames");
×
NEW
148
                metadataBlocks = metadataBlockNamesArray != null ? parseNewDataverseMetadataBlocks(metadataBlockNamesArray) : null;
×
149
            }
150

NEW
151
            JsonArray facetIdsArray = newDataverseJson.getJsonArray("facetIds");
×
NEW
152
            List<DatasetFieldType> facetList = facetIdsArray != null ? parseFacets(facetIdsArray) : null;
×
153

154
            if (!parentIdtf.isEmpty()) {
×
155
                Dataverse owner = findDataverseOrDie(parentIdtf);
×
NEW
156
                newDataverse.setOwner(owner);
×
157
            }
158

159
            // set the dataverse - contact relationship in the contacts
NEW
160
            for (DataverseContact dc : newDataverse.getDataverseContacts()) {
×
NEW
161
                dc.setDataverse(newDataverse);
×
UNCOV
162
            }
×
163

164
            AuthenticatedUser u = getRequestAuthenticatedUserOrDie(crc);
×
NEW
165
            newDataverse = execCommand(new CreateDataverseCommand(newDataverse, createDataverseRequest(u), facetList, inputLevels, metadataBlocks));
×
NEW
166
            return created("/dataverses/" + newDataverse.getAlias(), json(newDataverse));
×
UNCOV
167
        } catch (WrappedResponse ww) {
×
168

169
            String error = ConstraintViolationUtil.getErrorStringForConstraintViolations(ww.getCause());
×
170
            if (!error.isEmpty()) {
×
171
                logger.log(Level.INFO, error);
×
172
                return ww.refineResponse(error);
×
173
            }
174
            return ww.getResponse();
×
175

176
        } catch (EJBException ex) {
×
177
            Throwable cause = ex;
×
178
            StringBuilder sb = new StringBuilder();
×
179
            sb.append("Error creating dataverse.");
×
180
            while (cause.getCause() != null) {
×
181
                cause = cause.getCause();
×
182
                if (cause instanceof ConstraintViolationException) {
×
183
                    sb.append(ConstraintViolationUtil.getErrorStringForConstraintViolations(cause));
×
184
                }
185
            }
186
            logger.log(Level.SEVERE, sb.toString());
×
187
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Error creating dataverse: " + sb.toString());
×
188
        } catch (Exception ex) {
×
189
            logger.log(Level.SEVERE, "Error creating dataverse", ex);
×
190
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Error creating dataverse: " + ex.getMessage());
×
191

192
        }
193
    }
194

195
    private List<MetadataBlock> parseNewDataverseMetadataBlocks(JsonArray metadataBlockNamesArray) throws WrappedResponse {
NEW
196
        List<MetadataBlock> selectedMetadataBlocks = new ArrayList<>();
×
NEW
197
        for (JsonString metadataBlockName : metadataBlockNamesArray.getValuesAs(JsonString.class)) {
×
NEW
198
            MetadataBlock metadataBlock = metadataBlockSvc.findByName(metadataBlockName.getString());
×
NEW
199
            if (metadataBlock == null) {
×
NEW
200
                String errorMessage = MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.metadatablocks.error.invalidmetadatablockname"), metadataBlockName);
×
NEW
201
                throw new WrappedResponse(badRequest(errorMessage));
×
202
            }
NEW
203
            selectedMetadataBlocks.add(metadataBlock);
×
NEW
204
        }
×
205

NEW
206
        return selectedMetadataBlocks;
×
207
    }
208

209
    @POST
210
    @AuthRequired
211
    @Path("{identifier}/validateDatasetJson")
212
    @Consumes("application/json")
213
    public Response validateDatasetJson(@Context ContainerRequestContext crc, String body, @PathParam("identifier") String idtf) {
214
        User u = getRequestUser(crc);
×
215
        try {
216
            String validationMessage = execCommand(new ValidateDatasetJsonCommand(createDataverseRequest(u), findDataverseOrDie(idtf), body));
×
217
            return ok(validationMessage);
×
218
        } catch (WrappedResponse ex) {
×
219
            Logger.getLogger(Dataverses.class.getName()).log(Level.SEVERE, null, ex);
×
220
            return ex.getResponse();
×
221
        }
222
    }
223
    
224
    @GET
225
    @AuthRequired
226
    @Path("{identifier}/datasetSchema")
227
    @Produces(MediaType.APPLICATION_JSON)
228
    public Response getDatasetSchema(@Context ContainerRequestContext crc, @PathParam("identifier") String idtf) {
229
        User u = getRequestUser(crc);
×
230

231
        try {
232
            String datasetSchema = execCommand(new GetDatasetSchemaCommand(createDataverseRequest(u), findDataverseOrDie(idtf)));
×
233
            JsonObject jsonObject = JsonUtil.getJsonObject(datasetSchema);
×
234
            return Response.ok(jsonObject).build();
×
235
        } catch (WrappedResponse ex) {
×
236
            Logger.getLogger(Dataverses.class.getName()).log(Level.SEVERE, null, ex);
×
237
            return ex.getResponse();
×
238
        }
239
    }
240
            
241
    
242

243
    @POST
244
    @AuthRequired
245
    @Path("{identifier}/datasets")
246
    @Consumes("application/json")
247
    public Response createDataset(@Context ContainerRequestContext crc, String jsonBody, @PathParam("identifier") String parentIdtf, @QueryParam("doNotValidate") String doNotValidateParam) {
248
        try {
249
            logger.fine("Json is: " + jsonBody);
×
250
            User u = getRequestUser(crc);
×
251
            Dataverse owner = findDataverseOrDie(parentIdtf);
×
252
            Dataset ds = parseDataset(jsonBody);
×
253
            ds.setOwner(owner);
×
254
            // Will make validation happen always except for the (rare) occasion of all three conditions are true
255
            boolean validate = ! ( u.isAuthenticated() && StringUtil.isTrue(doNotValidateParam) &&
×
256
                JvmSettings.API_ALLOW_INCOMPLETE_METADATA.lookupOptional(Boolean.class).orElse(false) );
×
257

258
            if (ds.getVersions().isEmpty()) {
×
259
                return badRequest(BundleUtil.getStringFromBundle("dataverses.api.create.dataset.error.mustIncludeVersion"));
×
260
            }
261
            
262
            if (!ds.getFiles().isEmpty() && !u.isSuperuser()){
×
263
                return badRequest(BundleUtil.getStringFromBundle("dataverses.api.create.dataset.error.superuserFiles"));
×
264
            }
265

266
            //Throw BadRequestException if metadataLanguage isn't compatible with setting
267
            DataverseUtil.checkMetadataLangauge(ds, owner, settingsService.getBaseMetadataLanguageMap(null, true));
×
268

269
            // clean possible version metadata
270
            DatasetVersion version = ds.getVersions().get(0);
×
271

272
            if (!validate && (version.getDatasetAuthors().isEmpty() || version.getDatasetAuthors().stream().anyMatch(a -> a.getName() == null || a.getName().isEmpty()))) {
×
273
                return badRequest(BundleUtil.getStringFromBundle("dataverses.api.create.dataset.error.mustIncludeAuthorName"));
×
274
            }
275

276
            version.setMinorVersionNumber(null);
×
277
            version.setVersionNumber(null);
×
278
            version.setVersionState(DatasetVersion.VersionState.DRAFT);
×
279
            version.getTermsOfUseAndAccess().setFileAccessRequest(true);
×
280
            version.getTermsOfUseAndAccess().setDatasetVersion(version);
×
281

282
            ds.setAuthority(null);
×
283
            ds.setIdentifier(null);
×
284
            ds.setProtocol(null);
×
285
            ds.setGlobalIdCreateTime(null);
×
286
            Dataset managedDs = null;
×
287
            try {
288
                managedDs = execCommand(new CreateNewDatasetCommand(ds, createDataverseRequest(u), null, validate));
×
289
            } catch (WrappedResponse ww) {
×
290
                Throwable cause = ww.getCause();
×
291
                StringBuilder sb = new StringBuilder();
×
292
                if (cause == null) {
×
293
                    return ww.refineResponse("cause was null!");
×
294
                }
295
                while (cause.getCause() != null) {
×
296
                    cause = cause.getCause();
×
297
                    if (cause instanceof ConstraintViolationException) {
×
298
                        sb.append(ConstraintViolationUtil.getErrorStringForConstraintViolations(cause));
×
299
                    }
300
                }
301
                String error = sb.toString();
×
302
                if (!error.isEmpty()) {
×
303
                    logger.log(Level.INFO, error);
×
304
                    return ww.refineResponse(error);
×
305
                }
306
                return ww.getResponse();
×
307
            }
×
308

309
            return created("/datasets/" + managedDs.getId(),
×
310
                    Json.createObjectBuilder()
×
311
                            .add("id", managedDs.getId())
×
312
                            .add("persistentId", managedDs.getGlobalId().asString())
×
313
            );
314

315
        } catch (WrappedResponse ex) {
×
316
            return ex.getResponse();
×
317
        }
318
    }
319
    
320
    @POST
321
    @AuthRequired
322
    @Path("{identifier}/datasets")
323
    @Consumes("application/ld+json, application/json-ld")
324
    public Response createDatasetFromJsonLd(@Context ContainerRequestContext crc, String jsonLDBody, @PathParam("identifier") String parentIdtf) {
325
        try {
326
            User u = getRequestUser(crc);
×
327
            Dataverse owner = findDataverseOrDie(parentIdtf);
×
328
            Dataset ds = new Dataset();
×
329

330
            ds.setOwner(owner);
×
331
            ds = JSONLDUtil.updateDatasetMDFromJsonLD(ds, jsonLDBody, metadataBlockSvc, datasetFieldSvc, false, false, licenseSvc);
×
332
            
333
            ds.setOwner(owner);
×
334

335
            // clean possible dataset/version metadata
336
            DatasetVersion version = ds.getVersions().get(0);
×
337
            version.setMinorVersionNumber(null);
×
338
            version.setVersionNumber(null);
×
339
            version.setVersionState(DatasetVersion.VersionState.DRAFT);
×
340
            version.getTermsOfUseAndAccess().setFileAccessRequest(true);
×
341
            version.getTermsOfUseAndAccess().setDatasetVersion(version);
×
342

343
            ds.setAuthority(null);
×
344
            ds.setIdentifier(null);
×
345
            ds.setProtocol(null);
×
346
            ds.setGlobalIdCreateTime(null);
×
347
            
348
            //Throw BadRequestException if metadataLanguage isn't compatible with setting
349
            DataverseUtil.checkMetadataLangauge(ds, owner, settingsService.getBaseMetadataLanguageMap(null, true));
×
350

351
            Dataset managedDs = execCommand(new CreateNewDatasetCommand(ds, createDataverseRequest(u)));
×
352
            return created("/datasets/" + managedDs.getId(),
×
353
                    Json.createObjectBuilder()
×
354
                            .add("id", managedDs.getId())
×
355
                            .add("persistentId", managedDs.getGlobalId().asString())
×
356
            );
357

358
        } catch (WrappedResponse ex) {
×
359
            return ex.getResponse();
×
360
        }
361
    }
362

363
    @POST
364
    @AuthRequired
365
    @Path("{identifier}/datasets/:import")
366
    public Response importDataset(@Context ContainerRequestContext crc, String jsonBody, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) {
367
        try {
368
            User u = getRequestUser(crc);
×
369
            if (!u.isSuperuser()) {
×
370
                return error(Status.FORBIDDEN, "Not a superuser");
×
371
            }
372
            Dataverse owner = findDataverseOrDie(parentIdtf);
×
373
            Dataset ds = parseDataset(jsonBody);
×
374
            ds.setOwner(owner);
×
375

376
            if (ds.getVersions().isEmpty()) {
×
377
                return badRequest("Supplied json must contain a single dataset version.");
×
378
            }
379

380
            //Throw BadRequestException if metadataLanguage isn't compatible with setting
381
            DataverseUtil.checkMetadataLangauge(ds, owner, settingsService.getBaseMetadataLanguageMap(null, true));
×
382

383
            DatasetVersion version = ds.getVersions().get(0);
×
384
            if (version.getVersionState() == null) {
×
385
                version.setVersionState(DatasetVersion.VersionState.DRAFT);
×
386
            }
387

388
            if (nonEmpty(pidParam)) {
×
389
                if (!GlobalId.verifyImportCharacters(pidParam)) {
×
390
                    return badRequest("PID parameter contains characters that are not allowed by the Dataverse application. On import, the PID must only contain characters specified in this regex: " + BundleUtil.getStringFromBundle("pid.allowedCharacters"));
×
391
                }
392
                Optional<GlobalId> maybePid = PidProvider.parse(pidParam);
×
393
                if (maybePid.isPresent()) {
×
394
                    ds.setGlobalId(maybePid.get());
×
395
                } else {
396
                    // unparsable PID passed. Terminate.
397
                    return badRequest("Cannot parse the PID parameter '" + pidParam + "'. Make sure it is in valid form - see Dataverse Native API documentation.");
×
398
                }
399
            }
400

401
            if (ds.getIdentifier() == null) {
×
402
                return badRequest("Please provide a persistent identifier, either by including it in the JSON, or by using the pid query parameter.");
×
403
            }
404
            boolean shouldRelease = StringUtil.isTrue(releaseParam);
×
405
            DataverseRequest request = createDataverseRequest(u);
×
406

407
            if (shouldRelease) {
×
408
                DatasetVersion latestVersion = ds.getLatestVersion();
×
409
                latestVersion.setVersionState(DatasetVersion.VersionState.RELEASED);
×
410
                latestVersion.setVersionNumber(1l);
×
411
                latestVersion.setMinorVersionNumber(0l);
×
412
                if (latestVersion.getCreateTime() != null) {
×
413
                    latestVersion.setCreateTime(new Date());
×
414
                }
415
                if (latestVersion.getLastUpdateTime() != null) {
×
416
                    latestVersion.setLastUpdateTime(new Date());
×
417
                }
418
            }
419

420
            Dataset managedDs = execCommand(new ImportDatasetCommand(ds, request));
×
421
            JsonObjectBuilder responseBld = Json.createObjectBuilder()
×
422
                    .add("id", managedDs.getId())
×
423
                    .add("persistentId", managedDs.getGlobalId().asString());
×
424

425
            if (shouldRelease) {
×
426
                PublishDatasetResult res = execCommand(new PublishDatasetCommand(managedDs, request, false, shouldRelease));
×
427
                responseBld.add("releaseCompleted", res.isCompleted());
×
428
            }
429

430
            return created("/datasets/" + managedDs.getId(), responseBld);
×
431

432
        } catch (WrappedResponse ex) {
×
433
            return ex.getResponse();
×
434
        }
435
    }
436

437
    // TODO decide if I merge importddi with import just below (xml and json on same api, instead of 2 api)
438
    @POST
439
    @AuthRequired
440
    @Path("{identifier}/datasets/:importddi")
441
    public Response importDatasetDdi(@Context ContainerRequestContext crc, String xml, @PathParam("identifier") String parentIdtf, @QueryParam("pid") String pidParam, @QueryParam("release") String releaseParam) {
442
        try {
443
            User u = getRequestUser(crc);
×
444
            if (!u.isSuperuser()) {
×
445
                return error(Status.FORBIDDEN, "Not a superuser");
×
446
            }
447
            Dataverse owner = findDataverseOrDie(parentIdtf);
×
448
            Dataset ds = null;
×
449
            try {
450
                ds = jsonParser().parseDataset(importService.ddiToJson(xml));
×
451
                DataverseUtil.checkMetadataLangauge(ds, owner, settingsService.getBaseMetadataLanguageMap(null, true));
×
452
            } catch (JsonParseException jpe) {
×
453
                return badRequest("Error parsing data as Json: "+jpe.getMessage());
×
454
            } catch (ImportException e) {
×
455
                return badRequest("Invalid DOI found in the XML: "+e.getMessage());
×
456
            } catch (XMLStreamException e) {
×
457
                return badRequest("Invalid file content: "+e.getMessage());
×
458
            }
×
459

460
            swordService.addDatasetSubjectIfMissing(ds.getLatestVersion());
×
461

462
            ds.setOwner(owner);
×
463
            if (nonEmpty(pidParam)) {
×
464
                if (!GlobalId.verifyImportCharacters(pidParam)) {
×
465
                    return badRequest("PID parameter contains characters that are not allowed by the Dataverse application. On import, the PID must only contain characters specified in this regex: " + BundleUtil.getStringFromBundle("pid.allowedCharacters"));
×
466
                }
467
                Optional<GlobalId> maybePid = PidProvider.parse(pidParam);
×
468
                if (maybePid.isPresent()) {
×
469
                    ds.setGlobalId(maybePid.get());
×
470
                } else {
471
                    // unparsable PID passed. Terminate.
472
                    return badRequest("Cannot parse the PID parameter '" + pidParam + "'. Make sure it is in valid form - see Dataverse Native API documentation.");
×
473
                }
474
            }
475

476
            boolean shouldRelease = StringUtil.isTrue(releaseParam);
×
477
            DataverseRequest request = createDataverseRequest(u);
×
478

479
            Dataset managedDs = null;
×
480
            if (nonEmpty(pidParam)) {
×
481
                managedDs = execCommand(new ImportDatasetCommand(ds, request));
×
482
            }
483
            else {
484
                managedDs = execCommand(new CreateNewDatasetCommand(ds, request));
×
485
            }
486

487
            JsonObjectBuilder responseBld = Json.createObjectBuilder()
×
488
                    .add("id", managedDs.getId())
×
489
                    .add("persistentId", managedDs.getGlobalId().toString());
×
490

491
            if (shouldRelease) {
×
492
                DatasetVersion latestVersion = ds.getLatestVersion();
×
493
                latestVersion.setVersionState(DatasetVersion.VersionState.RELEASED);
×
494
                latestVersion.setVersionNumber(1l);
×
495
                latestVersion.setMinorVersionNumber(0l);
×
496
                if (latestVersion.getCreateTime() != null) {
×
497
                    latestVersion.setCreateTime(new Date());
×
498
                }
499
                if (latestVersion.getLastUpdateTime() != null) {
×
500
                    latestVersion.setLastUpdateTime(new Date());
×
501
                }
502
                PublishDatasetResult res = execCommand(new PublishDatasetCommand(managedDs, request, false, shouldRelease));
×
503
                responseBld.add("releaseCompleted", res.isCompleted());
×
504
            }
505

506
            return created("/datasets/" + managedDs.getId(), responseBld);
×
507

508
        } catch (WrappedResponse ex) {
×
509
            return ex.getResponse();
×
510
        }
511
    }
512
    
513
    @POST
514
    @AuthRequired
515
    @Path("{identifier}/datasets/:startmigration")
516
    @Consumes("application/ld+json, application/json-ld")
517
    public Response recreateDataset(@Context ContainerRequestContext crc, String jsonLDBody, @PathParam("identifier") String parentIdtf) {
518
        try {
519
            User u = getRequestUser(crc);
×
520
            if (!u.isSuperuser()) {
×
521
                return error(Status.FORBIDDEN, "Not a superuser");
×
522
            }
523
            Dataverse owner = findDataverseOrDie(parentIdtf);
×
524
            
525
            Dataset ds = new Dataset();
×
526

527
            ds.setOwner(owner);
×
528
            ds = JSONLDUtil.updateDatasetMDFromJsonLD(ds, jsonLDBody, metadataBlockSvc, datasetFieldSvc, false, true, licenseSvc);
×
529
          //ToDo - verify PID is one Dataverse can manage (protocol/authority/shoulder match)
530
          if (!PidUtil.getPidProvider(ds.getGlobalId().getProviderId()).canManagePID()) {
×
531
              throw new BadRequestException(
×
532
                      "Cannot recreate a dataset that has a PID that doesn't match the server's settings");
533
          }
534
            if(!dvObjectSvc.isGlobalIdLocallyUnique(ds.getGlobalId())) {
×
535
                throw new BadRequestException("Cannot recreate a dataset whose PID is already in use");
×
536
            }
537
            
538
            //Throw BadRequestException if metadataLanguage isn't compatible with setting
539
            DataverseUtil.checkMetadataLangauge(ds, owner, settingsService.getBaseMetadataLanguageMap(null, true));
×
540

541

542
            if (ds.getVersions().isEmpty()) {
×
543
                return badRequest("Supplied json must contain a single dataset version.");
×
544
            }
545

546
            DatasetVersion version = ds.getVersions().get(0);
×
547
            if (!version.isPublished()) {
×
548
                throw new BadRequestException("Cannot recreate a dataset that hasn't been published.");
×
549
            }
550
            //While the datasetversion whose metadata we're importing has been published, we consider it in draft until the API caller adds files and then completes the migration
551
            version.setVersionState(DatasetVersion.VersionState.DRAFT);
×
552

553
            DataverseRequest request = createDataverseRequest(u);
×
554

555
            Dataset managedDs = execCommand(new ImportDatasetCommand(ds, request));
×
556
            JsonObjectBuilder responseBld = Json.createObjectBuilder()
×
557
                    .add("id", managedDs.getId())
×
558
                    .add("persistentId", managedDs.getGlobalId().toString());
×
559

560
            return created("/datasets/" + managedDs.getId(), responseBld);
×
561

562
        } catch (WrappedResponse ex) {
×
563
            return ex.getResponse();
×
564
        }
565
    }
566
    
567
    private Dataset parseDataset(String datasetJson) throws WrappedResponse {
568
        try {
569
            return jsonParser().parseDataset(JsonUtil.getJsonObject(datasetJson));
×
570
        } catch (JsonParsingException | JsonParseException jpe) {
×
571
            logger.log(Level.SEVERE, "Error parsing dataset json. Json: {0}", datasetJson);
×
572
            throw new WrappedResponse(error(Status.BAD_REQUEST, "Error parsing Json: " + jpe.getMessage()));
×
573
        }
574
    }
575

576
    @GET
577
    @AuthRequired
578
    @Path("{identifier}")
579
    public Response getDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String idtf, @QueryParam("returnOwners") boolean returnOwners) {
580
        return response(req -> ok(
×
581
            json(execCommand(new GetDataverseCommand(req, findDataverseOrDie(idtf))),
×
582
                settingsService.isTrueForKey(SettingsServiceBean.Key.ExcludeEmailFromExport, false),
×
583
                returnOwners
×
584
            )), getRequestUser(crc));
×
585
    }
586

587
    @DELETE
588
    @AuthRequired
589
    @Path("{identifier}")
590
    public Response deleteDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String idtf) {
591
        return response(req -> {
×
592
            execCommand(new DeleteDataverseCommand(req, findDataverseOrDie(idtf)));
×
593
            return ok("Dataverse " + idtf + " deleted");
×
594
        }, getRequestUser(crc));
×
595
    }
596

597
    /**
598
     * Endpoint to change attributes of a Dataverse collection.
599
     *
600
     * @apiNote Example curl command:
601
     *          <code>curl -X PUT -d "test" http://localhost:8080/api/dataverses/$ALIAS/attribute/alias</code>
602
     *          to change the alias of the collection named $ALIAS to "test".
603
     */
604
    @PUT
605
    @AuthRequired
606
    @Path("{identifier}/attribute/{attribute}")
607
    public Response updateAttribute(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier,
608
                                    @PathParam("attribute") String attribute, @QueryParam("value") String value) {
609
        try {
610
            Dataverse collection = findDataverseOrDie(identifier);
×
611
            User user = getRequestUser(crc);
×
612
            DataverseRequest dvRequest = createDataverseRequest(user);
×
613
    
614
            // TODO: The cases below use hard coded strings, because we have no place for definitions of those!
615
            //       They are taken from util.json.JsonParser / util.json.JsonPrinter. This shall be changed.
616
            //       This also should be extended to more attributes, like the type, theme, contacts, some booleans, etc.
617
            switch (attribute) {
×
618
                case "alias":
619
                    collection.setAlias(value);
×
620
                    break;
×
621
                case "name":
622
                    collection.setName(value);
×
623
                    break;
×
624
                case "description":
625
                    collection.setDescription(value);
×
626
                    break;
×
627
                case "affiliation":
628
                    collection.setAffiliation(value);
×
629
                    break;
×
630
                /* commenting out the code from the draft pr #9462:
631
                case "versionPidsConduct":
632
                    CollectionConduct conduct = CollectionConduct.findBy(value);
633
                    if (conduct == null) {
634
                        return badRequest("'" + value + "' is not one of [" +
635
                            String.join(",", CollectionConduct.asList()) + "]");
636
                    }
637
                    collection.setDatasetVersionPidConduct(conduct);
638
                    break;
639
                 */
640
                case "filePIDsEnabled":
641
                    if(!user.isSuperuser()) {
×
642
                        return forbidden("You must be a superuser to change this setting");
×
643
                    }
644
                    if(!settingsService.isTrueForKey(SettingsServiceBean.Key.AllowEnablingFilePIDsPerCollection, false)) {
×
645
                        return forbidden("Changing File PID policy per collection is not enabled on this server");
×
646
                    }
647
                    collection.setFilePIDsEnabled(parseBooleanOrDie(value));
×
648
                    break;
×
649
                default:
650
                    return badRequest("'" + attribute + "' is not a supported attribute");
×
651
            }
652
            
653
            // Off to persistence layer
654
            execCommand(new UpdateDataverseCommand(collection, null, null, dvRequest, null));
×
655
    
656
            // Also return modified collection to user
657
            return ok("Update successful", JsonPrinter.json(collection));
×
658
        
659
        // TODO: This is an anti-pattern, necessary due to this bean being an EJB, causing very noisy and unnecessary
660
        //       logging by the EJB container for bubbling exceptions. (It would be handled by the error handlers.)
661
        } catch (WrappedResponse e) {
×
662
            return e.getResponse();
×
663
        }
664
    }
665

666
    @GET
667
    @AuthRequired
668
    @Path("{identifier}/inputLevels")
669
    public Response getInputLevels(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier) {
670
        try {
NEW
671
            Dataverse dataverse = findDataverseOrDie(identifier);
×
NEW
672
            List<DataverseFieldTypeInputLevel> inputLevels = execCommand(new ListDataverseInputLevelsCommand(createDataverseRequest(getRequestUser(crc)), dataverse));
×
NEW
673
            return ok(jsonDataverseInputLevels(inputLevels));
×
NEW
674
        } catch (WrappedResponse e) {
×
NEW
675
            return e.getResponse();
×
676
        }
677
    }
678

679
    @PUT
680
    @AuthRequired
681
    @Path("{identifier}/inputLevels")
682
    public Response updateInputLevels(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier, String jsonBody) {
683
        try {
684
            Dataverse dataverse = findDataverseOrDie(identifier);
×
NEW
685
            List<DataverseFieldTypeInputLevel> newInputLevels = parseInputLevels(Json.createReader(new StringReader(jsonBody)).readArray(), dataverse);
×
686
            execCommand(new UpdateDataverseInputLevelsCommand(dataverse, createDataverseRequest(getRequestUser(crc)), newInputLevels));
×
687
            return ok(BundleUtil.getStringFromBundle("dataverse.update.success"), JsonPrinter.json(dataverse));
×
688
        } catch (WrappedResponse e) {
×
689
            return e.getResponse();
×
690
        }
691
    }
692

693
    private List<DataverseFieldTypeInputLevel> parseInputLevels(JsonArray inputLevelsArray, Dataverse dataverse) throws WrappedResponse {
694
        List<DataverseFieldTypeInputLevel> newInputLevels = new ArrayList<>();
×
695
        for (JsonValue value : inputLevelsArray) {
×
696
            JsonObject inputLevel = (JsonObject) value;
×
697
            String datasetFieldTypeName = inputLevel.getString("datasetFieldTypeName");
×
698
            DatasetFieldType datasetFieldType = datasetFieldSvc.findByName(datasetFieldTypeName);
×
699

700
            if (datasetFieldType == null) {
×
NEW
701
                String errorMessage = MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.inputlevels.error.invalidfieldtypename"), datasetFieldTypeName);
×
702
                throw new WrappedResponse(badRequest(errorMessage));
×
703
            }
704

705
            boolean required = inputLevel.getBoolean("required");
×
706
            boolean include = inputLevel.getBoolean("include");
×
707

NEW
708
            if (required && !include) {
×
NEW
709
                String errorMessage = MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.inputlevels.error.cannotberequiredifnotincluded"), datasetFieldTypeName);
×
NEW
710
                throw new WrappedResponse(badRequest(errorMessage));
×
711
            }
712

713
            newInputLevels.add(new DataverseFieldTypeInputLevel(datasetFieldType, dataverse, required, include));
×
714
        }
×
715

716
        return newInputLevels;
×
717
    }
718

719
    private List<DatasetFieldType> parseFacets(JsonArray facetsArray) throws WrappedResponse {
NEW
720
        List<DatasetFieldType> facets = new LinkedList<>();
×
NEW
721
        for (JsonString facetId : facetsArray.getValuesAs(JsonString.class)) {
×
NEW
722
            DatasetFieldType dsfType = findDatasetFieldType(facetId.getString());
×
NEW
723
            if (dsfType == null) {
×
NEW
724
                throw new WrappedResponse(badRequest(MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.facets.error.fieldtypenotfound"), facetId)));
×
NEW
725
            } else if (!dsfType.isFacetable()) {
×
NEW
726
                throw new WrappedResponse(badRequest(MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.facets.error.fieldtypenotfacetable"), facetId)));
×
727
            }
NEW
728
            facets.add(dsfType);
×
NEW
729
        }
×
NEW
730
        return facets;
×
731
    }
732

733
    @DELETE
734
    @AuthRequired
735
    @Path("{linkingDataverseId}/deleteLink/{linkedDataverseId}")
736
    public Response deleteDataverseLinkingDataverse(@Context ContainerRequestContext crc, @PathParam("linkingDataverseId") String linkingDataverseId, @PathParam("linkedDataverseId") String linkedDataverseId) {
737
        boolean index = true;
×
738
        return response(req -> {
×
739
            execCommand(new DeleteDataverseLinkingDataverseCommand(req, findDataverseOrDie(linkingDataverseId), findDataverseLinkingDataverseOrDie(linkingDataverseId, linkedDataverseId), index));
×
740
            return ok("Link from Dataverse " + linkingDataverseId + " to linked Dataverse " + linkedDataverseId + " deleted");
×
741
        }, getRequestUser(crc));
×
742
    }
743

744
    @GET
745
    @AuthRequired
746
    @Path("{identifier}/metadatablocks")
747
    public Response listMetadataBlocks(@Context ContainerRequestContext crc,
748
                                       @PathParam("identifier") String dvIdtf,
749
                                       @QueryParam("onlyDisplayedOnCreate") boolean onlyDisplayedOnCreate,
750
                                       @QueryParam("returnDatasetFieldTypes") boolean returnDatasetFieldTypes) {
751
        try {
752
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
753
            final List<MetadataBlock> metadataBlocks = execCommand(
×
754
                    new ListMetadataBlocksCommand(
755
                            createDataverseRequest(getRequestUser(crc)),
×
756
                            dataverse,
757
                            onlyDisplayedOnCreate
758
                    )
759
            );
760
            return ok(json(metadataBlocks, returnDatasetFieldTypes, onlyDisplayedOnCreate, dataverse));
×
761
        } catch (WrappedResponse we) {
×
762
            return we.getResponse();
×
763
        }
764
    }
765

766
    @POST
767
    @AuthRequired
768
    @Path("{identifier}/metadatablocks")
769
    @Produces(MediaType.APPLICATION_JSON)
770
    public Response setMetadataBlocks(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String blockIds) {
771

772
        List<MetadataBlock> blocks = new LinkedList<>();
×
773
        try {
774
            for (JsonValue blockId : Util.asJsonArray(blockIds).getValuesAs(JsonValue.class)) {
×
775
                MetadataBlock blk = (blockId.getValueType() == ValueType.NUMBER)
×
776
                        ? findMetadataBlock(((JsonNumber) blockId).longValue())
×
777
                        : findMetadataBlock(((JsonString) blockId).getString());
×
778
                if (blk == null) {
×
779
                    return error(Response.Status.BAD_REQUEST, "Can't find metadata block '" + blockId + "'");
×
780
                }
781
                blocks.add(blk);
×
782
            }
×
783
        } catch (Exception e) {
×
784
            return error(Response.Status.BAD_REQUEST, e.getMessage());
×
785
        }
×
786

787
        try {
788
            execCommand(new UpdateDataverseMetadataBlocksCommand.SetBlocks(createDataverseRequest(getRequestUser(crc)), findDataverseOrDie(dvIdtf), blocks));
×
789
            return ok("Metadata blocks of dataverse " + dvIdtf + " updated.");
×
790

791
        } catch (WrappedResponse ex) {
×
792
            return ex.getResponse();
×
793
        }
794
    }
795

796
    @GET
797
    @AuthRequired
798
    @Path("{identifier}/metadatablocks/:isRoot")
799
    public Response getMetadataRoot_legacy(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
800
        return getMetadataRoot(crc, dvIdtf);
×
801
    }
802

803
    @GET
804
    @AuthRequired
805
    @Path("{identifier}/metadatablocks/isRoot")
806
    @Produces(MediaType.APPLICATION_JSON)
807
    public Response getMetadataRoot(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
808
        return response(req -> {
×
809
            final Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
810
            if (permissionSvc.request(req)
×
811
                    .on(dataverse)
×
812
                    .has(Permission.EditDataverse)) {
×
813
                return ok(dataverse.isMetadataBlockRoot());
×
814
            } else {
815
                return error(Status.FORBIDDEN, "Not authorized");
×
816
            }
817
        }, getRequestUser(crc));
×
818
    }
819

820
    @POST
821
    @AuthRequired
822
    @Path("{identifier}/metadatablocks/:isRoot")
823
    @Produces(MediaType.APPLICATION_JSON)
824
    @Consumes(MediaType.WILDCARD)
825
    public Response setMetadataRoot_legacy(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String body) {
826
        return setMetadataRoot(crc, dvIdtf, body);
×
827
    }
828

829
    @PUT
830
    @AuthRequired
831
    @Path("{identifier}/metadatablocks/isRoot")
832
    @Produces(MediaType.APPLICATION_JSON)
833
    @Consumes(MediaType.WILDCARD)
834
    public Response setMetadataRoot(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String body) {
835
        return response(req -> {
×
836
            final boolean root = parseBooleanOrDie(body);
×
837
            final Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
838
            execCommand(new UpdateDataverseMetadataBlocksCommand.SetRoot(req, dataverse, root));
×
839
            return ok("Dataverse " + dataverse.getName() + " is now a metadata  " + (root ? "" : "non-") + "root");
×
840
        }, getRequestUser(crc));
×
841
    }
842

843
    @GET
844
    @AuthRequired
845
    @Path("{identifier}/facets/")
846
    /**
847
     * return list of facets for the dataverse with alias `dvIdtf`
848
     */
849
    public Response listFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
850
        try {
851
            User u = getRequestUser(crc);
×
852
            DataverseRequest r = createDataverseRequest(u);
×
853
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
854
            JsonArrayBuilder fs = Json.createArrayBuilder();
×
855
            for (DataverseFacet f : execCommand(new ListFacetsCommand(r, dataverse))) {
×
856
                fs.add(f.getDatasetFieldType().getName());
×
857
            }
×
858
            return ok(fs);
×
859
        } catch (WrappedResponse e) {
×
860
            return e.getResponse();
×
861
        }
862
    }
863

864

865
    @GET
866
    @AuthRequired
867
    @Path("{identifier}/featured")
868
    /*
869
    Allows user to get the collections that are featured by a given collection
870
    probably more for SPA than end user
871
    */
872
    public Response getFeaturedDataverses(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf,  String dvAliases) {
873

874
        try {
875
            User u = getRequestUser(crc);
×
876
            DataverseRequest r = createDataverseRequest(u);
×
877
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
878
            JsonArrayBuilder fs = Json.createArrayBuilder();
×
879
            for (Dataverse f : execCommand(new ListFeaturedCollectionsCommand(r, dataverse))) {
×
880
                fs.add(f.getAlias());
×
881
            }
×
882
            return ok(fs);
×
883
        } catch (WrappedResponse e) {
×
884
            return e.getResponse();
×
885
        }
886
    }
887

888

889
    @POST
890
    @AuthRequired
891
    @Path("{identifier}/featured")
892
    /**
893
     * Allows user to set featured dataverses - must have edit dataverse permission
894
     *
895
     */
896
    public Response setFeaturedDataverses(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf,  String dvAliases) {
897
        List<Dataverse> dvsFromInput = new LinkedList<>();
×
898

899

900
        try {
901

902
            for (JsonString dvAlias : Util.asJsonArray(dvAliases).getValuesAs(JsonString.class)) {
×
903
                Dataverse dvToBeFeatured = dataverseService.findByAlias(dvAlias.getString());
×
904
                if (dvToBeFeatured == null) {
×
905
                    return error(Response.Status.BAD_REQUEST, "Can't find dataverse collection with alias '" + dvAlias + "'");
×
906
                }
907
                dvsFromInput.add(dvToBeFeatured);
×
908
            }
×
909

910
            if (dvsFromInput.isEmpty()) {
×
911
                return error(Response.Status.BAD_REQUEST, "Please provide a valid Json array of dataverse collection aliases to be featured.");
×
912
            }
913

914
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
915
            List<Dataverse> featuredSource = new ArrayList<>();
×
916
            List<Dataverse> featuredTarget = new ArrayList<>();
×
917
            featuredSource.addAll(dataverseService.findAllPublishedByOwnerId(dataverse.getId()));
×
918
            featuredSource.addAll(linkingService.findLinkedDataverses(dataverse.getId()));
×
919
            List<DataverseFeaturedDataverse> featuredList = featuredDataverseService.findByDataverseId(dataverse.getId());
×
920

921
            if (featuredSource.isEmpty()) {
×
922
                return error(Response.Status.BAD_REQUEST, "There are no collections avaialble to be featured in Dataverse collection '" + dataverse.getDisplayName() + "'.");
×
923
            }
924

925
            for (DataverseFeaturedDataverse dfd : featuredList) {
×
926
                Dataverse fd = dfd.getFeaturedDataverse();
×
927
                featuredTarget.add(fd);
×
928
                featuredSource.remove(fd);
×
929
            }
×
930

931
            for (Dataverse test : dvsFromInput) {
×
932
                if (featuredTarget.contains(test)) {
×
933
                    return error(Response.Status.BAD_REQUEST, "Dataverse collection '" + test.getDisplayName() + "' is already featured in Dataverse collection '" + dataverse.getDisplayName() + "'.");
×
934
                }
935

936
                if (featuredSource.contains(test)) {
×
937
                    featuredTarget.add(test);
×
938
                } else {
939
                    return error(Response.Status.BAD_REQUEST, "Dataverse collection '" + test.getDisplayName() + "' may not be featured in Dataverse collection '" + dataverse.getDisplayName() + "'.");
×
940
                }
941

942
            }
×
943
            // by passing null for Facets and DataverseFieldTypeInputLevel, those are not changed
944
            execCommand(new UpdateDataverseCommand(dataverse, null, featuredTarget, createDataverseRequest(getRequestUser(crc)), null));
×
945
            return ok("Featured Dataverses of dataverse " + dvIdtf + " updated.");
×
946

947
        } catch (WrappedResponse ex) {
×
948
            return ex.getResponse();
×
949
        } catch (JsonParsingException jpe){
×
950
            return error(Response.Status.BAD_REQUEST, "Please provide a valid Json array of dataverse collection aliases to be featured.");
×
951
        }
952

953
    }
954

955
    @DELETE
956
    @AuthRequired
957
    @Path("{identifier}/featured")
958
    public Response deleteFeaturedCollections(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) throws WrappedResponse {
959
        try {
960
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
961
            List<Dataverse> featuredTarget = new ArrayList<>();
×
962
            execCommand(new UpdateDataverseCommand(dataverse, null, featuredTarget, createDataverseRequest(getRequestUser(crc)), null));
×
963
            return ok(BundleUtil.getStringFromBundle("dataverses.api.delete.featured.collections.successful"));
×
964
        } catch (WrappedResponse ex) {
×
965
            return ex.getResponse();
×
966
        }
967
    }
968

969
    @POST
970
    @AuthRequired
971
    @Path("{identifier}/facets")
972
    @Produces(MediaType.APPLICATION_JSON)
973
    /**
974
     * (not publicly documented) API endpoint for assigning facets to a
975
     * dataverse. `curl -X POST -H "X-Dataverse-key: $ADMIN_KEY"
976
     * http://localhost:8088/api/dataverses/$dv/facets --upload-file foo.json`;
977
     * where foo.json contains a list of datasetField names, works as expected
978
     * (judging by the UI). This triggers a 500 when '-d @foo.json' is used.
979
     */
980
    public Response setFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String facetIds) {
NEW
981
        JsonArray jsonArray = Util.asJsonArray(facetIds);
×
982
        List<DatasetFieldType> facets;
983
        try {
NEW
984
            facets = parseFacets(jsonArray);
×
NEW
985
        } catch (WrappedResponse e) {
×
NEW
986
            return e.getResponse();
×
UNCOV
987
        }
×
988

989
        try {
990
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
991
            // by passing null for Featured Dataverses and DataverseFieldTypeInputLevel, those are not changed
992
            execCommand(new UpdateDataverseCommand(dataverse, facets, null, createDataverseRequest(getRequestUser(crc)), null));
×
993
            return ok("Facets of dataverse " + dvIdtf + " updated.");
×
994

995
        } catch (WrappedResponse ex) {
×
996
            return ex.getResponse();
×
997
        }
998
    }
999

1000
    @GET
1001
    @AuthRequired
1002
    @Path("{identifier}/metadatablockfacets")
1003
    @Produces(MediaType.APPLICATION_JSON)
1004
    public Response listMetadataBlockFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
1005
        try {
1006
            User u = getRequestUser(crc);
1✔
1007
            DataverseRequest request = createDataverseRequest(u);
1✔
1008
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
1✔
1009
            List<DataverseMetadataBlockFacet> metadataBlockFacets = Optional.ofNullable(execCommand(new ListMetadataBlockFacetsCommand(request, dataverse))).orElse(Collections.emptyList());
1✔
1010
            List<DataverseMetadataBlockFacetDTO.MetadataBlockDTO> metadataBlocksDTOs = metadataBlockFacets.stream()
1✔
1011
                    .map(item -> new DataverseMetadataBlockFacetDTO.MetadataBlockDTO(item.getMetadataBlock().getName(), item.getMetadataBlock().getLocaleDisplayFacet()))
1✔
1012
                    .collect(Collectors.toList());
1✔
1013
            DataverseMetadataBlockFacetDTO response = new DataverseMetadataBlockFacetDTO(dataverse.getId(), dataverse.getAlias(), dataverse.isMetadataBlockFacetRoot(), metadataBlocksDTOs);
1✔
1014
            return Response.ok(response).build();
1✔
1015
        } catch (WrappedResponse e) {
1✔
1016
            return e.getResponse();
1✔
1017
        }
1018
    }
1019

1020
    @POST
1021
    @AuthRequired
1022
    @Path("{identifier}/metadatablockfacets")
1023
    @Consumes(MediaType.APPLICATION_JSON)
1024
    @Produces(MediaType.APPLICATION_JSON)
1025
    public Response setMetadataBlockFacets(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, List<String> metadataBlockNames) {
1026
        try {
1027
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
1✔
1028

1029
            if(!dataverse.isMetadataBlockFacetRoot()) {
1✔
1030
                return badRequest(String.format("Dataverse: %s must have metadata block facet root set to true", dvIdtf));
1✔
1031
            }
1032

1033
            List<DataverseMetadataBlockFacet> metadataBlockFacets = new LinkedList<>();
1✔
1034
            for(String metadataBlockName: metadataBlockNames) {
1✔
1035
                MetadataBlock metadataBlock = findMetadataBlock(metadataBlockName);
1✔
1036
                if (metadataBlock == null) {
1✔
1037
                    return badRequest(String.format("Invalid metadata block name: %s", metadataBlockName));
1✔
1038
                }
1039

1040
                DataverseMetadataBlockFacet metadataBlockFacet = new DataverseMetadataBlockFacet();
1✔
1041
                metadataBlockFacet.setDataverse(dataverse);
1✔
1042
                metadataBlockFacet.setMetadataBlock(metadataBlock);
1✔
1043
                metadataBlockFacets.add(metadataBlockFacet);
1✔
1044
            }
1✔
1045

1046
            execCommand(new UpdateMetadataBlockFacetsCommand(createDataverseRequest(getRequestUser(crc)), dataverse, metadataBlockFacets));
1✔
1047
            return ok(String.format("Metadata block facets updated. DataverseId: %s blocks: %s", dvIdtf, metadataBlockNames));
1✔
1048

1049
        } catch (WrappedResponse ex) {
1✔
1050
            return ex.getResponse();
1✔
1051
        }
1052
    }
1053

1054
    @POST
1055
    @AuthRequired
1056
    @Path("{identifier}/metadatablockfacets/isRoot")
1057
    @Consumes(MediaType.APPLICATION_JSON)
1058
    @Produces(MediaType.APPLICATION_JSON)
1059
    public Response updateMetadataBlockFacetsRoot(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, String body) {
1060
        try {
1061
            final boolean blockFacetsRoot = parseBooleanOrDie(body);
1✔
1062
            Dataverse dataverse = findDataverseOrDie(dvIdtf);
1✔
1063
            if(dataverse.isMetadataBlockFacetRoot() == blockFacetsRoot) {
1✔
1064
                return ok(String.format("No update needed, dataverse already consistent with new value. DataverseId: %s blockFacetsRoot: %s", dvIdtf, blockFacetsRoot));
1✔
1065
            }
1066

1067
            execCommand(new UpdateMetadataBlockFacetRootCommand(createDataverseRequest(getRequestUser(crc)), dataverse, blockFacetsRoot));
1✔
1068
            return ok(String.format("Metadata block facets root updated. DataverseId: %s blockFacetsRoot: %s", dvIdtf, blockFacetsRoot));
1✔
1069

1070
        } catch (WrappedResponse ex) {
1✔
1071
            return ex.getResponse();
1✔
1072
        }
1073
    }
1074

1075
    // FIXME: This listContent method is way too optimistic, always returning "ok" and never "error".
1076
    // TODO: Investigate why there was a change in the timeframe of when pull request #4350 was merged
1077
    // (2438-4295-dois-for-files branch) such that a contributor API token no longer allows this method
1078
    // to be called without a PermissionException being thrown.
1079
    @GET
1080
    @AuthRequired
1081
    @Path("{identifier}/contents")
1082
    public Response listContent(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) throws WrappedResponse {
1083

1084
        DvObject.Visitor<JsonObjectBuilder> ser = new DvObject.Visitor<JsonObjectBuilder>() {
×
1085
            @Override
1086
            public JsonObjectBuilder visit(Dataverse dv) {
1087
                return Json.createObjectBuilder().add("type", "dataverse")
×
1088
                        .add("id", dv.getId())
×
1089
                        .add("title", dv.getName());
×
1090
            }
1091

1092
            @Override
1093
            public JsonObjectBuilder visit(Dataset ds) {
1094
                return json(ds).add("type", "dataset");
×
1095
            }
1096

1097
            @Override
1098
            public JsonObjectBuilder visit(DataFile df) {
1099
                throw new UnsupportedOperationException("Files don't live directly in Dataverses");
×
1100
            }
1101
        };
1102

1103
        return response(req -> ok(
×
1104
                execCommand(new ListDataverseContentCommand(req, findDataverseOrDie(dvIdtf)))
×
1105
                        .stream()
×
1106
                        .map(dvo -> (JsonObjectBuilder) dvo.accept(ser))
×
1107
                        .collect(toJsonArray())
×
1108
        ), getRequestUser(crc));
×
1109
    }
1110

1111
    @GET
1112
    @AuthRequired
1113
    @Path("{identifier}/storagesize")
1114
    public Response getStorageSize(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("includeCached") boolean includeCached) throws WrappedResponse {
1115
                
1116
        return response(req -> ok(MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.datasize"),
×
1117
                execCommand(new GetDataverseStorageSizeCommand(req, findDataverseOrDie(dvIdtf), includeCached)))), getRequestUser(crc));
×
1118
    }
1119
    
1120
    @GET
1121
    @AuthRequired
1122
    @Path("{identifier}/storage/quota")
1123
    public Response getCollectionQuota(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) throws WrappedResponse {
1124
        try {
1125
            Long bytesAllocated = execCommand(new GetCollectionQuotaCommand(createDataverseRequest(getRequestUser(crc)), findDataverseOrDie(dvIdtf)));
×
1126
            if (bytesAllocated != null) {
×
1127
                return ok(MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.storage.quota.allocation"),bytesAllocated));
×
1128
            }
1129
            return ok(BundleUtil.getStringFromBundle("dataverse.storage.quota.notdefined"));
×
1130
        } catch (WrappedResponse ex) {
×
1131
            return ex.getResponse();
×
1132
        }
1133
    }
1134
    
1135
    @POST
1136
    @AuthRequired
1137
    @Path("{identifier}/storage/quota/{bytesAllocated}")
1138
    public Response setCollectionQuota(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @PathParam("bytesAllocated") Long bytesAllocated) throws WrappedResponse {
1139
        try {
1140
            execCommand(new SetCollectionQuotaCommand(createDataverseRequest(getRequestUser(crc)), findDataverseOrDie(dvIdtf), bytesAllocated));
×
1141
            return ok(BundleUtil.getStringFromBundle("dataverse.storage.quota.updated"));
×
1142
        } catch (WrappedResponse ex) {
×
1143
            return ex.getResponse();
×
1144
        }
1145
    }
1146
    
1147
    @DELETE
1148
    @AuthRequired
1149
    @Path("{identifier}/storage/quota")
1150
    public Response deleteCollectionQuota(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) throws WrappedResponse {
1151
        try {
1152
            execCommand(new DeleteCollectionQuotaCommand(createDataverseRequest(getRequestUser(crc)), findDataverseOrDie(dvIdtf)));
×
1153
            return ok(BundleUtil.getStringFromBundle("dataverse.storage.quota.deleted"));
×
1154
        } catch (WrappedResponse ex) {
×
1155
            return ex.getResponse();
×
1156
        }
1157
    }
1158
    
1159
    /**
1160
     *
1161
     * @param crc
1162
     * @param identifier
1163
     * @return
1164
     * @throws edu.harvard.iq.dataverse.api.AbstractApiBean.WrappedResponse 
1165
     * @todo: add an optional parameter that would force the recorded storage use
1166
     * to be recalculated (or should that be a POST version of this API?)
1167
     */
1168
    @GET
1169
    @AuthRequired
1170
    @Path("{identifier}/storage/use")
1171
    public Response getCollectionStorageUse(@Context ContainerRequestContext crc, @PathParam("identifier") String identifier) throws WrappedResponse {
1172
        return response(req -> ok(MessageFormat.format(BundleUtil.getStringFromBundle("dataverse.storage.use"),
×
1173
                execCommand(new GetCollectionStorageUseCommand(req, findDataverseOrDie(identifier))))), getRequestUser(crc));
×
1174
    }
1175

1176
    @GET
1177
    @AuthRequired
1178
    @Path("{identifier}/roles")
1179
    public Response listRoles(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
1180
        return response(req -> ok(
×
1181
                execCommand(new ListRolesCommand(req, findDataverseOrDie(dvIdtf)))
×
1182
                        .stream().map(r -> json(r))
×
1183
                        .collect(toJsonArray())
×
1184
        ), getRequestUser(crc));
×
1185
    }
1186

1187
    @POST
1188
    @AuthRequired
1189
    @Path("{identifier}/roles")
1190
    public Response createRole(@Context ContainerRequestContext crc, RoleDTO roleDto, @PathParam("identifier") String dvIdtf) {
1191
        return response(req -> ok(json(execCommand(new CreateRoleCommand(roleDto.asRole(), req, findDataverseOrDie(dvIdtf))))), getRequestUser(crc));
×
1192
    }
1193

1194
    @GET
1195
    @AuthRequired
1196
    @Path("{identifier}/assignments")
1197
    public Response listAssignments(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
1198
        return response(req -> ok(
×
1199
                execCommand(new ListRoleAssignments(req, findDataverseOrDie(dvIdtf)))
×
1200
                        .stream()
×
1201
                        .map(a -> json(a))
×
1202
                        .collect(toJsonArray())
×
1203
        ), getRequestUser(crc));
×
1204
    }
1205

1206
    /**
1207
     * This code for setting a dataverse logo via API was started when initially
1208
     * investigating https://github.com/IQSS/dataverse/issues/3559 but it isn't
1209
     * finished so it's commented out. See also * "No functionality should be
1210
     * GUI-only. Make all functionality reachable via the API" at
1211
     * https://github.com/IQSS/dataverse/issues/3440
1212
     */
1213
//    File tempDir;
1214
//
1215
//    TODO: Code duplicate in ThemeWidgetFragment. Maybe extract, make static and put some place else?
1216
//          Important: at least use JvmSettings.DOCROOT_DIRECTORY and not the hardcoded location!
1217
//    private void createTempDir(Dataverse editDv) {
1218
//        try {
1219
//            File tempRoot = java.nio.file.Files.createDirectories(Paths.get("../docroot/logos/temp")).toFile();
1220
//            tempDir = java.nio.file.Files.createTempDirectory(tempRoot.toPath(), editDv.getId().toString()).toFile();
1221
//        } catch (IOException e) {
1222
//            throw new RuntimeException("Error creating temp directory", e); // improve error handling
1223
//        }
1224
//    }
1225
//
1226
//    private DataverseTheme initDataverseTheme(Dataverse editDv) {
1227
//        DataverseTheme dvt = new DataverseTheme();
1228
//        dvt.setLinkColor(DEFAULT_LINK_COLOR);
1229
//        dvt.setLogoBackgroundColor(DEFAULT_LOGO_BACKGROUND_COLOR);
1230
//        dvt.setBackgroundColor(DEFAULT_BACKGROUND_COLOR);
1231
//        dvt.setTextColor(DEFAULT_TEXT_COLOR);
1232
//        dvt.setDataverse(editDv);
1233
//        return dvt;
1234
//    }
1235
//
1236
//    @PUT
1237
//    @Path("{identifier}/logo")
1238
//    @Consumes(MediaType.MULTIPART_FORM_DATA)
1239
//    public Response setDataverseLogo(@PathParam("identifier") String dvIdtf,
1240
//            @FormDataParam("file") InputStream fileInputStream,
1241
//            @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
1242
//            @QueryParam("key") String apiKey) {
1243
//        boolean disabled = true;
1244
//        if (disabled) {
1245
//            return error(Status.FORBIDDEN, "Setting the dataverse logo via API needs more work.");
1246
//        }
1247
//        try {
1248
//            final DataverseRequest req = createDataverseRequest(findUserOrDie());
1249
//            final Dataverse editDv = findDataverseOrDie(dvIdtf);
1250
//
1251
//            logger.finer("entering fileUpload");
1252
//            if (tempDir == null) {
1253
//                createTempDir(editDv);
1254
//                logger.finer("created tempDir");
1255
//            }
1256
//            File uploadedFile;
1257
//            try {
1258
//                String fileName = contentDispositionHeader.getFileName();
1259
//
1260
//                uploadedFile = new File(tempDir, fileName);
1261
//                if (!uploadedFile.exists()) {
1262
//                    uploadedFile.createNewFile();
1263
//                }
1264
//                logger.finer("created file");
1265
//                File file = null;
1266
//                file = FileUtil.inputStreamToFile(fileInputStream);
1267
//                if (file.length() > systemConfig.getUploadLogoSizeLimit()) {
1268
//                    return error(Response.Status.BAD_REQUEST, "File is larger than maximum size: " + systemConfig.getUploadLogoSizeLimit() + ".");
1269
//                }
1270
//                java.nio.file.Files.copy(fileInputStream, uploadedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
1271
//                logger.finer("copied inputstream to file");
1272
//                editDv.setDataverseTheme(initDataverseTheme(editDv));
1273
//                editDv.getDataverseTheme().setLogo(fileName);
1274
//
1275
//            } catch (IOException e) {
1276
//                logger.finer("caught IOException");
1277
//                logger.throwing("ThemeWidgetFragment", "handleImageFileUpload", e);
1278
//                throw new RuntimeException("Error uploading logo file", e); // improve error handling
1279
//            }
1280
//            // If needed, set the default values for the logo
1281
//            if (editDv.getDataverseTheme().getLogoFormat() == null) {
1282
//                editDv.getDataverseTheme().setLogoFormat(DataverseTheme.ImageFormat.SQUARE);
1283
//            }
1284
//            logger.finer("end handelImageFileUpload");
1285
//            UpdateDataverseThemeCommand cmd = new UpdateDataverseThemeCommand(editDv, uploadedFile, req);
1286
//            Dataverse saved = execCommand(cmd);
1287
//
1288
//            /**
1289
//             * @todo delete the temp file:
1290
//             * docroot/logos/temp/1148114212463761832421/cc0.png
1291
//             */
1292
//            return ok("logo uploaded: " + saved.getDataverseTheme().getLogo());
1293
//        } catch (WrappedResponse ex) {
1294
//            return error(Status.BAD_REQUEST, "problem uploading logo: " + ex);
1295
//        }
1296
//    }
1297
    @POST
1298
    @AuthRequired
1299
    @Path("{identifier}/assignments")
1300
    public Response createAssignment(@Context ContainerRequestContext crc, RoleAssignmentDTO ra, @PathParam("identifier") String dvIdtf, @QueryParam("key") String apiKey) {
1301

1302
        try {
1303
            final DataverseRequest req = createDataverseRequest(getRequestUser(crc));
×
1304
            final Dataverse dataverse = findDataverseOrDie(dvIdtf);
×
1305

1306
            RoleAssignee assignee = findAssignee(ra.getAssignee());
×
1307
            if (assignee == null) {
×
1308
                return error(Status.BAD_REQUEST, "Assignee not found");
×
1309
            }
1310

1311
            DataverseRole theRole;
1312
            Dataverse dv = dataverse;
×
1313
            theRole = null;
×
1314
            while ((theRole == null) && (dv != null)) {
×
1315
                for (DataverseRole aRole : rolesSvc.availableRoles(dv.getId())) {
×
1316
                    if (aRole.getAlias().equals(ra.getRole())) {
×
1317
                        theRole = aRole;
×
1318
                        break;
×
1319
                    }
1320
                }
×
1321
                dv = dv.getOwner();
×
1322
            }
1323
            if (theRole == null) {
×
1324
                return error(Status.BAD_REQUEST, "Can't find role named '" + ra.getRole() + "' in dataverse " + dataverse);
×
1325
            }
1326
            String privateUrlToken = null;
×
1327

1328
            return ok(json(execCommand(new AssignRoleCommand(assignee, theRole, dataverse, req, privateUrlToken))));
×
1329

1330
        } catch (WrappedResponse ex) {
×
1331
            logger.log(Level.WARNING, "Can''t create assignment: {0}", ex.getMessage());
×
1332
            return ex.getResponse();
×
1333
        }
1334
    }
1335

1336
    @DELETE
1337
    @AuthRequired
1338
    @Path("{identifier}/assignments/{id}")
1339
    public Response deleteAssignment(@Context ContainerRequestContext crc, @PathParam("id") long assignmentId, @PathParam("identifier") String dvIdtf) {
1340
        RoleAssignment ra = em.find(RoleAssignment.class, assignmentId);
×
1341
        if (ra != null) {
×
1342
            try {
1343
                findDataverseOrDie(dvIdtf);
×
1344
                execCommand(new RevokeRoleCommand(ra, createDataverseRequest(getRequestUser(crc))));
×
1345
                return ok("Role " + ra.getRole().getName()
×
1346
                        + " revoked for assignee " + ra.getAssigneeIdentifier()
×
1347
                        + " in " + ra.getDefinitionPoint().accept(DvObject.NamePrinter));
×
1348
            } catch (WrappedResponse ex) {
×
1349
                return ex.getResponse();
×
1350
            }
1351
        } else {
1352
            return error(Status.NOT_FOUND, "Role assignment " + assignmentId + " not found");
×
1353
        }
1354
    }
1355

1356
    @POST
1357
    @AuthRequired
1358
    @Path("{identifier}/actions/:publish")
1359
    public Response publishDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
1360
        try {
1361
            Dataverse dv = findDataverseOrDie(dvIdtf);
×
1362
            return ok(json(execCommand(new PublishDataverseCommand(createDataverseRequest(getRequestAuthenticatedUserOrDie(crc)), dv))));
×
1363

1364
        } catch (WrappedResponse wr) {
×
1365
            return wr.getResponse();
×
1366
        }
1367
    }
1368

1369
    @POST
1370
    @AuthRequired
1371
    @Path("{identifier}/groups/")
1372
    public Response createExplicitGroup(@Context ContainerRequestContext crc, ExplicitGroupDTO dto, @PathParam("identifier") String dvIdtf) {
1373
        return response(req -> {
×
1374
            ExplicitGroupProvider prv = explicitGroupSvc.getProvider();
×
1375
            ExplicitGroup newGroup = dto.apply(prv.makeGroup());
×
1376

1377
            newGroup = execCommand(new CreateExplicitGroupCommand(req, findDataverseOrDie(dvIdtf), newGroup));
×
1378

1379
            String groupUri = String.format("%s/groups/%s", dvIdtf, newGroup.getGroupAliasInOwner());
×
1380
            return created(groupUri, json(newGroup));
×
1381
        }, getRequestUser(crc));
×
1382
    }
1383

1384
    @GET
1385
    @AuthRequired
1386
    @Path("{identifier}/groups/")
1387
    public Response listGroups(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf, @QueryParam("key") String apiKey) {
1388
        return response(req -> ok(
×
1389
                execCommand(new ListExplicitGroupsCommand(req, findDataverseOrDie(dvIdtf)))
×
1390
                        .stream().map(eg -> json(eg))
×
1391
                        .collect(toJsonArray())
×
1392
        ), getRequestUser(crc));
×
1393
    }
1394

1395
    @GET
1396
    @AuthRequired
1397
    @Path("{identifier}/groups/{aliasInOwner}")
1398
    public Response getGroupByOwnerAndAliasInOwner(@Context ContainerRequestContext crc,
1399
                                                   @PathParam("identifier") String dvIdtf,
1400
                                                   @PathParam("aliasInOwner") String grpAliasInOwner) {
1401
        return response(req -> ok(json(findExplicitGroupOrDie(findDataverseOrDie(dvIdtf),
×
1402
                req,
1403
                grpAliasInOwner))), getRequestUser(crc));
×
1404
    }
1405
    
1406
    @GET
1407
    @AuthRequired
1408
    @Path("{identifier}/guestbookResponses/")
1409
    public Response getGuestbookResponsesByDataverse(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf,
1410
            @QueryParam("guestbookId") Long gbId, @Context HttpServletResponse response) {
1411

1412
        Dataverse dv;
1413
        try {
1414
            dv = findDataverseOrDie(dvIdtf);
×
1415
            User u = getRequestUser(crc);
×
1416
            DataverseRequest req = createDataverseRequest(u);
×
1417
            if (permissionSvc.request(req)
×
1418
                    .on(dv)
×
1419
                    .has(Permission.EditDataverse)) {
×
1420
            } else {
1421
                return error(Status.FORBIDDEN, "Not authorized");
×
1422
            }
1423

1424
        } catch (WrappedResponse wr) {
×
1425
            return wr.getResponse();
×
1426
        }
×
1427

1428
        StreamingOutput stream = new StreamingOutput() {
×
1429

1430
            @Override
1431
            public void write(OutputStream os) throws IOException,
1432
                    WebApplicationException {
1433

1434
                Map<Integer, Object> customQandAs = guestbookResponseService.mapCustomQuestionAnswersAsStrings(dv.getId(), gbId);
×
1435
                Map<Integer, String> datasetTitles = guestbookResponseService.mapDatasetTitles(dv.getId());
×
1436

1437
                List<Object[]> guestbookResults = guestbookResponseService.getGuestbookResults(dv.getId(), gbId);
×
1438
                os.write("Guestbook, Dataset, Dataset PID, Date, Type, File Name, File Id, File PID, User Name, Email, Institution, Position, Custom Questions\n".getBytes());
×
1439
                for (Object[] result : guestbookResults) {
×
1440
                    StringBuilder sb = guestbookResponseService.convertGuestbookResponsesToCSV(customQandAs, datasetTitles, result);
×
1441
                    os.write(sb.toString().getBytes());
×
1442
                }
×
1443
            }
×
1444
        };
1445
        return Response.ok(stream).build();
×
1446
    }
1447
    
1448
    @PUT
1449
    @AuthRequired
1450
    @Path("{identifier}/groups/{aliasInOwner}")
1451
    public Response updateGroup(@Context ContainerRequestContext crc, ExplicitGroupDTO groupDto,
1452
            @PathParam("identifier") String dvIdtf,
1453
            @PathParam("aliasInOwner") String grpAliasInOwner) {
1454
        return response(req -> ok(json(execCommand(
×
1455
                new UpdateExplicitGroupCommand(req,
1456
                        groupDto.apply(findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner)))))), getRequestUser(crc));
×
1457
    }
1458
    
1459
    @PUT
1460
    @AuthRequired
1461
    @Path("{identifier}/defaultContributorRole/{roleAlias}")
1462
    public Response updateDefaultContributorRole(
1463
            @Context ContainerRequestContext crc,
1464
            @PathParam("identifier") String dvIdtf,
1465
            @PathParam("roleAlias") String roleAlias) {
1466

1467
        DataverseRole defaultRole;
1468
        
1469
        if (roleAlias.equals(DataverseRole.NONE)) {
×
1470
            defaultRole = null;
×
1471
        } else {
1472
            try {
1473
                Dataverse dv = findDataverseOrDie(dvIdtf);
×
1474
                defaultRole = rolesSvc.findCustomRoleByAliasAndOwner(roleAlias, dv.getId());
×
1475
            } catch (Exception nre) {
×
1476
                List<String> args = Arrays.asList(roleAlias);
×
1477
                String retStringError = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.failure.role.not.found", args);
×
1478
                return error(Status.NOT_FOUND, retStringError);
×
1479
            }
×
1480

1481
            if (!defaultRole.doesDvObjectClassHavePermissionForObject(Dataset.class)) {
×
1482
                List<String> args = Arrays.asList(roleAlias);
×
1483
                String retStringError = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.failure.role.does.not.have.dataset.permissions", args);
×
1484
                return error(Status.BAD_REQUEST, retStringError);
×
1485
            }
1486

1487
        }
1488

1489
        try {
1490
            Dataverse dv = findDataverseOrDie(dvIdtf);
×
1491
            
1492
            String defaultRoleName = defaultRole == null ? BundleUtil.getStringFromBundle("permission.default.contributor.role.none.name") : defaultRole.getName();
×
1493

1494
            return response(req -> {
×
1495
                execCommand(new UpdateDataverseDefaultContributorRoleCommand(defaultRole, req, dv));
×
1496
                List<String> args = Arrays.asList(dv.getDisplayName(), defaultRoleName);
×
1497
                String retString = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.success", args);
×
1498
                return ok(retString);
×
1499
            }, getRequestUser(crc));
×
1500

1501
        } catch (WrappedResponse wr) {
×
1502
            return wr.getResponse();
×
1503
        }
1504

1505
    }
1506

1507
    @DELETE
1508
    @AuthRequired
1509
    @Path("{identifier}/groups/{aliasInOwner}")
1510
    public Response deleteGroup(@Context ContainerRequestContext crc,
1511
                                @PathParam("identifier") String dvIdtf,
1512
                                @PathParam("aliasInOwner") String grpAliasInOwner) {
1513
        return response(req -> {
×
1514
            execCommand(new DeleteExplicitGroupCommand(req,
×
1515
                    findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner)));
×
1516
            return ok("Group " + dvIdtf + "/" + grpAliasInOwner + " deleted");
×
1517
        }, getRequestUser(crc));
×
1518
    }
1519

1520
    @POST
1521
    @AuthRequired
1522
    @Path("{identifier}/groups/{aliasInOwner}/roleAssignees")
1523
    @Consumes("application/json")
1524
    public Response addRoleAssingees(@Context ContainerRequestContext crc,
1525
                                     List<String> roleAssingeeIdentifiers,
1526
                                     @PathParam("identifier") String dvIdtf,
1527
                                     @PathParam("aliasInOwner") String grpAliasInOwner) {
1528
        return response(req -> ok(
×
1529
                json(
×
1530
                    execCommand(
×
1531
                                new AddRoleAssigneesToExplicitGroupCommand(req,
1532
                                        findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner),
×
1533
                                        new TreeSet<>(roleAssingeeIdentifiers))))), getRequestUser(crc));
×
1534
    }
1535

1536
    @PUT
1537
    @AuthRequired
1538
    @Path("{identifier}/groups/{aliasInOwner}/roleAssignees/{roleAssigneeIdentifier: .*}")
1539
    public Response addRoleAssingee(@Context ContainerRequestContext crc,
1540
                                    @PathParam("identifier") String dvIdtf,
1541
                                    @PathParam("aliasInOwner") String grpAliasInOwner,
1542
                                    @PathParam("roleAssigneeIdentifier") String roleAssigneeIdentifier) {
1543
        return addRoleAssingees(crc, Collections.singletonList(roleAssigneeIdentifier), dvIdtf, grpAliasInOwner);
×
1544
    }
1545

1546
    @DELETE
1547
    @AuthRequired
1548
    @Path("{identifier}/groups/{aliasInOwner}/roleAssignees/{roleAssigneeIdentifier: .*}")
1549
    public Response deleteRoleAssingee(@Context ContainerRequestContext crc,
1550
                                       @PathParam("identifier") String dvIdtf,
1551
                                       @PathParam("aliasInOwner") String grpAliasInOwner,
1552
                                       @PathParam("roleAssigneeIdentifier") String roleAssigneeIdentifier) {
1553
        return response(req -> ok(json(execCommand(
×
1554
                new RemoveRoleAssigneesFromExplicitGroupCommand(req,
1555
                        findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner),
×
1556
                        Collections.singleton(roleAssigneeIdentifier))))), getRequestUser(crc));
×
1557
    }
1558

1559
    private ExplicitGroup findExplicitGroupOrDie(DvObject dv, DataverseRequest req, String groupIdtf) throws WrappedResponse {
1560
        ExplicitGroup eg = execCommand(new GetExplicitGroupCommand(req, dv, groupIdtf));
×
1561
        if (eg == null) {
×
1562
            throw new WrappedResponse(notFound("Can't find " + groupIdtf + " in dataverse " + dv.getId()));
×
1563
        }
1564
        return eg;
×
1565
    }
1566

1567
    @GET
1568
    @AuthRequired
1569
    @Path("{identifier}/links")
1570
    public Response listLinks(@Context ContainerRequestContext crc, @PathParam("identifier") String dvIdtf) {
1571
        try {
1572
            User u = getRequestUser(crc);
×
1573
            Dataverse dv = findDataverseOrDie(dvIdtf);
×
1574
            if (!u.isSuperuser()) {
×
1575
                return error(Status.FORBIDDEN, "Not a superuser");
×
1576
            }
1577

1578
            List<Dataverse> dvsThisDvHasLinkedToList = dataverseSvc.findDataversesThisIdHasLinkedTo(dv.getId());
×
1579
            JsonArrayBuilder dvsThisDvHasLinkedToBuilder = Json.createArrayBuilder();
×
1580
            for (Dataverse dataverse : dvsThisDvHasLinkedToList) {
×
1581
                dvsThisDvHasLinkedToBuilder.add(dataverse.getAlias());
×
1582
            }
×
1583

1584
            List<Dataverse> dvsThatLinkToThisDvList = dataverseSvc.findDataversesThatLinkToThisDvId(dv.getId());
×
1585
            JsonArrayBuilder dvsThatLinkToThisDvBuilder = Json.createArrayBuilder();
×
1586
            for (Dataverse dataverse : dvsThatLinkToThisDvList) {
×
1587
                dvsThatLinkToThisDvBuilder.add(dataverse.getAlias());
×
1588
            }
×
1589

1590
            List<Dataset> datasetsThisDvHasLinkedToList = dataverseSvc.findDatasetsThisIdHasLinkedTo(dv.getId());
×
1591
            JsonArrayBuilder datasetsThisDvHasLinkedToBuilder = Json.createArrayBuilder();
×
1592
            for (Dataset dataset : datasetsThisDvHasLinkedToList) {
×
1593
                datasetsThisDvHasLinkedToBuilder.add(dataset.getLatestVersion().getTitle());
×
1594
            }
×
1595

1596
            JsonObjectBuilder response = Json.createObjectBuilder();
×
1597
            response.add("dataverses that the " + dv.getAlias() + " dataverse has linked to", dvsThisDvHasLinkedToBuilder);
×
1598
            response.add("dataverses that link to the " + dv.getAlias(), dvsThatLinkToThisDvBuilder);
×
1599
            response.add("datasets that the " + dv.getAlias() + " has linked to", datasetsThisDvHasLinkedToBuilder);
×
1600
            return ok(response);
×
1601

1602
        } catch (WrappedResponse wr) {
×
1603
            return wr.getResponse();
×
1604
        }
1605
    }
1606

1607
    @POST
1608
    @AuthRequired
1609
    @Path("{id}/move/{targetDataverseAlias}")
1610
    public Response moveDataverse(@Context ContainerRequestContext crc, @PathParam("id") String id, @PathParam("targetDataverseAlias") String targetDataverseAlias, @QueryParam("forceMove") Boolean force) {
1611
        try {
1612
            User u = getRequestUser(crc);
×
1613
            Dataverse dv = findDataverseOrDie(id);
×
1614
            Dataverse target = findDataverseOrDie(targetDataverseAlias);
×
1615
            if (target == null) {
×
1616
                return error(Response.Status.BAD_REQUEST, "Target Dataverse not found.");
×
1617
            }
1618
            execCommand(new MoveDataverseCommand(
×
1619
                    createDataverseRequest(u), dv, target, force
×
1620
            ));
1621
            return ok("Dataverse moved successfully");
×
1622
        } catch (WrappedResponse ex) {
×
1623
            return ex.getResponse();
×
1624
        }
1625
    }
1626

1627
    @PUT
1628
    @AuthRequired
1629
    @Path("{linkedDataverseAlias}/link/{linkingDataverseAlias}")
1630
    public Response linkDataverse(@Context ContainerRequestContext crc, @PathParam("linkedDataverseAlias") String linkedDataverseAlias, @PathParam("linkingDataverseAlias") String linkingDataverseAlias) {
1631
        try {
1632
            User u = getRequestUser(crc);
×
1633
            Dataverse linked = findDataverseOrDie(linkedDataverseAlias);
×
1634
            Dataverse linking = findDataverseOrDie(linkingDataverseAlias);
×
1635
            if (linked == null) {
×
1636
                return error(Response.Status.BAD_REQUEST, "Linked Dataverse not found.");
×
1637
            }
1638
            if (linking == null) {
×
1639
                return error(Response.Status.BAD_REQUEST, "Linking Dataverse not found.");
×
1640
            }
1641
            execCommand(new LinkDataverseCommand(
×
1642
                    createDataverseRequest(u), linking, linked
×
1643
            ));
1644
            return ok("Dataverse " + linked.getAlias() + " linked successfully to " + linking.getAlias());
×
1645
        } catch (WrappedResponse ex) {
×
1646
            return ex.getResponse();
×
1647
        }
1648
    }
1649

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

© 2025 Coveralls, Inc