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

IQSS / dataverse / #24958

10 Mar 2025 05:32PM CUT coverage: 22.614%. Remained the same
#24958

Pull #11325

github

stevenwinship
extend size of deaccessionlink column
Pull Request #11325: extend size of deaccessionlink column

20033 of 88586 relevant lines covered (22.61%)

0.23 hits per line

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

0.0
/src/main/java/edu/harvard/iq/dataverse/api/Admin.java
1
package edu.harvard.iq.dataverse.api;
2

3
import edu.harvard.iq.dataverse.BannerMessage;
4
import edu.harvard.iq.dataverse.BannerMessageServiceBean;
5
import edu.harvard.iq.dataverse.BannerMessageText;
6
import edu.harvard.iq.dataverse.DataFile;
7
import edu.harvard.iq.dataverse.DataFileServiceBean;
8
import edu.harvard.iq.dataverse.Dataset;
9
import edu.harvard.iq.dataverse.DatasetServiceBean;
10
import edu.harvard.iq.dataverse.DatasetVersion;
11
import edu.harvard.iq.dataverse.DatasetVersionServiceBean;
12
import edu.harvard.iq.dataverse.Dataverse;
13
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
14
import edu.harvard.iq.dataverse.DataverseServiceBean;
15
import edu.harvard.iq.dataverse.DataverseSession;
16
import edu.harvard.iq.dataverse.DvObject;
17
import edu.harvard.iq.dataverse.DvObjectServiceBean;
18
import edu.harvard.iq.dataverse.FileMetadata;
19
import edu.harvard.iq.dataverse.api.auth.AuthRequired;
20
import edu.harvard.iq.dataverse.settings.JvmSettings;
21
import edu.harvard.iq.dataverse.util.StringUtil;
22
import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder;
23
import edu.harvard.iq.dataverse.validation.EMailValidator;
24
import edu.harvard.iq.dataverse.EjbDataverseEngine;
25
import edu.harvard.iq.dataverse.Template;
26
import edu.harvard.iq.dataverse.TemplateServiceBean;
27
import edu.harvard.iq.dataverse.UserServiceBean;
28
import edu.harvard.iq.dataverse.actionlogging.ActionLogRecord;
29
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
30
import edu.harvard.iq.dataverse.authorization.AuthenticatedUserDisplayInfo;
31
import edu.harvard.iq.dataverse.authorization.AuthenticationProvider;
32
import edu.harvard.iq.dataverse.authorization.UserIdentifier;
33
import edu.harvard.iq.dataverse.authorization.exceptions.AuthenticationProviderFactoryNotFoundException;
34
import edu.harvard.iq.dataverse.authorization.exceptions.AuthorizationSetupException;
35
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
36
import edu.harvard.iq.dataverse.authorization.providers.AuthenticationProviderRow;
37
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUser;
38
import edu.harvard.iq.dataverse.authorization.providers.builtin.BuiltinUserServiceBean;
39
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibAuthenticationProvider;
40
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibServiceBean;
41
import edu.harvard.iq.dataverse.authorization.providers.shib.ShibUtil;
42
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
43
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
44
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailData;
45
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailException;
46
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailInitResponse;
47
import edu.harvard.iq.dataverse.dataaccess.DataAccess;
48
import edu.harvard.iq.dataverse.dataaccess.DataAccessOption;
49
import edu.harvard.iq.dataverse.dataaccess.StorageIO;
50
import edu.harvard.iq.dataverse.engine.command.impl.AbstractSubmitToArchiveCommand;
51
import edu.harvard.iq.dataverse.engine.command.impl.PublishDataverseCommand;
52
import edu.harvard.iq.dataverse.settings.Setting;
53
import jakarta.json.Json;
54
import jakarta.json.JsonArrayBuilder;
55
import jakarta.json.JsonObjectBuilder;
56
import jakarta.ws.rs.Consumes;
57
import jakarta.ws.rs.DELETE;
58
import jakarta.ws.rs.GET;
59
import jakarta.ws.rs.POST;
60
import jakarta.ws.rs.PUT;
61
import jakarta.ws.rs.Path;
62
import jakarta.ws.rs.PathParam;
63
import jakarta.ws.rs.container.ContainerRequestContext;
64
import jakarta.ws.rs.core.Context;
65
import jakarta.ws.rs.core.Response;
66
import static edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder.jsonObjectBuilder;
67

68
import java.io.InputStream;
69
import java.io.StringReader;
70
import java.nio.charset.StandardCharsets;
71
import java.util.Collections;
72
import java.util.Map;
73
import java.util.Map.Entry;
74
import java.util.function.Predicate;
75
import java.util.logging.Level;
76
import java.util.logging.Logger;
77
import jakarta.ejb.EJB;
78
import jakarta.ejb.Stateless;
79
import jakarta.json.JsonObject;
80
import jakarta.json.JsonReader;
81
import jakarta.validation.ConstraintViolation;
82
import jakarta.validation.ConstraintViolationException;
83
import jakarta.ws.rs.Produces;
84
import jakarta.ws.rs.core.Response.Status;
85

86
import org.apache.commons.io.IOUtils;
87

88
import java.util.List;
89
import edu.harvard.iq.dataverse.authorization.AuthTestDataServiceBean;
90
import edu.harvard.iq.dataverse.authorization.AuthenticationProvidersRegistrationServiceBean;
91
import edu.harvard.iq.dataverse.authorization.DataverseRole;
92
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
93
import edu.harvard.iq.dataverse.authorization.UserRecordIdentifier;
94
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroupServiceBean;
95
import edu.harvard.iq.dataverse.authorization.users.User;
96
import edu.harvard.iq.dataverse.dataaccess.ImageThumbConverter;
97
import edu.harvard.iq.dataverse.dataset.DatasetThumbnail;
98
import edu.harvard.iq.dataverse.dataset.DatasetUtil;
99
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
100
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
101
import edu.harvard.iq.dataverse.engine.command.impl.DeactivateUserCommand;
102
import edu.harvard.iq.dataverse.engine.command.impl.DeleteRoleCommand;
103
import edu.harvard.iq.dataverse.engine.command.impl.DeleteTemplateCommand;
104
import edu.harvard.iq.dataverse.engine.command.impl.RegisterDvObjectCommand;
105
import edu.harvard.iq.dataverse.ingest.IngestServiceBean;
106
import edu.harvard.iq.dataverse.pidproviders.handle.HandlePidProvider;
107
import edu.harvard.iq.dataverse.settings.FeatureFlags;
108
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
109
import edu.harvard.iq.dataverse.userdata.UserListMaker;
110
import edu.harvard.iq.dataverse.userdata.UserListResult;
111
import edu.harvard.iq.dataverse.util.ArchiverUtil;
112
import edu.harvard.iq.dataverse.util.BundleUtil;
113
import edu.harvard.iq.dataverse.util.FileUtil;
114
import edu.harvard.iq.dataverse.util.SystemConfig;
115
import edu.harvard.iq.dataverse.util.URLTokenUtil;
116
import edu.harvard.iq.dataverse.util.UrlSignerUtil;
117

118
import java.io.FileInputStream;
119
import java.io.IOException;
120
import java.io.OutputStream;
121

122
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
123
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.rolesToJson;
124
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.toJsonArray;
125
import java.util.ArrayList;
126
import java.util.Arrays;
127
import java.util.Date;
128
import jakarta.inject.Inject;
129
import jakarta.json.JsonArray;
130
import jakarta.persistence.Query;
131
import jakarta.ws.rs.QueryParam;
132
import jakarta.ws.rs.WebApplicationException;
133
import jakarta.ws.rs.core.StreamingOutput;
134
import java.nio.file.Paths;
135
import java.util.TreeMap;
136

137
/**
138
 * Where the secure, setup API calls live.
139
 * 
140
 * @author michael
141
 */
142
@Stateless
143
@Path("admin")
144
public class Admin extends AbstractApiBean {
×
145

146
    private static final Logger logger = Logger.getLogger(Admin.class.getName());
×
147

148
    @EJB
149
    AuthenticationProvidersRegistrationServiceBean authProvidersRegistrationSvc;
150
    @EJB
151
    BuiltinUserServiceBean builtinUserService;
152
    @EJB
153
    ShibServiceBean shibService;
154
    @EJB
155
    AuthTestDataServiceBean authTestDataService;
156
    @EJB
157
    UserServiceBean userService;
158
    @EJB
159
    IngestServiceBean ingestService;
160
    @EJB
161
    DataFileServiceBean fileService;
162
    @EJB
163
    DatasetServiceBean datasetService;
164
    @EJB
165
    DataverseServiceBean dataverseService;
166
    @EJB
167
    DvObjectServiceBean dvObjectService;
168
    @EJB
169
    DatasetVersionServiceBean datasetversionService;
170
    @Inject
171
    DataverseRequestServiceBean dvRequestService;
172
    @EJB
173
    EjbDataverseEngine commandEngine;
174
    @EJB
175
    GroupServiceBean groupService;
176
    @EJB
177
    SettingsServiceBean settingsService;
178
    @EJB
179
    DatasetVersionServiceBean datasetVersionService;
180
    @EJB
181
    ExplicitGroupServiceBean explicitGroupService;
182
    @EJB
183
    BannerMessageServiceBean bannerMessageService;
184
    @EJB
185
    TemplateServiceBean templateService;
186

187
    // Make the session available
188
    @Inject
189
    DataverseSession session;
190

191
    public static final String listUsersPartialAPIPath = "list-users";
192
    public static final String listUsersFullAPIPath = "/api/admin/" + listUsersPartialAPIPath;
193

194
    @Path("settings")
195
    @GET
196
    public Response listAllSettings() {
197
        JsonObjectBuilder bld = jsonObjectBuilder();
×
198
        settingsSvc.listAll().forEach(s -> bld.add(s.getName(), s.getContent()));
×
199
        return ok(bld);
×
200
    }
201

202
    @Path("settings/{name}")
203
    @PUT
204
    public Response putSetting(@PathParam("name") String name, String content) {
205
        Setting s = settingsSvc.set(name, content);
×
206
        return ok(jsonObjectBuilder().add(s.getName(), s.getContent()));
×
207
    }
208

209
    @Path("settings/{name}/lang/{lang}")
210
    @PUT
211
    public Response putSettingLang(@PathParam("name") String name, @PathParam("lang") String lang, String content) {
212
        Setting s = settingsSvc.set(name, lang, content);
×
213
        return ok("Setting " + name + " - " + lang + " - added.");
×
214
    }
215

216
    @Path("settings/{name}")
217
    @GET
218
    public Response getSetting(@PathParam("name") String name) {
219
        String s = settingsSvc.get(name);
×
220

221
        return (s != null) ? ok(s) : notFound("Setting " + name + " not found");
×
222
    }
223

224
    @Path("settings/{name}")
225
    @DELETE
226
    public Response deleteSetting(@PathParam("name") String name) {
227
        settingsSvc.delete(name);
×
228

229
        return ok("Setting " + name + " deleted.");
×
230
    }
231

232
    @Path("settings/{name}/lang/{lang}")
233
    @DELETE
234
    public Response deleteSettingLang(@PathParam("name") String name, @PathParam("lang") String lang) {
235
        settingsSvc.delete(name, lang);
×
236
        return ok("Setting " + name + " - " + lang + " deleted.");
×
237
    }
238
        
239
    @Path("template/{id}")
240
    @DELETE
241
    public Response deleteTemplate(@PathParam("id") long id) {
242
        
243
        AuthenticatedUser superuser = authSvc.getAdminUser();
×
244
        if (superuser == null) {
×
245
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Cannot find superuser to execute DeleteTemplateCommand.");
×
246
        }
247

248
        Template doomed = templateService.find(id);
×
249
        if (doomed == null) {
×
250
            return error(Response.Status.NOT_FOUND, "Template with id " + id + " -  not found.");
×
251
        }
252

253
        Dataverse dv = doomed.getDataverse();
×
254
        List <Dataverse> dataverseWDefaultTemplate = templateService.findDataversesByDefaultTemplateId(doomed.getId());
×
255

256
        try {
257
            commandEngine.submit(new DeleteTemplateCommand(createDataverseRequest(superuser), dv, doomed, dataverseWDefaultTemplate));
×
258
        } catch (CommandException ex) {
×
259
            Logger.getLogger(Admin.class.getName()).log(Level.SEVERE, null, ex);
×
260
            return error(Response.Status.BAD_REQUEST, ex.getLocalizedMessage());
×
261
        }
×
262

263
        return ok("Template " + doomed.getName() + " deleted.");
×
264
    }
265
    
266
    
267
    @Path("templates")
268
    @GET
269
    public Response findAllTemplates() {
270
        return findTemplates("");
×
271
    }
272
    
273
    @Path("templates/{alias}")
274
    @GET
275
    public Response findTemplates(@PathParam("alias") String alias) {
276
        List<Template> templates;
277

278
            if (alias.isEmpty()) {
×
279
                templates = templateService.findAll();
×
280
            } else {
281
                try{
282
                    Dataverse owner = findDataverseOrDie(alias);
×
283
                    templates = templateService.findByOwnerId(owner.getId());
×
284
                } catch (WrappedResponse r){
×
285
                    return r.getResponse();
×
286
                }
×
287
            }
288

289
            JsonArrayBuilder container = Json.createArrayBuilder();
×
290
            for (Template t : templates) {
×
291
                JsonObjectBuilder bld = Json.createObjectBuilder();
×
292
                bld.add("templateId", t.getId());
×
293
                bld.add("templateName", t.getName());
×
294
                Dataverse loopowner = t.getDataverse();
×
295
                if (loopowner != null) {
×
296
                    bld.add("owner", loopowner.getDisplayName());
×
297
                } else {
298
                    bld.add("owner", "This an orphan template, it may be safely removed");
×
299
                }
300
                container.add(bld);
×
301
            }
×
302

303
            return ok(container);
×
304

305
        
306
    }
307

308
    @Path("authenticationProviderFactories")
309
    @GET
310
    public Response listAuthProviderFactories() {
311
        return ok(authSvc.listProviderFactories().stream()
×
312
                .map(f -> jsonObjectBuilder().add("alias", f.getAlias()).add("info", f.getInfo()))
×
313
                .collect(toJsonArray()));
×
314
    }
315

316
    @Path("authenticationProviders")
317
    @GET
318
    public Response listAuthProviders() {
319
        return ok(em.createNamedQuery("AuthenticationProviderRow.findAll", AuthenticationProviderRow.class)
×
320
                .getResultList().stream().map(r -> json(r)).collect(toJsonArray()));
×
321
    }
322

323
    @Path("authenticationProviders")
324
    @POST
325
    public Response addProvider(AuthenticationProviderRow row) {
326
        try {
327
            AuthenticationProviderRow managed = em.find(AuthenticationProviderRow.class, row.getId());
×
328
            if (managed != null) {
×
329
                managed = em.merge(row);
×
330
            } else {
331
                em.persist(row);
×
332
                managed = row;
×
333
            }
334
            if (managed.isEnabled()) {
×
335
                AuthenticationProvider provider = authProvidersRegistrationSvc.loadProvider(managed);
×
336
                authProvidersRegistrationSvc.deregisterProvider(provider.getId());
×
337
                authProvidersRegistrationSvc.registerProvider(provider);
×
338
            }
339
            return created("/api/admin/authenticationProviders/" + managed.getId(), json(managed));
×
340
        } catch (AuthorizationSetupException e) {
×
341
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
342
        }
343
    }
344

345
    @Path("authenticationProviders/{id}")
346
    @GET
347
    public Response showProvider(@PathParam("id") String id) {
348
        AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
×
349
        return (row != null) ? ok(json(row))
×
350
                : error(Status.NOT_FOUND, "Can't find authetication provider with id '" + id + "'");
×
351
    }
352

353
    @POST
354
    @Path("authenticationProviders/{id}/:enabled")
355
    public Response enableAuthenticationProvider_deprecated(@PathParam("id") String id, String body) {
356
        return enableAuthenticationProvider(id, body);
×
357
    }
358

359
    @PUT
360
    @Path("authenticationProviders/{id}/enabled")
361
    @Produces("application/json")
362
    public Response enableAuthenticationProvider(@PathParam("id") String id, String body) {
363
        body = body.trim();
×
364
        if (!Util.isBoolean(body)) {
×
365
            return error(Response.Status.BAD_REQUEST, "Illegal value '" + body + "'. Use 'true' or 'false'");
×
366
        }
367
        boolean enable = Util.isTrue(body);
×
368

369
        AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
×
370
        if (row == null) {
×
371
            return notFound("Can't find authentication provider with id '" + id + "'");
×
372
        }
373

374
        row.setEnabled(enable);
×
375
        em.merge(row);
×
376

377
        if (enable) {
×
378
            // enable a provider
379
            if (authSvc.getAuthenticationProvider(id) != null) {
×
380
                return ok(String.format("Authentication provider '%s' already enabled", id));
×
381
            }
382
            try {
383
                authProvidersRegistrationSvc.registerProvider(authProvidersRegistrationSvc.loadProvider(row));
×
384
                return ok(String.format("Authentication Provider %s enabled", row.getId()));
×
385

386
            } catch (AuthenticationProviderFactoryNotFoundException ex) {
×
387
                return notFound(String.format("Can't instantiate provider, as there's no factory with alias %s",
×
388
                        row.getFactoryAlias()));
×
389
            } catch (AuthorizationSetupException ex) {
×
390
                logger.log(Level.WARNING, "Error instantiating authentication provider: " + ex.getMessage(), ex);
×
391
                return error(Status.INTERNAL_SERVER_ERROR,
×
392
                        String.format("Can't instantiate provider: %s", ex.getMessage()));
×
393
            }
394

395
        } else {
396
            // disable a provider
397
            authProvidersRegistrationSvc.deregisterProvider(id);
×
398
            return ok("Authentication Provider '" + id + "' disabled. "
×
399
                    + (authSvc.getAuthenticationProviderIds().isEmpty()
×
400
                            ? "WARNING: no enabled authentication providers left."
×
401
                            : ""));
×
402
        }
403
    }
404

405
    @GET
406
    @Path("authenticationProviders/{id}/enabled")
407
    public Response checkAuthenticationProviderEnabled(@PathParam("id") String id) {
408
        List<AuthenticationProviderRow> prvs = em
×
409
                .createNamedQuery("AuthenticationProviderRow.findById", AuthenticationProviderRow.class)
×
410
                .setParameter("id", id).getResultList();
×
411
        if (prvs.isEmpty()) {
×
412
            return notFound("Can't find a provider with id '" + id + "'.");
×
413
        } else {
414
            return ok(Boolean.toString(prvs.get(0).isEnabled()));
×
415
        }
416
    }
417

418
    @DELETE
419
    @Path("authenticationProviders/{id}/")
420
    public Response deleteAuthenticationProvider(@PathParam("id") String id) {
421
        authProvidersRegistrationSvc.deregisterProvider(id);
×
422
        AuthenticationProviderRow row = em.find(AuthenticationProviderRow.class, id);
×
423
        if (row != null) {
×
424
            em.remove(row);
×
425
        }
426

427
        return ok("AuthenticationProvider " + id + " deleted. "
×
428
                + (authSvc.getAuthenticationProviderIds().isEmpty()
×
429
                        ? "WARNING: no enabled authentication providers left."
×
430
                        : ""));
×
431
    }
432

433
    @GET
434
    @Path("authenticatedUsers/{identifier}/")
435
    public Response getAuthenticatedUserByIdentifier(@PathParam("identifier") String identifier) {
436
        AuthenticatedUser authenticatedUser = authSvc.getAuthenticatedUser(identifier);
×
437
        if (authenticatedUser != null) {
×
438
            return ok(json(authenticatedUser));
×
439
        }
440
        return error(Response.Status.BAD_REQUEST, "User " + identifier + " not found.");
×
441
    }
442

443
    @DELETE
444
    @Path("authenticatedUsers/{identifier}/")
445
    public Response deleteAuthenticatedUser(@PathParam("identifier") String identifier) {
446
        AuthenticatedUser user = authSvc.getAuthenticatedUser(identifier);
×
447
        if (user != null) {
×
448
            return deleteAuthenticatedUser(user);
×
449
        }
450
        return error(Response.Status.BAD_REQUEST, "User " + identifier + " not found.");
×
451
    }
452
    
453
    @DELETE
454
    @Path("authenticatedUsers/id/{id}/")
455
    public Response deleteAuthenticatedUserById(@PathParam("id") Long id) {
456
        AuthenticatedUser user = authSvc.findByID(id);
×
457
        if (user != null) {
×
458
            return deleteAuthenticatedUser(user);
×
459
        }
460
        return error(Response.Status.BAD_REQUEST, "User " + id + " not found.");
×
461
    }
462

463
    private Response deleteAuthenticatedUser(AuthenticatedUser au) {
464
        
465
        //getDeleteUserErrorMessages does all of the tests to see
466
        //if the user is 'deletable' if it returns an empty string the user 
467
        //can be safely deleted.
468
        
469
        String errorMessages = authSvc.getDeleteUserErrorMessages(au);
×
470
        
471
        if (!errorMessages.isEmpty()) {
×
472
            return badRequest(errorMessages);
×
473
        }
474
        
475
        //if the user is deletable we will delete access requests and group membership
476
        // many-to-many relationships that couldn't be cascade deleted
477
        authSvc.removeAuthentictedUserItems(au);
×
478
        
479
        authSvc.deleteAuthenticatedUser(au.getId());
×
480
        return ok("AuthenticatedUser " + au.getIdentifier() + " deleted.");
×
481
    }
482

483
    @POST
484
    @Path("authenticatedUsers/{identifier}/deactivate")
485
    public Response deactivateAuthenticatedUser(@PathParam("identifier") String identifier) {
486
        AuthenticatedUser user = authSvc.getAuthenticatedUser(identifier);
×
487
        if (user != null) {
×
488
            return deactivateAuthenticatedUser(user);
×
489
        }
490
        return error(Response.Status.BAD_REQUEST, "User " + identifier + " not found.");
×
491
    }
492

493
    @POST
494
    @Path("authenticatedUsers/id/{id}/deactivate")
495
    public Response deactivateAuthenticatedUserById(@PathParam("id") Long id) {
496
        AuthenticatedUser user = authSvc.findByID(id);
×
497
        if (user != null) {
×
498
            return deactivateAuthenticatedUser(user);
×
499
        }
500
        return error(Response.Status.BAD_REQUEST, "User " + id + " not found.");
×
501
    }
502

503
    private Response deactivateAuthenticatedUser(AuthenticatedUser userToDisable) {
504
        AuthenticatedUser superuser = authSvc.getAdminUser();
×
505
        if (superuser == null) {
×
506
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Cannot find superuser to execute DeactivateUserCommand.");
×
507
        }
508
        try {
509
            execCommand(new DeactivateUserCommand(createDataverseRequest(superuser), userToDisable));
×
510
            return ok("User " + userToDisable.getIdentifier() + " deactivated.");
×
511
        } catch (WrappedResponse ex) {
×
512
            return ex.getResponse();
×
513
        }
514
    }
515

516
    @POST
517
    @Path("publishDataverseAsCreator/{id}")
518
    public Response publishDataverseAsCreator(@PathParam("id") long id) {
519
        try {
520
            Dataverse dataverse = dataverseSvc.find(id);
×
521
            if (dataverse != null) {
×
522
                AuthenticatedUser authenticatedUser = dataverse.getCreator();
×
523
                return ok(json(execCommand(
×
524
                        new PublishDataverseCommand(createDataverseRequest(authenticatedUser), dataverse))));
×
525
            } else {
526
                return error(Status.BAD_REQUEST, "Could not find dataverse with id " + id);
×
527
            }
528
        } catch (WrappedResponse wr) {
×
529
            return wr.getResponse();
×
530
        }
531
    }
532

533
    @Deprecated
534
    @GET
535
    @AuthRequired
536
    @Path("authenticatedUsers")
537
    public Response listAuthenticatedUsers(@Context ContainerRequestContext crc) {
538
        try {
539
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
540
            if (!user.isSuperuser()) {
×
541
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
542
            }
543
        } catch (WrappedResponse ex) {
×
544
            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
545
        }
×
546
        JsonArrayBuilder userArray = Json.createArrayBuilder();
×
547
        authSvc.findAllAuthenticatedUsers().stream().forEach((user) -> {
×
548
            userArray.add(json(user));
×
549
        });
×
550
        return ok(userArray);
×
551
    }
552

553
    @GET
554
    @AuthRequired
555
    @Path(listUsersPartialAPIPath)
556
    @Produces({ "application/json" })
557
    public Response filterAuthenticatedUsers(
558
            @Context ContainerRequestContext crc,
559
            @QueryParam("searchTerm") String searchTerm,
560
            @QueryParam("selectedPage") Integer selectedPage,
561
            @QueryParam("itemsPerPage") Integer itemsPerPage,
562
            @QueryParam("sortKey") String sortKey
563
    ) {
564

565
        User authUser = getRequestUser(crc);
×
566

567
        if (!authUser.isSuperuser()) {
×
568
            return error(Response.Status.FORBIDDEN,
×
569
                    BundleUtil.getStringFromBundle("dashboard.list_users.api.auth.not_superuser"));
×
570
        }
571

572
        UserListMaker userListMaker = new UserListMaker(userService);
×
573

574
        // String sortKey = null;
575
        UserListResult userListResult = userListMaker.runUserSearch(searchTerm, itemsPerPage, selectedPage, sortKey);
×
576

577
        return ok(userListResult.toJSON());
×
578
    }
579

580
    /**
581
     * @todo Make this support creation of BuiltInUsers.
582
     *
583
     * @todo Add way more error checking. Only the happy path is tested by AdminIT.
584
     */
585
    @POST
586
    @Path("authenticatedUsers")
587
    public Response createAuthenicatedUser(JsonObject jsonObject) {
588
        logger.fine("JSON in: " + jsonObject);
×
589
        String persistentUserId = jsonObject.getString("persistentUserId");
×
590
        String identifier = jsonObject.getString("identifier");
×
591
        String proposedAuthenticatedUserIdentifier = identifier.replaceFirst("@", "");
×
592
        String firstName = jsonObject.getString("firstName");
×
593
        String lastName = jsonObject.getString("lastName");
×
594
        String emailAddress = jsonObject.getString("email");
×
595
        String position = null;
×
596
        String affiliation = null;
×
597
        UserRecordIdentifier userRecordId = new UserRecordIdentifier(jsonObject.getString("authenticationProviderId"),
×
598
                persistentUserId);
599
        AuthenticatedUserDisplayInfo userDisplayInfo = new AuthenticatedUserDisplayInfo(firstName, lastName,
×
600
                emailAddress, affiliation, position);
601
        boolean generateUniqueIdentifier = true;
×
602
        AuthenticatedUser authenticatedUser = authSvc.createAuthenticatedUser(userRecordId,
×
603
                proposedAuthenticatedUserIdentifier, userDisplayInfo, true);
604
        return ok(json(authenticatedUser));
×
605
    }
606

607
        //TODO: Delete this endpoint after 4.9.3. Was updated with change in docs. --MAD
608
    /**
609
     * curl -X PUT -d "shib@mailinator.com"
610
     * http://localhost:8080/api/admin/authenticatedUsers/id/11/convertShibToBuiltIn
611
     *
612
     * @deprecated We have documented this API endpoint so we'll keep in around for
613
     *             a while but we should encourage everyone to switch to the
614
     *             "convertRemoteToBuiltIn" endpoint and then remove this
615
     *             Shib-specfic one.
616
     */
617
    @PUT
618
    @AuthRequired
619
    @Path("authenticatedUsers/id/{id}/convertShibToBuiltIn")
620
    @Deprecated
621
    public Response convertShibUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
622
        try {
623
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
624
            if (!user.isSuperuser()) {
×
625
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
626
            }
627
        } catch (WrappedResponse ex) {
×
628
            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
629
        }
×
630
        try {
631
            BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
×
632
            if (builtinUser == null) {
×
633
                return error(Response.Status.BAD_REQUEST, "User id " + id
×
634
                        + " could not be converted from Shibboleth to BuiltIn. An Exception was not thrown.");
635
            }
636
                        AuthenticatedUser authUser = authSvc.getAuthenticatedUser(builtinUser.getUserName());
×
637
            JsonObjectBuilder output = Json.createObjectBuilder();
×
638
            output.add("email", authUser.getEmail());
×
639
            output.add("username", builtinUser.getUserName());
×
640
            return ok(output);
×
641
        } catch (Throwable ex) {
×
642
            StringBuilder sb = new StringBuilder();
×
643
            sb.append(ex + " ");
×
644
            while (ex.getCause() != null) {
×
645
                ex = ex.getCause();
×
646
                sb.append(ex + " ");
×
647
            }
648
            String msg = "User id " + id
×
649
                    + " could not be converted from Shibboleth to BuiltIn. Details from Exception: " + sb;
650
            logger.info(msg);
×
651
            return error(Response.Status.BAD_REQUEST, msg);
×
652
        }
653
    }
654

655
    @PUT
656
    @AuthRequired
657
    @Path("authenticatedUsers/id/{id}/convertRemoteToBuiltIn")
658
    public Response convertOAuthUserToBuiltin(@Context ContainerRequestContext crc, @PathParam("id") Long id, String newEmailAddress) {
659
        try {
660
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
661
            if (!user.isSuperuser()) {
×
662
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
663
            }
664
        } catch (WrappedResponse ex) {
×
665
            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
666
        }
×
667
        try {
668
            BuiltinUser builtinUser = authSvc.convertRemoteToBuiltIn(id, newEmailAddress);
×
669
                        //AuthenticatedUser authUser = authService.getAuthenticatedUser(aUser.getUserName());
670
            if (builtinUser == null) {
×
671
                return error(Response.Status.BAD_REQUEST, "User id " + id
×
672
                        + " could not be converted from remote to BuiltIn. An Exception was not thrown.");
673
            }
674
                        AuthenticatedUser authUser = authSvc.getAuthenticatedUser(builtinUser.getUserName());
×
675
            JsonObjectBuilder output = Json.createObjectBuilder();
×
676
            output.add("email", authUser.getEmail());
×
677
            output.add("username", builtinUser.getUserName());
×
678
            return ok(output);
×
679
        } catch (Throwable ex) {
×
680
            StringBuilder sb = new StringBuilder();
×
681
            sb.append(ex + " ");
×
682
            while (ex.getCause() != null) {
×
683
                ex = ex.getCause();
×
684
                sb.append(ex + " ");
×
685
            }
686
            String msg = "User id " + id + " could not be converted from remote to BuiltIn. Details from Exception: "
×
687
                    + sb;
688
            logger.info(msg);
×
689
            return error(Response.Status.BAD_REQUEST, msg);
×
690
        }
691
    }
692

693
    /**
694
     * This is used in testing via AdminIT.java but we don't expect sysadmins to use
695
     * this.
696
     */
697
    @PUT
698
    @AuthRequired
699
    @Path("authenticatedUsers/convert/builtin2shib")
700
    public Response builtin2shib(@Context ContainerRequestContext crc, String content) {
701
        logger.info("entering builtin2shib...");
×
702
        try {
703
            AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
×
704
            if (!userToRunThisMethod.isSuperuser()) {
×
705
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
706
            }
707
        } catch (WrappedResponse ex) {
×
708
            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
709
        }
×
710
        boolean disabled = false;
×
711
        if (disabled) {
×
712
            return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
×
713
        }
714
        AuthenticatedUser builtInUserToConvert = null;
×
715
        String emailToFind;
716
        String password;
717
        String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
×
718
                                    // tell us their
719
        String newEmailAddressToUse;
720
        try {
721
            String[] args = content.split(":");
×
722
            emailToFind = args[0];
×
723
            password = args[1];
×
724
            newEmailAddressToUse = args[2];
×
725
            // authuserId = args[666];
726
        } catch (ArrayIndexOutOfBoundsException ex) {
×
727
            return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
×
728
        }
×
729
        AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
×
730
        String existing = "NOT FOUND";
×
731
        if (existingAuthUserFoundByEmail != null) {
×
732
            builtInUserToConvert = existingAuthUserFoundByEmail;
×
733
            existing = existingAuthUserFoundByEmail.getIdentifier();
×
734
        } else {
735
            long longToLookup = Long.parseLong(authuserId);
×
736
            AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
×
737
            if (specifiedUserToConvert != null) {
×
738
                builtInUserToConvert = specifiedUserToConvert;
×
739
            } else {
740
                return error(Response.Status.BAD_REQUEST,
×
741
                        "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
742
                                + " and no user was found using specified id " + longToLookup);
743
            }
744
        }
745
        String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
×
746
        Map<String, String> randomUser = authTestDataService.getRandomUser();
×
747
        // String eppn = UUID.randomUUID().toString().substring(0, 8);
748
        String eppn = randomUser.get("eppn");
×
749
        String idPEntityId = randomUser.get("idp");
×
750
        String notUsed = null;
×
751
        String separator = "|";
×
752
        UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(idPEntityId + separator + eppn, notUsed);
×
753
        String overwriteFirstName = randomUser.get("firstName");
×
754
        String overwriteLastName = randomUser.get("lastName");
×
755
        String overwriteEmail = randomUser.get("email");
×
756
        overwriteEmail = newEmailAddressToUse;
×
757
        logger.info("overwriteEmail: " + overwriteEmail);
×
758
        boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
×
759
        if (!validEmail) {
×
760
            // See https://github.com/IQSS/dataverse/issues/2998
761
            return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
×
762
        }
763
        /**
764
         * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
765
         *       constructor.
766
         */
767
        /**
768
         * Here we are exercising (via an API test) shibService.getAffiliation with the
769
         * TestShib IdP and a non-production DevShibAccountType.
770
         */
771
        idPEntityId = ShibUtil.testShibIdpEntityId;
×
772
        String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
×
773
                ShibServiceBean.DevShibAccountType.RANDOM);
774
        logger.info("overwriteAffiliation: " + overwriteAffiliation);
×
775
        /**
776
         * @todo Find a place to put "position" in the authenticateduser table:
777
         *       https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
778
         */
779
        String overwritePosition = "staff;student";
×
780
        AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
×
781
                overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
782
        JsonObjectBuilder response = Json.createObjectBuilder();
×
783
        JsonArrayBuilder problems = Json.createArrayBuilder();
×
784
        if (password != null) {
×
785
            response.add("password supplied", password);
×
786
            boolean knowsExistingPassword = false;
×
787
            BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
×
788
            if (oldBuiltInUser != null) {
×
789
                                if (builtInUserToConvert.isDeactivated()) {
×
790
                                        problems.add("builtin account has been deactivated");
×
791
                                        return error(Status.BAD_REQUEST, problems.build().toString());
×
792
                                }
793
                String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
×
794
                response.add("old username", usernameOfBuiltinAccountToConvert);
×
795
                AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
×
796
                        password);
797
                if (authenticatedUser != null) {
×
798
                    knowsExistingPassword = true;
×
799
                    AuthenticatedUser convertedUser = authSvc.convertBuiltInToShib(builtInUserToConvert, shibProviderId,
×
800
                            newUserIdentifierInLookupTable);
801
                    if (convertedUser != null) {
×
802
                        /**
803
                         * @todo Display name is not being overwritten. Logic must be in Shib backing
804
                         *       bean
805
                         */
806
                        AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
×
807
                        if (updatedInfoUser != null) {
×
808
                            response.add("display name overwritten with", updatedInfoUser.getName());
×
809
                        } else {
810
                            problems.add("couldn't update display info");
×
811
                        }
812
                    } else {
×
813
                        problems.add("unable to convert user");
×
814
                    }
815
                }
816
            } else {
×
817
                problems.add("couldn't find old username");
×
818
            }
819
            if (!knowsExistingPassword) {
×
820
                String message = "User doesn't know password.";
×
821
                problems.add(message);
×
822
                /**
823
                 * @todo Someday we should make a errorResponse method that takes JSON arrays
824
                 *       and objects.
825
                 */
826
                return error(Status.BAD_REQUEST, problems.build().toString());
×
827
            }
828
            // response.add("knows existing password", knowsExistingPassword);
829
        }
830

831
        response.add("user to convert", builtInUserToConvert.getIdentifier());
×
832
        response.add("existing user found by email (prompt to convert)", existing);
×
833
        response.add("changing to this provider", shibProviderId);
×
834
        response.add("value to overwrite old first name", overwriteFirstName);
×
835
        response.add("value to overwrite old last name", overwriteLastName);
×
836
        response.add("value to overwrite old email address", overwriteEmail);
×
837
        if (overwriteAffiliation != null) {
×
838
            response.add("affiliation", overwriteAffiliation);
×
839
        }
840
        response.add("problems", problems);
×
841
        return ok(response);
×
842
    }
843

844
    /**
845
     * This is used in testing via AdminIT.java but we don't expect sysadmins to use
846
     * this.
847
     */
848
    @PUT
849
    @AuthRequired
850
    @Path("authenticatedUsers/convert/builtin2oauth")
851
    public Response builtin2oauth(@Context ContainerRequestContext crc, String content) {
852
        logger.info("entering builtin2oauth...");
×
853
        try {
854
            AuthenticatedUser userToRunThisMethod = getRequestAuthenticatedUserOrDie(crc);
×
855
            if (!userToRunThisMethod.isSuperuser()) {
×
856
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
857
            }
858
        } catch (WrappedResponse ex) {
×
859
            return error(Response.Status.FORBIDDEN, "Superusers only.");
×
860
        }
×
861
        boolean disabled = false;
×
862
        if (disabled) {
×
863
            return error(Response.Status.BAD_REQUEST, "API endpoint disabled.");
×
864
        }
865
        AuthenticatedUser builtInUserToConvert = null;
×
866
        String emailToFind;
867
        String password;
868
        String authuserId = "0"; // could let people specify id on authuser table. probably better to let them
×
869
                                    // tell us their
870
        String newEmailAddressToUse;
871
        String newProviderId;
872
        String newPersistentUserIdInLookupTable;
873
        logger.info("content: " + content);
×
874
        try {
875
            String[] args = content.split(":");
×
876
            emailToFind = args[0];
×
877
            password = args[1];
×
878
            newEmailAddressToUse = args[2];
×
879
            newProviderId = args[3];
×
880
            newPersistentUserIdInLookupTable = args[4];
×
881
            // authuserId = args[666];
882
        } catch (ArrayIndexOutOfBoundsException ex) {
×
883
            return error(Response.Status.BAD_REQUEST, "Problem with content <<<" + content + ">>>: " + ex.toString());
×
884
        }
×
885
        AuthenticatedUser existingAuthUserFoundByEmail = shibService.findAuthUserByEmail(emailToFind);
×
886
        String existing = "NOT FOUND";
×
887
        if (existingAuthUserFoundByEmail != null) {
×
888
            builtInUserToConvert = existingAuthUserFoundByEmail;
×
889
            existing = existingAuthUserFoundByEmail.getIdentifier();
×
890
        } else {
891
            long longToLookup = Long.parseLong(authuserId);
×
892
            AuthenticatedUser specifiedUserToConvert = authSvc.findByID(longToLookup);
×
893
            if (specifiedUserToConvert != null) {
×
894
                builtInUserToConvert = specifiedUserToConvert;
×
895
            } else {
896
                return error(Response.Status.BAD_REQUEST,
×
897
                        "No user to convert. We couldn't find a *single* existing user account based on " + emailToFind
898
                                + " and no user was found using specified id " + longToLookup);
899
            }
900
        }
901
        // String shibProviderId = ShibAuthenticationProvider.PROVIDER_ID;
902
        Map<String, String> randomUser = authTestDataService.getRandomUser();
×
903
        // String eppn = UUID.randomUUID().toString().substring(0, 8);
904
        String eppn = randomUser.get("eppn");
×
905
        String idPEntityId = randomUser.get("idp");
×
906
        String notUsed = null;
×
907
        String separator = "|";
×
908
        // UserIdentifier newUserIdentifierInLookupTable = new
909
        // UserIdentifier(idPEntityId + separator + eppn, notUsed);
910
        UserIdentifier newUserIdentifierInLookupTable = new UserIdentifier(newPersistentUserIdInLookupTable, notUsed);
×
911
        String overwriteFirstName = randomUser.get("firstName");
×
912
        String overwriteLastName = randomUser.get("lastName");
×
913
        String overwriteEmail = randomUser.get("email");
×
914
        overwriteEmail = newEmailAddressToUse;
×
915
        logger.info("overwriteEmail: " + overwriteEmail);
×
916
        boolean validEmail = EMailValidator.isEmailValid(overwriteEmail);
×
917
        if (!validEmail) {
×
918
            // See https://github.com/IQSS/dataverse/issues/2998
919
            return error(Response.Status.BAD_REQUEST, "invalid email: " + overwriteEmail);
×
920
        }
921
        /**
922
         * @todo If affiliation is not null, put it in RoleAssigneeDisplayInfo
923
         *       constructor.
924
         */
925
        /**
926
         * Here we are exercising (via an API test) shibService.getAffiliation with the
927
         * TestShib IdP and a non-production DevShibAccountType.
928
         */
929
        // idPEntityId = ShibUtil.testShibIdpEntityId;
930
        // String overwriteAffiliation = shibService.getAffiliation(idPEntityId,
931
        // ShibServiceBean.DevShibAccountType.RANDOM);
932
        String overwriteAffiliation = null;
×
933
        logger.info("overwriteAffiliation: " + overwriteAffiliation);
×
934
        /**
935
         * @todo Find a place to put "position" in the authenticateduser table:
936
         *       https://github.com/IQSS/dataverse/issues/1444#issuecomment-74134694
937
         */
938
        String overwritePosition = "staff;student";
×
939
        AuthenticatedUserDisplayInfo displayInfo = new AuthenticatedUserDisplayInfo(overwriteFirstName,
×
940
                overwriteLastName, overwriteEmail, overwriteAffiliation, overwritePosition);
941
        JsonObjectBuilder response = Json.createObjectBuilder();
×
942
        JsonArrayBuilder problems = Json.createArrayBuilder();
×
943
        if (password != null) {
×
944
            response.add("password supplied", password);
×
945
            boolean knowsExistingPassword = false;
×
946
            BuiltinUser oldBuiltInUser = builtinUserService.findByUserName(builtInUserToConvert.getUserIdentifier());
×
947
            if (oldBuiltInUser != null) {
×
948
                String usernameOfBuiltinAccountToConvert = oldBuiltInUser.getUserName();
×
949
                response.add("old username", usernameOfBuiltinAccountToConvert);
×
950
                AuthenticatedUser authenticatedUser = authSvc.canLogInAsBuiltinUser(usernameOfBuiltinAccountToConvert,
×
951
                        password);
952
                if (authenticatedUser != null) {
×
953
                    knowsExistingPassword = true;
×
954
                    AuthenticatedUser convertedUser = authSvc.convertBuiltInUserToRemoteUser(builtInUserToConvert,
×
955
                            newProviderId, newUserIdentifierInLookupTable);
956
                    if (convertedUser != null) {
×
957
                        /**
958
                         * @todo Display name is not being overwritten. Logic must be in Shib backing
959
                         *       bean
960
                         */
961
                        AuthenticatedUser updatedInfoUser = authSvc.updateAuthenticatedUser(convertedUser, displayInfo);
×
962
                        if (updatedInfoUser != null) {
×
963
                            response.add("display name overwritten with", updatedInfoUser.getName());
×
964
                        } else {
965
                            problems.add("couldn't update display info");
×
966
                        }
967
                    } else {
×
968
                        problems.add("unable to convert user");
×
969
                    }
970
                }
971
            } else {
×
972
                problems.add("couldn't find old username");
×
973
            }
974
            if (!knowsExistingPassword) {
×
975
                String message = "User doesn't know password.";
×
976
                problems.add(message);
×
977
                /**
978
                 * @todo Someday we should make a errorResponse method that takes JSON arrays
979
                 *       and objects.
980
                 */
981
                return error(Status.BAD_REQUEST, problems.build().toString());
×
982
            }
983
            // response.add("knows existing password", knowsExistingPassword);
984
        }
985

986
        response.add("user to convert", builtInUserToConvert.getIdentifier());
×
987
        response.add("existing user found by email (prompt to convert)", existing);
×
988
        response.add("changing to this provider", newProviderId);
×
989
        response.add("value to overwrite old first name", overwriteFirstName);
×
990
        response.add("value to overwrite old last name", overwriteLastName);
×
991
        response.add("value to overwrite old email address", overwriteEmail);
×
992
        if (overwriteAffiliation != null) {
×
993
            response.add("affiliation", overwriteAffiliation);
×
994
        }
995
        response.add("problems", problems);
×
996
        return ok(response);
×
997
    }
998

999

1000

1001

1002
    @Path("roles")
1003
    @POST
1004
    public Response createNewBuiltinRole(RoleDTO roleDto) {
1005
        ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "createBuiltInRole")
×
1006
                .setInfo(roleDto.getAlias() + ":" + roleDto.getDescription());
×
1007
        try {
1008
            return ok(json(rolesSvc.save(roleDto.asRole())));
×
1009
        } catch (Exception e) {
×
1010
            alr.setActionResult(ActionLogRecord.Result.InternalError);
×
1011
            alr.setInfo(alr.getInfo() + "// " + e.getMessage());
×
1012
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1013
        } finally {
1014
            actionLogSvc.log(alr);
×
1015
        }
1016
    }
1017
    @Path("roles/{id}")
1018
    @PUT
1019
    public Response updateBuiltinRole(RoleDTO roleDto, @PathParam("id") long roleId) {
1020
        ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "updateBuiltInRole")
×
1021
                .setInfo(roleDto.getAlias() + ":" + roleDto.getDescription());
×
1022
        try {
1023
            DataverseRole role = roleDto.updateRoleFromDTO(rolesSvc.find(roleId));
×
1024
            return ok(json(rolesSvc.save(role)));
×
1025
        } catch (Exception e) {
×
1026
            alr.setActionResult(ActionLogRecord.Result.InternalError);
×
1027
            alr.setInfo(alr.getInfo() + "// " + e.getMessage());
×
1028
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1029
        } finally {
1030
            actionLogSvc.log(alr);
×
1031
        }
1032
    }
1033

1034
    @Path("roles")
1035
    @GET
1036
    public Response listBuiltinRoles() {
1037
        try {
1038
            return ok(rolesToJson(rolesSvc.findBuiltinRoles()));
×
1039
        } catch (Exception e) {
×
1040
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1041
        }
1042
    }
1043

1044
    @DELETE
1045
    @AuthRequired
1046
    @Path("roles/{id}")
1047
    public Response deleteRole(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1048

1049
        return response(req -> {
×
1050
            DataverseRole doomed = findRoleOrDie(id);
×
1051
            execCommand(new DeleteRoleCommand(req, doomed));
×
1052
            return ok("role " + doomed.getName() + " deleted.");
×
1053
        }, getRequestUser(crc));
×
1054
    }
1055

1056
    @Path("superuser/{identifier}")
1057
    @Deprecated
1058
    @POST
1059
    public Response toggleSuperuser(@PathParam("identifier") String identifier) {
1060
        ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "toggleSuperuser")
×
1061
                .setInfo(identifier);
×
1062
        try {
1063
            final AuthenticatedUser user = authSvc.getAuthenticatedUser(identifier);
×
1064
            return setSuperuserStatus(user, !user.isSuperuser());
×
1065
        } catch (Exception e) {
×
1066
            alr.setActionResult(ActionLogRecord.Result.InternalError);
×
1067
            alr.setInfo(alr.getInfo() + "// " + e.getMessage());
×
1068
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1069
        } finally {
1070
            actionLogSvc.log(alr);
×
1071
        }
1072
    }
1073

1074
    private Response setSuperuserStatus(AuthenticatedUser user, Boolean isSuperuser) {
1075
        if (user.isDeactivated()) {
×
1076
            return error(Status.BAD_REQUEST, "You cannot make a deactivated user a superuser.");
×
1077
        }
1078
        user.setSuperuser(isSuperuser);
×
1079
        return ok("User " + user.getIdentifier() + " " + (user.isSuperuser() ? "set" : "removed")
×
1080
                + " as a superuser.");
1081
    }
1082

1083
    @Path("superuser/{identifier}")
1084
    @PUT
1085
    // Using string instead of boolean so user doesn't need to add a Content-type header in their request
1086
    public Response setSuperuserStatus(@PathParam("identifier") String identifier, String isSuperuser) {
1087
        ActionLogRecord alr = new ActionLogRecord(ActionLogRecord.ActionType.Admin, "setSuperuserStatus")
×
1088
                .setInfo(identifier + ":" + isSuperuser);
×
1089
        try {
1090
            return setSuperuserStatus(authSvc.getAuthenticatedUser(identifier), StringUtil.isTrue(isSuperuser));
×
1091
        } catch (Exception e) {
×
1092
            alr.setActionResult(ActionLogRecord.Result.InternalError);
×
1093
            alr.setInfo(alr.getInfo() + "// " + e.getMessage());
×
1094
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1095
        } finally {
1096
            actionLogSvc.log(alr);
×
1097
        }
1098
    }
1099

1100
    @GET
1101
    @Path("validate/datasets")
1102
    @Produces({"application/json"})
1103
    public Response validateAllDatasets(@QueryParam("variables") boolean includeVariables) {
1104
        
1105
        // Streaming output: the API will start producing 
1106
        // the output right away, as it goes through the list 
1107
        // of the datasets; there's potentially a lot of content 
1108
        // to validate, so we don't want to wait for the process 
1109
        // to finish. Or to wait to encounter the first invalid 
1110
        // object - so we'll be reporting both the success and failure
1111
        // outcomes for all the validated datasets, to give the user
1112
        // an indication of the progress. 
1113
        // This is the first streaming API that produces json that 
1114
        // we have; there may be better ways to stream json - but 
1115
        // what I have put together below works. -- L.A. 
1116
        StreamingOutput stream = new StreamingOutput() {
×
1117

1118
            @Override
1119
            public void write(OutputStream os) throws IOException,
1120
                    WebApplicationException {
1121
                os.write("{\"datasets\": [\n".getBytes());
×
1122
                
1123
                boolean wroteObject = false;
×
1124
                for (Long datasetId : datasetService.findAllLocalDatasetIds()) {
×
1125
                    // Potentially, there's a godzillion datasets in this Dataverse. 
1126
                    // This is why we go through the list of ids here, and instantiate 
1127
                    // only one dataset at a time. 
1128
                    boolean success = false;
×
1129
                    boolean constraintViolationDetected = false;
×
1130
                     
1131
                    JsonObjectBuilder output = Json.createObjectBuilder();
×
1132
                    output.add("datasetId", datasetId);
×
1133

1134
                    
1135
                    try {
1136
                        datasetService.instantiateDatasetInNewTransaction(datasetId, includeVariables);
×
1137
                        success = true;
×
1138
                    } catch (Exception ex) {
×
1139
                        Throwable cause = ex;
×
1140
                        while (cause != null) {
×
1141
                            if (cause instanceof ConstraintViolationException) {
×
1142
                                ConstraintViolationException constraintViolationException = (ConstraintViolationException) cause;
×
1143
                                for (ConstraintViolation<?> constraintViolation : constraintViolationException
×
1144
                                        .getConstraintViolations()) {
×
1145
                                    String databaseRow = constraintViolation.getLeafBean().toString();
×
1146
                                    String field = constraintViolation.getPropertyPath().toString();
×
1147
                                    String invalidValue = null;
×
1148
                                    if (constraintViolation.getInvalidValue() != null) {
×
1149
                                        invalidValue = constraintViolation.getInvalidValue().toString();
×
1150
                                    }
1151
                                    output.add("status", "invalid");
×
1152
                                    output.add("entityClassDatabaseTableRowId", databaseRow);
×
1153
                                    output.add("field", field);
×
1154
                                    output.add("invalidValue", invalidValue == null ? "NULL" : invalidValue);
×
1155
                                    
1156
                                    constraintViolationDetected = true; 
×
1157
                                    
1158
                                    break; 
×
1159
                                    
1160
                                }
1161
                            }
1162
                            cause = cause.getCause();
×
1163
                        }
1164
                    }
×
1165
                    
1166
                    
1167
                    if (success) {
×
1168
                        output.add("status", "valid");
×
1169
                    } else if (!constraintViolationDetected) {
×
1170
                        output.add("status", "unknown");
×
1171
                    }
1172
                    
1173
                    // write it out:
1174
                    
1175
                    if (wroteObject) {
×
1176
                        os.write(",\n".getBytes());
×
1177
                    }
1178

1179
                    os.write(output.build().toString().getBytes(StandardCharsets.UTF_8));
×
1180
                    
1181
                    if (!wroteObject) {
×
1182
                        wroteObject = true;
×
1183
                    }
1184
                }
×
1185
                
1186
                
1187
                os.write("\n]\n}\n".getBytes());
×
1188
            }
×
1189
            
1190
        };
1191
        return Response.ok(stream).build();
×
1192
    }
1193
        
1194
    @Path("validate/dataset/{id}")
1195
    @GET
1196
    public Response validateDataset(@PathParam("id") String id, @QueryParam("variables") boolean includeVariables) {
1197
        Dataset dataset;
1198
        try {
1199
            dataset = findDatasetOrDie(id);
×
1200
        } catch (Exception ex) {
×
1201
            return error(Response.Status.NOT_FOUND, "No Such Dataset");
×
1202
        }
×
1203

1204
        Long dbId = dataset.getId();
×
1205

1206
        String msg = "unknown";
×
1207
        try {
1208
            datasetService.instantiateDatasetInNewTransaction(dbId, includeVariables);
×
1209
            msg = "valid";
×
1210
        } catch (Exception ex) {
×
1211
            Throwable cause = ex;
×
1212
            while (cause != null) {
×
1213
                if (cause instanceof ConstraintViolationException) {
×
1214
                    ConstraintViolationException constraintViolationException = (ConstraintViolationException) cause;
×
1215
                    for (ConstraintViolation<?> constraintViolation : constraintViolationException
×
1216
                            .getConstraintViolations()) {
×
1217
                        String databaseRow = constraintViolation.getLeafBean().toString();
×
1218
                        String field = constraintViolation.getPropertyPath().toString();
×
1219
                        String invalidValue = null; 
×
1220
                        if (constraintViolation.getInvalidValue() != null) {
×
1221
                            invalidValue = constraintViolation.getInvalidValue().toString();
×
1222
                        }
1223
                        JsonObjectBuilder violation = Json.createObjectBuilder();
×
1224
                        violation.add("entityClassDatabaseTableRowId", databaseRow);
×
1225
                        violation.add("field", field);
×
1226
                        violation.add("invalidValue", invalidValue == null ? "NULL" : invalidValue);
×
1227
                        return ok(violation);
×
1228
                    }
1229
                }
1230
                cause = cause.getCause();
×
1231
            }
1232
        }
×
1233
        return ok(msg);
×
1234
    }
1235
    
1236
    // This API does the same thing as /validateDataFileHashValue/{fileId}, 
1237
    // but for all the files in the dataset, with streaming output.
1238
    @GET
1239
    @Path("validate/dataset/files/{id}")
1240
    @Produces({"application/json"})
1241
    public Response validateDatasetDatafiles(@PathParam("id") String id) {
1242
        
1243
        // Streaming output: the API will start producing 
1244
        // the output right away, as it goes through the list 
1245
        // of the datafiles in the dataset.
1246
        // The streaming mechanism is modeled after validate/datasets API.
1247
        StreamingOutput stream = new StreamingOutput() {
×
1248

1249
            @Override
1250
            public void write(OutputStream os) throws IOException,
1251
                    WebApplicationException {
1252
                Dataset dataset;
1253
        
1254
                try {
1255
                    dataset = findDatasetOrDie(id);
×
1256
                } catch (Exception ex) {
×
1257
                    throw new IOException(ex.getMessage());
×
1258
                }
×
1259
                
1260
                os.write("{\"dataFiles\": [\n".getBytes());
×
1261
                
1262
                boolean wroteObject = false;
×
1263
                for (DataFile dataFile : dataset.getFiles()) {
×
1264
                    // Potentially, there's a godzillion datasets in this Dataverse. 
1265
                    // This is why we go through the list of ids here, and instantiate 
1266
                    // only one dataset at a time. 
1267
                    boolean success = false;
×
1268
                    boolean constraintViolationDetected = false;
×
1269
                     
1270
                    JsonObjectBuilder output = Json.createObjectBuilder();
×
1271
                    output.add("datafileId", dataFile.getId());
×
1272
                    output.add("storageIdentifier", dataFile.getStorageIdentifier());
×
1273

1274
                    
1275
                    try {
1276
                        FileUtil.validateDataFileChecksum(dataFile);
×
1277
                        success = true;
×
1278
                    } catch (IOException ex) {
×
1279
                        output.add("status", "invalid");
×
1280
                        output.add("errorMessage", ex.getMessage());
×
1281
                    }
×
1282
                    
1283
                    if (success) {
×
1284
                        output.add("status", "valid");
×
1285
                    } 
1286
                    
1287
                    // write it out:
1288
                    
1289
                    if (wroteObject) {
×
1290
                        os.write(",\n".getBytes());
×
1291
                    }
1292

1293
                    os.write(output.build().toString().getBytes(StandardCharsets.UTF_8));
×
1294
                    
1295
                    if (!wroteObject) {
×
1296
                        wroteObject = true;
×
1297
                    }
1298
                }
×
1299
                
1300
                os.write("\n]\n}\n".getBytes());
×
1301
            }
×
1302
            
1303
        };
1304
        return Response.ok(stream).build();
×
1305
    }
1306

1307
    @Path("assignments/assignees/{raIdtf: .*}")
1308
    @GET
1309
    public Response getAssignmentsFor(@PathParam("raIdtf") String raIdtf) {
1310

1311
        JsonArrayBuilder arr = Json.createArrayBuilder();
×
1312
        roleAssigneeSvc.getAssignmentsFor(raIdtf).forEach(a -> arr.add(json(a)));
×
1313

1314
        return ok(arr);
×
1315
    }
1316

1317
    /**
1318
     * This method is used in integration tests.
1319
     *
1320
     * @param userId
1321
     *            The database id of an AuthenticatedUser.
1322
     * @return The confirm email token.
1323
     */
1324
    @Path("confirmEmail/{userId}")
1325
    @GET
1326
    public Response getConfirmEmailToken(@PathParam("userId") long userId) {
1327
        AuthenticatedUser user = authSvc.findByID(userId);
×
1328
        if (user != null) {
×
1329
            ConfirmEmailData confirmEmailData = confirmEmailSvc.findSingleConfirmEmailDataByUser(user);
×
1330
            if (confirmEmailData != null) {
×
1331
                return ok(Json.createObjectBuilder().add("token", confirmEmailData.getToken()));
×
1332
            }
1333
        }
1334
        return error(Status.BAD_REQUEST, "Could not find confirm email token for user " + userId);
×
1335
    }
1336

1337
    /**
1338
     * This method is used in integration tests.
1339
     *
1340
     * @param userId
1341
     *            The database id of an AuthenticatedUser.
1342
     */
1343
    @Path("confirmEmail/{userId}")
1344
    @POST
1345
    public Response startConfirmEmailProcess(@PathParam("userId") long userId) {
1346
        AuthenticatedUser user = authSvc.findByID(userId);
×
1347
        if (user != null) {
×
1348
            try {
1349
                ConfirmEmailInitResponse confirmEmailInitResponse = confirmEmailSvc.beginConfirm(user);
×
1350
                ConfirmEmailData confirmEmailData = confirmEmailInitResponse.getConfirmEmailData();
×
1351
                return ok(Json.createObjectBuilder().add("tokenCreated", confirmEmailData.getCreated().toString())
×
1352
                        .add("identifier", user.getUserIdentifier()));
×
1353
            } catch (ConfirmEmailException ex) {
×
1354
                return error(Status.BAD_REQUEST,
×
1355
                        "Could not start confirm email process for user " + userId + ": " + ex.getLocalizedMessage());
×
1356
            }
1357
        }
1358
        return error(Status.BAD_REQUEST, "Could not find user based on " + userId);
×
1359
    }
1360

1361
    /**
1362
     * This method is used by an integration test in UsersIT.java to exercise bug
1363
     * https://github.com/IQSS/dataverse/issues/3287 . Not for use by users!
1364
     */
1365
    @Path("convertUserFromBcryptToSha1")
1366
    @POST
1367
    public Response convertUserFromBcryptToSha1(String json) {
1368
        JsonReader jsonReader = Json.createReader(new StringReader(json));
×
1369
        JsonObject object = jsonReader.readObject();
×
1370
        jsonReader.close();
×
1371
        BuiltinUser builtinUser = builtinUserService.find(new Long(object.getInt("builtinUserId")));
×
1372
        builtinUser.updateEncryptedPassword("4G7xxL9z11/JKN4jHPn4g9iIQck=", 0); // password is "sha-1Pass", 0 means
×
1373
                                                                                // SHA-1
1374
        BuiltinUser savedUser = builtinUserService.save(builtinUser);
×
1375
        return ok("foo: " + savedUser);
×
1376

1377
    }
1378

1379
    @Path("permissions/{dvo}")
1380
    @AuthRequired
1381
    @GET
1382
    public Response findPermissonsOn(@Context final ContainerRequestContext crc, @PathParam("dvo") final String dvo) {
1383
        try {
1384
            final DvObject dvObj = findDvo(dvo);
×
1385
            final User aUser = getRequestUser(crc);
×
1386
            final JsonObjectBuilder bld = Json.createObjectBuilder();
×
1387
            bld.add("user", aUser.getIdentifier());
×
1388
            bld.add("permissions", json(permissionSvc.permissionsFor(createDataverseRequest(aUser), dvObj)));
×
1389
            return ok(bld);
×
1390
        } catch (WrappedResponse r) {
×
1391
            return r.getResponse();
×
1392
        } catch (Exception e) {
×
1393
            logger.log(Level.SEVERE, "Error while testing permissions", e);
×
1394
            return error(Response.Status.INTERNAL_SERVER_ERROR, e.getMessage());
×
1395
        }
1396
    }
1397

1398
    @Path("assignee/{idtf}")
1399
    @GET
1400
    public Response findRoleAssignee(@PathParam("idtf") String idtf) {
1401
        RoleAssignee ra = roleAssigneeSvc.getRoleAssignee(idtf);
×
1402
        return (ra == null) ? notFound("Role Assignee '" + idtf + "' not found.") : ok(json(ra.getDisplayInfo()));
×
1403
    }
1404

1405
    @Path("datasets/integrity/{datasetVersionId}/fixmissingunf")
1406
    @POST
1407
    public Response fixUnf(@PathParam("datasetVersionId") String datasetVersionId,
1408
            @QueryParam("forceRecalculate") boolean forceRecalculate) {
1409
        JsonObjectBuilder info = datasetVersionSvc.fixMissingUnf(datasetVersionId, forceRecalculate);
×
1410
        return ok(info);
×
1411
    }
1412

1413
    @Path("datafiles/integrity/fixmissingoriginaltypes")
1414
    @GET
1415
    public Response fixMissingOriginalTypes() {
1416
        JsonObjectBuilder info = Json.createObjectBuilder();
×
1417

1418
        List<Long> affectedFileIds = fileService.selectFilesWithMissingOriginalTypes();
×
1419

1420
        if (affectedFileIds.isEmpty()) {
×
1421
            info.add("message",
×
1422
                    "All the tabular files in the database already have the original types set correctly; exiting.");
1423
        } else {
1424
            for (Long fileid : affectedFileIds) {
×
1425
                logger.fine("found file id: " + fileid);
×
1426
            }
×
1427
            info.add("message", "Found " + affectedFileIds.size()
×
1428
                    + " tabular files with missing original types. Kicking off an async job that will repair the files in the background.");
1429
        }
1430

1431
        ingestService.fixMissingOriginalTypes(affectedFileIds);
×
1432

1433
        return ok(info);
×
1434
    }
1435
        
1436
    @Path("datafiles/integrity/fixmissingoriginalsizes")
1437
    @GET
1438
    public Response fixMissingOriginalSizes(@QueryParam("limit") Integer limit) {
1439
        JsonObjectBuilder info = Json.createObjectBuilder();
×
1440

1441
        List<Long> affectedFileIds = fileService.selectFilesWithMissingOriginalSizes();
×
1442

1443
        if (affectedFileIds.isEmpty()) {
×
1444
            info.add("message",
×
1445
                    "All the tabular files in the database already have the original sizes set correctly; exiting.");
1446
        } else {
1447
            
1448
            int howmany = affectedFileIds.size();
×
1449
            String message = "Found " + howmany + " tabular files with missing original sizes. "; 
×
1450
            
1451
            if (limit == null || howmany <= limit) {
×
1452
                message = message.concat(" Kicking off an async job that will repair the files in the background.");
×
1453
            } else {
1454
                affectedFileIds.subList(limit, howmany-1).clear();
×
1455
                message = message.concat(" Kicking off an async job that will repair the " + limit + " files in the background.");                        
×
1456
            }
1457
            info.add("message", message);
×
1458
        }
1459

1460
        ingestService.fixMissingOriginalSizes(affectedFileIds);
×
1461
        return ok(info);
×
1462
    }
1463

1464
    /**
1465
     * This method is used in API tests, called from UtilIt.java.
1466
     */
1467
    @GET
1468
    @Path("datasets/thumbnailMetadata/{id}")
1469
    public Response getDatasetThumbnailMetadata(@PathParam("id") Long idSupplied) {
1470
        Dataset dataset = datasetSvc.find(idSupplied);
×
1471
        if (dataset == null) {
×
1472
            return error(Response.Status.NOT_FOUND, "Could not find dataset based on id supplied: " + idSupplied + ".");
×
1473
        }
1474
        JsonObjectBuilder data = Json.createObjectBuilder();
×
1475
        DatasetThumbnail datasetThumbnail = dataset.getDatasetThumbnail(ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE);
×
1476
        data.add("isUseGenericThumbnail", dataset.isUseGenericThumbnail());
×
1477
        data.add("datasetLogoPresent", DatasetUtil.isDatasetLogoPresent(dataset, ImageThumbConverter.DEFAULT_CARDIMAGE_SIZE));
×
1478
        if (datasetThumbnail != null) {
×
1479
            data.add("datasetThumbnailBase64image", datasetThumbnail.getBase64image());
×
1480
            DataFile dataFile = datasetThumbnail.getDataFile();
×
1481
            if (dataFile != null) {
×
1482
                /**
1483
                 * @todo Change this from a String to a long.
1484
                 */
1485
                data.add("dataFileId", dataFile.getId().toString());
×
1486
            }
1487
        }
1488
        return ok(data);
×
1489
    }
1490

1491
    /**
1492
     * validatePassword
1493
     * <p>
1494
     * Validate a password with an API call
1495
     *
1496
     * @param password
1497
     *            The password
1498
     * @return A response with the validation result.
1499
     */
1500
    @Path("validatePassword")
1501
    @POST
1502
    public Response validatePassword(String password) {
1503

1504
        final List<String> errors = passwordValidatorService.validate(password, new Date(), false);
×
1505
        final JsonArrayBuilder errorArray = Json.createArrayBuilder();
×
1506
        errors.forEach(errorArray::add);
×
1507
        return ok(Json.createObjectBuilder().add("password", password).add("errors", errorArray));
×
1508
    }
1509

1510
    @GET
1511
    @Path("/isOrcid")
1512
    public Response isOrcidEnabled() {
1513
        return authSvc.isOrcidEnabled() ? ok("Orcid is enabled") : ok("no orcid for you.");
×
1514
    }
1515

1516
    @POST
1517
    @AuthRequired
1518
    @Path("{id}/reregisterHDLToPID")
1519
    public Response reregisterHdlToPID(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1520
        logger.info("Starting to reregister  " + id + " Dataset Id. (from hdl to doi)" + new Date());
×
1521
        try {
1522

1523
            
1524
            User u = getRequestUser(crc);
×
1525
            if (!u.isSuperuser()) {
×
1526
                logger.info("Bad Request Unauthor " );
×
1527
                return error(Status.UNAUTHORIZED, BundleUtil.getStringFromBundle("admin.api.auth.mustBeSuperUser"));
×
1528
            }
1529

1530
            DataverseRequest r = createDataverseRequest(u);
×
1531
            Dataset ds = findDatasetOrDie(id);
×
1532
            
1533
            if (HandlePidProvider.HDL_PROTOCOL.equals(dvObjectService.getEffectivePidGenerator(ds).getProtocol())) {
×
1534
                logger.info("Bad Request protocol set to handle  " );
×
1535
                return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.set.for.doi"));
×
1536
            }
1537
            if (ds.getIdentifier() != null && !ds.getIdentifier().isEmpty() && ds.getProtocol().equals(HandlePidProvider.HDL_PROTOCOL)) {
×
1538
                execCommand(new RegisterDvObjectCommand(r, ds, true));
×
1539
            } else {
1540
                return error(Status.BAD_REQUEST, BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure.must.be.hdl.dataset"));
×
1541
            }
1542

1543
        } catch (WrappedResponse r) {
×
1544
            logger.info("Failed to migrate Dataset Handle id: " + id);
×
1545
            return badRequest(BundleUtil.getStringFromBundle("admin.api.migrateHDL.failure", Arrays.asList(id)));
×
1546
        } catch (Exception e) {
×
1547
            logger.info("Failed to migrate Dataset Handle id: " + id + " Unexpected Exception " + e.getMessage());
×
1548
            List<String> args = Arrays.asList(id,e.getMessage());
×
1549
            return badRequest(BundleUtil.getStringFromBundle("admin.api.migrateHDL.failureWithException", args));
×
1550
        }
×
1551
        
1552
        return ok(BundleUtil.getStringFromBundle("admin.api.migrateHDL.success"));
×
1553
    }
1554

1555
    @GET
1556
    @AuthRequired
1557
    @Path("{id}/registerDataFile")
1558
    public Response registerDataFile(@Context ContainerRequestContext crc, @PathParam("id") String id) {
1559
        logger.info("Starting to register  " + id + " file id. " + new Date());
×
1560

1561
        try {
1562
            User u = getRequestUser(crc);
×
1563
            DataverseRequest r = createDataverseRequest(u);
×
1564
            DataFile df = findDataFileOrDie(id);
×
1565
            if(!systemConfig.isFilePIDsEnabledForCollection(df.getOwner().getOwner())) {
×
1566
                return forbidden("PIDs are not enabled for this file's collection.");
×
1567
            }
1568
            if (df.getIdentifier() == null || df.getIdentifier().isEmpty()) {
×
1569
                execCommand(new RegisterDvObjectCommand(r, df));
×
1570
            } else {
1571
                return ok("File was already registered. ");
×
1572
            }
1573

1574
        } catch (WrappedResponse r) {
×
1575
            logger.info("Failed to register file id: " + id);
×
1576
        } catch (Exception e) {
×
1577
            logger.info("Failed to register file id: " + id + " Unexpecgted Exception " + e.getMessage());
×
1578
        }
×
1579
        return ok("Datafile registration complete. File registered successfully.");
×
1580
    }
1581

1582
    @GET
1583
    @AuthRequired
1584
    @Path("/registerDataFileAll")
1585
    public Response registerDataFileAll(@Context ContainerRequestContext crc) {
1586
        Integer count = fileService.findAll().size();
×
1587
        Integer successes = 0;
×
1588
        Integer alreadyRegistered = 0;
×
1589
        Integer released = 0;
×
1590
        Integer draft = 0;
×
1591
        Integer skipped = 0;
×
1592
        logger.info("Starting to register: analyzing " + count + " files. " + new Date());
×
1593
        logger.info("Only unregistered, published files will be registered.");
×
1594
        User u = null;
×
1595
        try {
1596
            u = getRequestAuthenticatedUserOrDie(crc);
×
1597
        } catch (WrappedResponse e1) {
×
1598
            return error(Status.UNAUTHORIZED, "api key required");
×
1599
        }
×
1600
        DataverseRequest r = createDataverseRequest(u);
×
1601
        for (DataFile df : fileService.findAll()) {
×
1602
            try {
1603
                if ((df.getIdentifier() == null || df.getIdentifier().isEmpty())) {
×
1604
                    if(!systemConfig.isFilePIDsEnabledForCollection(df.getOwner().getOwner())) {
×
1605
                        skipped++;
×
1606
                        if (skipped % 100 == 0) {
×
1607
                            logger.info(skipped + " of  " + count + " files not in collections that allow file PIDs. " + new Date());
×
1608
                        }
1609
                    } else if (df.isReleased()) {
×
1610
                        released++;
×
1611
                        execCommand(new RegisterDvObjectCommand(r, df));
×
1612
                        successes++;
×
1613
                        if (successes % 100 == 0) {
×
1614
                            logger.info(successes + " of  " + count + " files registered successfully. " + new Date());
×
1615
                        }
1616
                        try {
1617
                            Thread.sleep(1000);
×
1618
                        } catch (InterruptedException ie) {
×
1619
                            logger.warning("Interrupted Exception when attempting to execute Thread.sleep()!");
×
1620
                        }
×
1621
                    } else {
1622
                        draft++;
×
1623
                        if (draft % 100 == 0) {
×
1624
                          logger.info(draft + " of  " + count + " files not yet published");
×
1625
                        }
1626
                    }
1627
                } else {
1628
                    alreadyRegistered++;
×
1629
                    if(alreadyRegistered % 100 == 0) {
×
1630
                      logger.info(alreadyRegistered + " of  " + count + " files are already registered. " + new Date());
×
1631
                    }
1632
                }
1633
            } catch (WrappedResponse ex) {
×
1634
                logger.info("Failed to register file id: " + df.getId());
×
1635
                Logger.getLogger(Datasets.class.getName()).log(Level.SEVERE, null, ex);
×
1636
            } catch (Exception e) {
×
1637
                logger.info("Unexpected Exception: " + e.getMessage());
×
1638
            }
×
1639
            
1640

1641
        }
×
1642
        logger.info("Final Results:");
×
1643
        logger.info(alreadyRegistered + " of  " + count + " files were already registered. " + new Date());
×
1644
        logger.info(draft + " of  " + count + " files are not yet published. " + new Date());
×
1645
        logger.info(released + " of  " + count + " unregistered, published files to register. " + new Date());
×
1646
        logger.info(successes + " of  " + released + " unregistered, published files registered successfully. "
×
1647
                + new Date());
1648
        logger.info(skipped + " of  " + count + " files not in collections that allow file PIDs. " + new Date());
×
1649

1650
        return ok("Datafile registration complete." + successes + " of  " + released
×
1651
                + " unregistered, published files registered successfully.");
1652
    }
1653
    
1654
    @GET
1655
    @AuthRequired
1656
    @Path("/registerDataFiles/{alias}")
1657
    public Response registerDataFilesInCollection(@Context ContainerRequestContext crc, @PathParam("alias") String alias, @QueryParam("sleep") Integer sleepInterval) {
1658
        Dataverse collection;
1659
        try {
1660
            collection = findDataverseOrDie(alias);
×
1661
        } catch (WrappedResponse r) {
×
1662
            return r.getResponse();
×
1663
        }
×
1664
        
1665
        AuthenticatedUser superuser = authSvc.getAdminUser();
×
1666
        if (superuser == null) {
×
1667
            return error(Response.Status.INTERNAL_SERVER_ERROR, "Cannot find the superuser to execute /admin/registerDataFiles.");
×
1668
        }
1669
        
1670
        if (!systemConfig.isFilePIDsEnabledForCollection(collection)) {
×
1671
            return ok("Registration of file-level pid is disabled in collection "+alias+"; nothing to do");
×
1672
        }
1673
        
1674
        List<DataFile> dataFiles = fileService.findByDirectCollectionOwner(collection.getId());
×
1675
        Integer count = dataFiles.size();
×
1676
        Integer countSuccesses = 0;
×
1677
        Integer countAlreadyRegistered = 0;
×
1678
        Integer countReleased = 0;
×
1679
        Integer countDrafts = 0;
×
1680
        
1681
        if (sleepInterval == null) {
×
1682
            sleepInterval = 1; 
×
1683
        } else if (sleepInterval.intValue() < 1) {
×
1684
            return error(Response.Status.BAD_REQUEST, "Invalid sleep interval: "+sleepInterval);
×
1685
        }
1686
        
1687
        logger.info("Starting to register: analyzing " + count + " files. " + new Date());
×
1688
        logger.info("Only unregistered, published files will be registered.");
×
1689
        
1690
        
1691
        
1692
        for (DataFile df : dataFiles) {
×
1693
            try {
1694
                if ((df.getIdentifier() == null || df.getIdentifier().isEmpty())) {
×
1695
                    if (df.isReleased()) {
×
1696
                        countReleased++;
×
1697
                        DataverseRequest r = createDataverseRequest(superuser);
×
1698
                        execCommand(new RegisterDvObjectCommand(r, df));
×
1699
                        countSuccesses++;
×
1700
                        if (countSuccesses % 100 == 0) {
×
1701
                            logger.info(countSuccesses + " out of " + count + " files registered successfully. " + new Date());
×
1702
                        }
1703
                        try {
1704
                            Thread.sleep(sleepInterval * 1000);
×
1705
                        } catch (InterruptedException ie) {
×
1706
                            logger.warning("Interrupted Exception when attempting to execute Thread.sleep()!");
×
1707
                        }
×
1708
                    } else {
×
1709
                        countDrafts++;
×
1710
                        logger.fine(countDrafts + " out of " + count + " files not yet published");
×
1711
                    }
1712
                } else {
1713
                    countAlreadyRegistered++;
×
1714
                    logger.fine(countAlreadyRegistered + " out of " + count + " files are already registered. " + new Date());
×
1715
                }
1716
            } catch (WrappedResponse ex) {
×
1717
                countReleased++;
×
1718
                logger.info("Failed to register file id: " + df.getId());
×
1719
                Logger.getLogger(Datasets.class.getName()).log(Level.SEVERE, null, ex);
×
1720
            } catch (Exception e) {
×
1721
                logger.info("Unexpected Exception: " + e.getMessage());
×
1722
            }
×
1723
        }
×
1724
        
1725
        logger.info(countAlreadyRegistered + " out of " + count + " files were already registered. " + new Date());
×
1726
        logger.info(countDrafts + " out of " + count + " files are not yet published. " + new Date());
×
1727
        logger.info(countReleased + " out of " + count + " unregistered, published files to register. " + new Date());
×
1728
        logger.info(countSuccesses + " out of " + countReleased + " unregistered, published files registered successfully. "
×
1729
                + new Date());
1730

1731
        return ok("Datafile registration complete. " + countSuccesses + " out of " + countReleased
×
1732
                + " unregistered, published files registered successfully.");
1733
    }
1734

1735
    @GET
1736
    @AuthRequired
1737
    @Path("/updateHashValues/{alg}")
1738
    public Response updateHashValues(@Context ContainerRequestContext crc, @PathParam("alg") String alg, @QueryParam("num") int num) {
1739
        Integer count = fileService.findAll().size();
×
1740
        Integer successes = 0;
×
1741
        Integer alreadyUpdated = 0;
×
1742
        Integer rehashed = 0;
×
1743
        Integer harvested = 0;
×
1744

1745
        if (num <= 0)
×
1746
            num = Integer.MAX_VALUE;
×
1747
        DataFile.ChecksumType cType = null;
×
1748
        try {
1749
            cType = DataFile.ChecksumType.fromString(alg);
×
1750
        } catch (IllegalArgumentException iae) {
×
1751
            return error(Status.BAD_REQUEST, "Unknown algorithm");
×
1752
        }
×
1753
        logger.info("Starting to rehash: analyzing " + count + " files. " + new Date());
×
1754
        logger.info("Hashes not created with " + alg + " will be verified, and, if valid, replaced with a hash using "
×
1755
                + alg);
1756
        try {
1757
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1758
            if (!u.isSuperuser())
×
1759
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1760
        } catch (WrappedResponse e1) {
×
1761
            return error(Status.UNAUTHORIZED, "api key required");
×
1762
        }
×
1763

1764
        for (DataFile df : fileService.findAll()) {
×
1765
            if (rehashed.intValue() >= num)
×
1766
                break;
×
1767
            InputStream in = null;
×
1768
            InputStream in2 = null;
×
1769
            try {
1770
                if (df.isHarvested()) {
×
1771
                    harvested++;
×
1772
                } else {
1773
                    if (!df.getChecksumType().equals(cType)) {
×
1774

1775
                        rehashed++;
×
1776
                        logger.fine(rehashed + ": Datafile: " + df.getFileMetadata().getLabel() + ", "
×
1777
                                + df.getIdentifier());
×
1778
                        // verify hash and calc new one to replace it
1779
                        StorageIO<DataFile> storage = df.getStorageIO();
×
1780
                        storage.open(DataAccessOption.READ_ACCESS);
×
1781
                        if (!df.isTabularData()) {
×
1782
                            in = storage.getInputStream();
×
1783
                        } else {
1784
                            // if this is a tabular file, read the preserved original "auxiliary file"
1785
                            // instead:
1786
                            in = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1787
                        }
1788
                        if (in == null)
×
1789
                            logger.warning("Cannot retrieve file.");
×
1790
                        String currentChecksum = FileUtil.calculateChecksum(in, df.getChecksumType());
×
1791
                        if (currentChecksum.equals(df.getChecksumValue())) {
×
1792
                            logger.fine("Current checksum for datafile: " + df.getFileMetadata().getLabel() + ", "
×
1793
                                    + df.getIdentifier() + " is valid");
×
1794
                            // Need to reset so we don't get the same stream (StorageIO class inputstreams
1795
                            // are normally only used once)
1796
                            storage.setInputStream(null);
×
1797
                            storage.open(DataAccessOption.READ_ACCESS);
×
1798
                            if (!df.isTabularData()) {
×
1799
                                in2 = storage.getInputStream();
×
1800
                            } else {
1801
                                // if this is a tabular file, read the preserved original "auxiliary file"
1802
                                // instead:
1803
                                in2 = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1804
                            }
1805
                            if (in2 == null)
×
1806
                                logger.warning("Cannot retrieve file to calculate new checksum.");
×
1807
                            String newChecksum = FileUtil.calculateChecksum(in2, cType);
×
1808

1809
                            df.setChecksumType(cType);
×
1810
                            df.setChecksumValue(newChecksum);
×
1811
                            successes++;
×
1812
                            if (successes % 100 == 0) {
×
1813
                                logger.info(
×
1814
                                        successes + " of  " + count + " files rehashed successfully. " + new Date());
1815
                            }
1816
                        } else {
×
1817
                            logger.warning("Problem: Current checksum for datafile: " + df.getFileMetadata().getLabel()
×
1818
                                    + ", " + df.getIdentifier() + " is INVALID");
×
1819
                        }
1820
                    } else {
×
1821
                        alreadyUpdated++;
×
1822
                        if (alreadyUpdated % 100 == 0) {
×
1823
                            logger.info(alreadyUpdated + " of  " + count
×
1824
                                    + " files are already have hashes with the new algorithm. " + new Date());
1825
                        }
1826
                    }
1827
                }
1828
            } catch (Exception e) {
×
1829
                logger.warning("Unexpected Exception: " + e.getMessage());
×
1830

1831
            } finally {
1832
                IOUtils.closeQuietly(in);
×
1833
                IOUtils.closeQuietly(in2);
×
1834
            }
1835
        }
×
1836
        logger.info("Final Results:");
×
1837
        logger.info(harvested + " harvested files skipped.");
×
1838
        logger.info(
×
1839
                alreadyUpdated + " of  " + count + " files already had hashes with the new algorithm. " + new Date());
1840
        logger.info(rehashed + " of  " + count + " files to rehash. " + new Date());
×
1841
        logger.info(
×
1842
                successes + " of  " + rehashed + " files successfully rehashed with the new algorithm. " + new Date());
1843

1844
        return ok("Datafile rehashing complete." + successes + " of  " + rehashed + " files successfully rehashed.");
×
1845
    }
1846
        
1847
    @POST
1848
    @AuthRequired
1849
    @Path("/computeDataFileHashValue/{fileId}/algorithm/{alg}")
1850
    public Response computeDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId, @PathParam("alg") String alg) {
1851

1852
        try {
1853
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1854
            if (!u.isSuperuser()) {
×
1855
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1856
            }
1857
        } catch (WrappedResponse e1) {
×
1858
            return error(Status.UNAUTHORIZED, "api key required");
×
1859
        }
×
1860

1861
        DataFile fileToUpdate = null;
×
1862
        try {
1863
            fileToUpdate = findDataFileOrDie(fileId);
×
1864
        } catch (WrappedResponse r) {
×
1865
            logger.info("Could not find file with the id: " + fileId);
×
1866
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
1867
        }
×
1868

1869
        if (fileToUpdate.isHarvested()) {
×
1870
            return error(Status.BAD_REQUEST, "File with the id: " + fileId + " is harvested.");
×
1871
        }
1872

1873
        DataFile.ChecksumType cType = null;
×
1874
        try {
1875
            cType = DataFile.ChecksumType.fromString(alg);
×
1876
        } catch (IllegalArgumentException iae) {
×
1877
            return error(Status.BAD_REQUEST, "Unknown algorithm: " + alg);
×
1878
        }
×
1879

1880
        String newChecksum = "";
×
1881

1882
        InputStream in = null;
×
1883
        try {
1884

1885
            StorageIO<DataFile> storage = fileToUpdate.getStorageIO();
×
1886
            storage.open(DataAccessOption.READ_ACCESS);
×
1887
            if (!fileToUpdate.isTabularData()) {
×
1888
                in = storage.getInputStream();
×
1889
            } else {
1890
                in = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1891
            }
1892
            if (in == null) {
×
1893
                return error(Status.NOT_FOUND, "Could not retrieve file with the id: " + fileId);
×
1894
            }
1895
            newChecksum = FileUtil.calculateChecksum(in, cType);
×
1896
            fileToUpdate.setChecksumType(cType);
×
1897
            fileToUpdate.setChecksumValue(newChecksum);
×
1898

1899
        } catch (Exception e) {
×
1900
            logger.warning("Unexpected Exception: " + e.getMessage());
×
1901

1902
        } finally {
1903
            IOUtils.closeQuietly(in);
×
1904
        }
1905

1906
        return ok("Datafile rehashing complete. " + fileId + "  successfully rehashed. New hash value is: " + newChecksum);
×
1907
    }
1908
    
1909
    @POST
1910
    @AuthRequired
1911
    @Path("/validateDataFileHashValue/{fileId}")
1912
    public Response validateDataFileHashValue(@Context ContainerRequestContext crc, @PathParam("fileId") String fileId) {
1913

1914
        try {
1915
            User u = getRequestAuthenticatedUserOrDie(crc);
×
1916
            if (!u.isSuperuser()) {
×
1917
                return error(Status.UNAUTHORIZED, "must be superuser");
×
1918
            }
1919
        } catch (WrappedResponse e1) {
×
1920
            return error(Status.UNAUTHORIZED, "api key required");
×
1921
        }
×
1922

1923
        DataFile fileToValidate = null;
×
1924
        try {
1925
            fileToValidate = findDataFileOrDie(fileId);
×
1926
        } catch (WrappedResponse r) {
×
1927
            logger.info("Could not find file with the id: " + fileId);
×
1928
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
1929
        }
×
1930

1931
        if (fileToValidate.isHarvested()) {
×
1932
            return error(Status.BAD_REQUEST, "File with the id: " + fileId + " is harvested.");
×
1933
        }
1934

1935
        DataFile.ChecksumType cType = null;
×
1936
        try {
1937
            String checkSumTypeFromDataFile = fileToValidate.getChecksumType().toString();
×
1938
            cType = DataFile.ChecksumType.fromString(checkSumTypeFromDataFile);
×
1939
        } catch (IllegalArgumentException iae) {
×
1940
            return error(Status.BAD_REQUEST, "Unknown algorithm");
×
1941
        }
×
1942

1943
        String currentChecksum = fileToValidate.getChecksumValue();
×
1944
        String calculatedChecksum = "";
×
1945
        InputStream in = null;
×
1946
        try {
1947

1948
            StorageIO<DataFile> storage = fileToValidate.getStorageIO();
×
1949
            storage.open(DataAccessOption.READ_ACCESS);
×
1950
            if (!fileToValidate.isTabularData()) {
×
1951
                in = storage.getInputStream();
×
1952
            } else {
1953
                in = storage.getAuxFileAsInputStream(FileUtil.SAVED_ORIGINAL_FILENAME_EXTENSION);
×
1954
            }
1955
            if (in == null) {
×
1956
                return error(Status.NOT_FOUND, "Could not retrieve file with the id: " + fileId);
×
1957
            }
1958
            calculatedChecksum = FileUtil.calculateChecksum(in, cType);
×
1959

1960
        } catch (Exception e) {
×
1961
            logger.warning("Unexpected Exception: " + e.getMessage());
×
1962
            return error(Status.BAD_REQUEST, "Checksum Validation Unexpected Exception: " + e.getMessage());
×
1963
        } finally {
1964
            IOUtils.closeQuietly(in);
×
1965

1966
        }
1967

1968
        if (currentChecksum.equals(calculatedChecksum)) {
×
1969
            return ok("Datafile validation complete for " + fileId + ". The hash value is: " + calculatedChecksum);
×
1970
        } else {
1971
            return error(Status.EXPECTATION_FAILED, "Datafile validation failed for " + fileId + ". The saved hash value is: " + currentChecksum + " while the recalculated hash value for the stored file is: " + calculatedChecksum);
×
1972
        }
1973

1974
    }
1975

1976
    @POST
1977
    @AuthRequired
1978
    @Path("/submitDatasetVersionToArchive/{id}/{version}")
1979
    public Response submitDatasetVersionToArchive(@Context ContainerRequestContext crc, @PathParam("id") String dsid,
1980
            @PathParam("version") String versionNumber) {
1981

1982
        try {
1983
            AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc);
×
1984

1985
            Dataset ds = findDatasetOrDie(dsid);
×
1986

1987
            DatasetVersion dv = datasetversionService.findByFriendlyVersionNumber(ds.getId(), versionNumber);
×
1988
            if(dv==null) {
×
1989
                return error(Status.BAD_REQUEST, "Requested version not found.");
×
1990
            }
1991
            if (dv.getArchivalCopyLocation() == null) {
×
1992
                String className = settingsService.getValueForKey(SettingsServiceBean.Key.ArchiverClassName);
×
1993
                // Note - the user is being sent via the createDataverseRequest(au) call to the
1994
                // back-end command where it is used to get the API Token which is
1995
                // then used to retrieve files (e.g. via S3 direct downloads) to create the Bag
1996
                AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className,
×
1997
                        createDataverseRequest(au), dv);
×
1998
                // createSubmitToArchiveCommand() tries to find and instantiate an non-abstract
1999
                // implementation of AbstractSubmitToArchiveCommand based on the provided
2000
                // className. If a class with that name isn't found (or can't be instatiated), it
2001
                // will return null
2002
                if (cmd != null) {
×
2003
                    if(ArchiverUtil.onlySingleVersionArchiving(cmd.getClass(), settingsService)) {
×
2004
                        for (DatasetVersion version : ds.getVersions()) {
×
2005
                            if ((dv != version) && version.getArchivalCopyLocation() != null) {
×
2006
                                return error(Status.CONFLICT, "Dataset already archived.");
×
2007
                            }
2008
                        } 
×
2009
                    }
2010
                    new Thread(new Runnable() {
×
2011
                        public void run() {
2012
                            try {
2013
                                DatasetVersion dv = commandEngine.submit(cmd);
×
2014
                                if (!dv.getArchivalCopyLocationStatus().equals(DatasetVersion.ARCHIVAL_STATUS_FAILURE)) {
×
2015
                                    logger.info(
×
2016
                                            "DatasetVersion id=" + ds.getGlobalId().toString() + " v" + versionNumber
×
2017
                                                    + " submitted to Archive, status: " + dv.getArchivalCopyLocationStatus());
×
2018
                                } else {
2019
                                    logger.severe("Error submitting version due to conflict/error at Archive");
×
2020
                                }
2021
                            } catch (CommandException ex) {
×
2022
                                logger.log(Level.SEVERE, "Unexpected Exception calling  submit archive command", ex);
×
2023
                            }
×
2024
                        }
×
2025
                    }).start();
×
2026
                    return ok("Archive submission using " + cmd.getClass().getCanonicalName()
×
2027
                            + " started. Processing can take significant time for large datasets and requires that the user have permission to publish the dataset. View log and/or check archive for results.");
2028
                } else {
2029
                    logger.log(Level.SEVERE, "Could not find Archiver class: " + className);
×
2030
                    return error(Status.INTERNAL_SERVER_ERROR, "Could not find Archiver class: " + className);
×
2031
                }
2032
            } else {
2033
                return error(Status.BAD_REQUEST, "Version was already submitted for archiving.");
×
2034
            }
2035
        } catch (WrappedResponse e1) {
×
2036
            return e1.getResponse();
×
2037
        }
2038
    }
2039

2040
    
2041
    /**
2042
     * Iteratively archives all unarchived dataset versions
2043
     * @param
2044
     * listonly - don't archive, just list unarchived versions
2045
     * limit - max number to process
2046
     * lastestonly - only archive the latest versions
2047
     * @return
2048
     */
2049
    @POST
2050
    @AuthRequired
2051
    @Path("/archiveAllUnarchivedDatasetVersions")
2052
    public Response archiveAllUnarchivedDatasetVersions(@Context ContainerRequestContext crc, @QueryParam("listonly") boolean listonly, @QueryParam("limit") Integer limit, @QueryParam("latestonly") boolean latestonly) {
2053

2054
        try {
2055
            AuthenticatedUser au = getRequestAuthenticatedUserOrDie(crc);
×
2056

2057
            List<DatasetVersion> dsl = datasetversionService.getUnarchivedDatasetVersions();
×
2058
            if (dsl != null) {
×
2059
                if (listonly) {
×
2060
                    JsonArrayBuilder jab = Json.createArrayBuilder();
×
2061
                    logger.fine("Unarchived versions found: ");
×
2062
                    int current = 0;
×
2063
                    for (DatasetVersion dv : dsl) {
×
2064
                        if (limit != null && current >= limit) {
×
2065
                            break;
×
2066
                        }
2067
                        if (!latestonly || dv.equals(dv.getDataset().getLatestVersionForCopy())) {
×
2068
                            jab.add(dv.getDataset().getGlobalId().toString() + ", v" + dv.getFriendlyVersionNumber());
×
2069
                            logger.fine("    " + dv.getDataset().getGlobalId().toString() + ", v" + dv.getFriendlyVersionNumber());
×
2070
                            current++;
×
2071
                        }
2072
                    }
×
2073
                    return ok(jab); 
×
2074
                }
2075
                String className = settingsService.getValueForKey(SettingsServiceBean.Key.ArchiverClassName);
×
2076
                // Note - the user is being sent via the createDataverseRequest(au) call to the
2077
                // back-end command where it is used to get the API Token which is
2078
                // then used to retrieve files (e.g. via S3 direct downloads) to create the Bag
2079
                final DataverseRequest request = createDataverseRequest(au);
×
2080
                // createSubmitToArchiveCommand() tries to find and instantiate an non-abstract
2081
                // implementation of AbstractSubmitToArchiveCommand based on the provided
2082
                // className. If a class with that name isn't found (or can't be instatiated, it
2083
                // will return null
2084
                AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className, request, dsl.get(0));
×
2085
                if (cmd != null) {
×
2086
                    //Found an archiver to use
2087
                    new Thread(new Runnable() {
×
2088
                        public void run() {
2089
                            int total = dsl.size();
×
2090
                            int successes = 0;
×
2091
                            int failures = 0;
×
2092
                            for (DatasetVersion dv : dsl) {
×
2093
                                if (limit != null && (successes + failures) >= limit) {
×
2094
                                    break;
×
2095
                                }
2096
                                if (!latestonly || dv.equals(dv.getDataset().getLatestVersionForCopy())) {
×
2097
                                    try {
2098
                                        AbstractSubmitToArchiveCommand cmd = ArchiverUtil.createSubmitToArchiveCommand(className, request, dv);
×
2099

2100
                                        dv = commandEngine.submit(cmd);
×
2101
                                        if (!dv.getArchivalCopyLocationStatus().equals(DatasetVersion.ARCHIVAL_STATUS_FAILURE)) {
×
2102
                                            successes++;
×
2103
                                            logger.info("DatasetVersion id=" + dv.getDataset().getGlobalId().toString() + " v" + dv.getFriendlyVersionNumber() + " submitted to Archive, status: "
×
2104
                                                    + dv.getArchivalCopyLocationStatus());
×
2105
                                        } else {
2106
                                            failures++;
×
2107
                                            logger.severe("Error submitting version due to conflict/error at Archive for " + dv.getDataset().getGlobalId().toString() + " v" + dv.getFriendlyVersionNumber());
×
2108
                                        }
2109
                                    } catch (CommandException ex) {
×
2110
                                        failures++;
×
2111
                                        logger.log(Level.SEVERE, "Unexpected Exception calling  submit archive command", ex);
×
2112
                                    }
×
2113
                                }
2114
                                logger.fine(successes + failures + " of " + total + " archive submissions complete");
×
2115
                            }
×
2116
                            logger.info("Archiving complete: " + successes + " Successes, " + failures + " Failures. See prior log messages for details.");
×
2117
                        }
×
2118
                    }).start();
×
2119
                    return ok("Starting to archive all unarchived published dataset versions using " + cmd.getClass().getCanonicalName() + ". Processing can take significant time for large datasets/ large numbers of dataset versions  and requires that the user have permission to publish the dataset(s). View log and/or check archive for results.");
×
2120
                } else {
2121
                    logger.log(Level.SEVERE, "Could not find Archiver class: " + className);
×
2122
                    return error(Status.INTERNAL_SERVER_ERROR, "Could not find Archiver class: " + className);
×
2123
                }
2124
            } else {
2125
                return error(Status.BAD_REQUEST, "No unarchived published dataset versions found");
×
2126
            }
2127
        } catch (WrappedResponse e1) {
×
2128
            return e1.getResponse();
×
2129
        }
2130
    }
2131
    
2132
    @DELETE
2133
    @Path("/clearMetricsCache")
2134
    public Response clearMetricsCache() {
2135
        em.createNativeQuery("DELETE FROM metric").executeUpdate();
×
2136
        return ok("all metric caches cleared.");
×
2137
    }
2138

2139
    @DELETE
2140
    @Path("/clearMetricsCache/{name}")
2141
    public Response clearMetricsCacheByName(@PathParam("name") String name) {
2142
        Query deleteQuery = em.createNativeQuery("DELETE FROM metric where name = ?");
×
2143
        deleteQuery.setParameter(1, name);
×
2144
        deleteQuery.executeUpdate();
×
2145
        return ok("metric cache " + name + " cleared.");
×
2146
    }
2147

2148
    @GET
2149
    @AuthRequired
2150
    @Path("/dataverse/{alias}/addRoleAssignmentsToChildren")
2151
    public Response addRoleAssignementsToChildren(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2152
        Dataverse owner = dataverseSvc.findByAlias(alias);
×
2153
        if (owner == null) {
×
2154
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2155
        }
2156
        try {
2157
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2158
            if (!user.isSuperuser()) {
×
2159
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2160
            }
2161
        } catch (WrappedResponse wr) {
×
2162
            return wr.getResponse();
×
2163
        }
×
2164
        boolean inheritAllRoles = false;
×
2165
        String rolesString = settingsSvc.getValueForKey(SettingsServiceBean.Key.InheritParentRoleAssignments, "");
×
2166
        if (rolesString.length() > 0) {
×
2167
            ArrayList<String> rolesToInherit = new ArrayList<String>(Arrays.asList(rolesString.split("\\s*,\\s*")));
×
2168
            if (!rolesToInherit.isEmpty()) {
×
2169
                if (rolesToInherit.contains("*")) {
×
2170
                    inheritAllRoles = true;
×
2171
                }
2172
                return ok(dataverseSvc.addRoleAssignmentsToChildren(owner, rolesToInherit, inheritAllRoles));
×
2173
            }
2174
        }
2175
        return error(Response.Status.BAD_REQUEST,
×
2176
                "InheritParentRoleAssignments does not list any roles on this instance");
2177
    }
2178
    
2179
    @GET
2180
    @AuthRequired
2181
    @Path("/dataverse/{alias}/storageDriver")
2182
    public Response getStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2183
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2184
        if (dataverse == null) {
×
2185
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2186
        }
2187
        try {
2188
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2189
            if (!user.isSuperuser()) {
×
2190
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2191
            }
2192
        } catch (WrappedResponse wr) {
×
2193
            return wr.getResponse();
×
2194
        }
×
2195
        //Note that this returns what's set directly on this dataverse. If null/DataAccess.UNDEFINED_STORAGE_DRIVER_IDENTIFIER, the user would have to recurse the chain of parents to find the effective storageDriver
2196
        return ok(dataverse.getStorageDriverId());
×
2197
    }
2198
    
2199
    @PUT
2200
    @AuthRequired
2201
    @Path("/dataverse/{alias}/storageDriver")
2202
    public Response setStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias, String label) throws WrappedResponse {
2203
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2204
        if (dataverse == null) {
×
2205
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2206
        }
2207
        try {
2208
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2209
            if (!user.isSuperuser()) {
×
2210
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2211
            }
2212
        } catch (WrappedResponse wr) {
×
2213
            return wr.getResponse();
×
2214
        }
×
2215
        for (Entry<String, String> store: DataAccess.getStorageDriverLabels().entrySet()) {
×
2216
            if(store.getKey().equals(label)) {
×
2217
                dataverse.setStorageDriverId(store.getValue());
×
2218
                return ok("Storage set to: " + store.getKey() + "/" + store.getValue());
×
2219
            }
2220
        }
×
2221
        return error(Response.Status.BAD_REQUEST,
×
2222
                "No Storage Driver found for : " + label);
2223
    }
2224

2225
    @DELETE
2226
    @AuthRequired
2227
    @Path("/dataverse/{alias}/storageDriver")
2228
    public Response resetStorageDriver(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2229
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2230
        if (dataverse == null) {
×
2231
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2232
        }
2233
        try {
2234
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2235
            if (!user.isSuperuser()) {
×
2236
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2237
            }
2238
        } catch (WrappedResponse wr) {
×
2239
            return wr.getResponse();
×
2240
        }
×
2241
        dataverse.setStorageDriverId("");
×
2242
        return ok("Storage reset to default: " + DataAccess.DEFAULT_STORAGE_DRIVER_IDENTIFIER);
×
2243
    }
2244
    
2245
    @GET
2246
    @AuthRequired
2247
    @Path("/dataverse/storageDrivers")
2248
    public Response listStorageDrivers(@Context ContainerRequestContext crc) throws WrappedResponse {
2249
        try {
2250
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2251
            if (!user.isSuperuser()) {
×
2252
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2253
            }
2254
        } catch (WrappedResponse wr) {
×
2255
            return wr.getResponse();
×
2256
        }
×
2257
        JsonObjectBuilder bld = jsonObjectBuilder();
×
2258
        DataAccess.getStorageDriverLabels().entrySet().forEach(s -> bld.add(s.getKey(), s.getValue()));
×
2259
        return ok(bld);
×
2260
    }
2261
    
2262
    @GET
2263
    @AuthRequired
2264
    @Path("/dataverse/{alias}/curationLabelSet")
2265
    public Response getCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2266
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2267
        if (dataverse == null) {
×
2268
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2269
        }
2270
        try {
2271
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2272
            if (!user.isSuperuser()) {
×
2273
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2274
            }
2275
        } catch (WrappedResponse wr) {
×
2276
            return wr.getResponse();
×
2277
        }
×
2278
        // Note that this returns what's set directly on this dataverse. If
2279
        // null/SystemConfig.DEFAULTCURATIONLABELSET, the user would have to recurse the
2280
        // chain of parents to find the effective curationLabelSet
2281
        return ok(dataverse.getCurationLabelSetName());
×
2282
    }
2283

2284
    @PUT
2285
    @AuthRequired
2286
    @Path("/dataverse/{alias}/curationLabelSet")
2287
    public Response setCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias, @QueryParam("name") String name) throws WrappedResponse {
2288
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2289
        if (dataverse == null) {
×
2290
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2291
        }
2292
        try {
2293
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2294
            if (!user.isSuperuser()) {
×
2295
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2296
            }
2297
        } catch (WrappedResponse wr) {
×
2298
            return wr.getResponse();
×
2299
        }
×
2300
        if (SystemConfig.CURATIONLABELSDISABLED.equals(name) || SystemConfig.DEFAULTCURATIONLABELSET.equals(name)) {
×
2301
            dataverse.setCurationLabelSetName(name);
×
2302
            return ok("Curation Label Set Name set to: " + name);
×
2303
        } else {
2304
            for (String setName : systemConfig.getCurationLabels().keySet()) {
×
2305
                if (setName.equals(name)) {
×
2306
                    dataverse.setCurationLabelSetName(name);
×
2307
                    return ok("Curation Label Set Name set to: " + setName);
×
2308
                }
2309
            }
×
2310
        }
2311
        return error(Response.Status.BAD_REQUEST,
×
2312
                "No Curation Label Set found for : " + name);
2313
    }
2314

2315
    @DELETE
2316
    @AuthRequired
2317
    @Path("/dataverse/{alias}/curationLabelSet")
2318
    public Response resetCurationLabelSet(@Context ContainerRequestContext crc, @PathParam("alias") String alias) throws WrappedResponse {
2319
        Dataverse dataverse = dataverseSvc.findByAlias(alias);
×
2320
        if (dataverse == null) {
×
2321
            return error(Response.Status.NOT_FOUND, "Could not find dataverse based on alias supplied: " + alias + ".");
×
2322
        }
2323
        try {
2324
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2325
            if (!user.isSuperuser()) {
×
2326
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2327
            }
2328
        } catch (WrappedResponse wr) {
×
2329
            return wr.getResponse();
×
2330
        }
×
2331
        dataverse.setCurationLabelSetName(SystemConfig.DEFAULTCURATIONLABELSET);
×
2332
        return ok("Curation Label Set reset to default: " + SystemConfig.DEFAULTCURATIONLABELSET);
×
2333
    }
2334

2335
    @GET
2336
    @AuthRequired
2337
    @Path("/dataverse/curationLabelSets")
2338
    public Response listCurationLabelSets(@Context ContainerRequestContext crc) throws WrappedResponse {
2339
        try {
2340
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2341
            if (!user.isSuperuser()) {
×
2342
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2343
            }
2344
        } catch (WrappedResponse wr) {
×
2345
            return wr.getResponse();
×
2346
        }
×
2347
        JsonObjectBuilder bld = Json.createObjectBuilder();
×
2348

2349
        systemConfig.getCurationLabels().entrySet().forEach(s -> {
×
2350
            JsonArrayBuilder labels = Json.createArrayBuilder();
×
2351
            Arrays.asList(s.getValue()).forEach(l -> labels.add(l));
×
2352
            bld.add(s.getKey(), labels);
×
2353
        });
×
2354
        return ok(bld);
×
2355
    }
2356
    
2357
    @POST
2358
    @Path("/bannerMessage")
2359
    public Response addBannerMessage(JsonObject jsonObject) throws WrappedResponse {
2360

2361
        BannerMessage toAdd = new BannerMessage();
×
2362
        try {
2363

2364
            String dismissible = jsonObject.getString("dismissibleByUser");
×
2365

2366
            boolean dismissibleByUser = false;
×
2367
            if (dismissible.equals("true")) {
×
2368
                dismissibleByUser = true;
×
2369
            }
2370
            toAdd.setDismissibleByUser(dismissibleByUser);
×
2371
            toAdd.setBannerMessageTexts(new ArrayList());
×
2372
            toAdd.setActive(true);
×
2373
            JsonArray jsonArray = jsonObject.getJsonArray("messageTexts");
×
2374
            for (int i = 0; i < jsonArray.size(); i++) {
×
2375
                JsonObject obj = (JsonObject) jsonArray.get(i);
×
2376
                String message = obj.getString("message");
×
2377
                String lang = obj.getString("lang");
×
2378
                BannerMessageText messageText = new BannerMessageText();
×
2379
                messageText.setMessage(message);
×
2380
                messageText.setLang(lang);
×
2381
                messageText.setBannerMessage(toAdd);
×
2382
                toAdd.getBannerMessageTexts().add(messageText);
×
2383
            }
2384
            bannerMessageService.save(toAdd);
×
2385

2386
            JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder()
×
2387
                .add("message", "Banner Message added successfully.")
×
2388
                .add("id", toAdd.getId());
×
2389

2390
            return ok(jsonObjectBuilder);
×
2391

2392
        } catch (Exception e) {
×
2393
            logger.warning("Unexpected Exception: " + e.getMessage());
×
2394
            return error(Status.BAD_REQUEST, "Add Banner Message unexpected exception: invalid or missing JSON object.");
×
2395
        }
2396

2397
    }
2398
    
2399
    @DELETE
2400
    @Path("/bannerMessage/{id}")
2401
    public Response deleteBannerMessage(@PathParam("id") Long id) throws WrappedResponse {
2402
 
2403
        BannerMessage message = em.find(BannerMessage.class, id);
×
2404
        if (message == null){
×
2405
            return error(Response.Status.NOT_FOUND, "Message id = "  + id + " not found.");
×
2406
        }
2407
        bannerMessageService.deleteBannerMessage(id);
×
2408
        
2409
        return ok("Message id =  " + id + " deleted.");
×
2410

2411
    }
2412
    
2413
    @PUT
2414
    @Path("/bannerMessage/{id}/deactivate")
2415
    public Response deactivateBannerMessage(@PathParam("id") Long id) throws WrappedResponse {
2416
        BannerMessage message = em.find(BannerMessage.class, id);
×
2417
        if (message == null){
×
2418
            return error(Response.Status.NOT_FOUND, "Message id = "  + id + " not found.");
×
2419
        }
2420
        bannerMessageService.deactivateBannerMessage(id);
×
2421
        
2422
        return ok("Message id =  " + id + " deactivated.");
×
2423

2424
    }
2425
    
2426
    @GET
2427
    @Path("/bannerMessage")
2428
    public Response getBannerMessages(@PathParam("id") Long id) throws WrappedResponse {
2429

2430
        List<BannerMessage> messagesList = bannerMessageService.findAllBannerMessages();
×
2431

2432
        for (BannerMessage message : messagesList) {
×
2433
            if ("".equals(message.getDisplayValue())) {
×
2434
               return error(Response.Status.INTERNAL_SERVER_ERROR, "No banner messages found for this locale.");
×
2435
            }
2436
        }
×
2437

2438
        JsonArrayBuilder messages = messagesList.stream()
×
2439
        .map(m -> jsonObjectBuilder().add("id", m.getId()).add("displayValue", m.getDisplayValue()))
×
2440
        .collect(toJsonArray());
×
2441
        
2442
        return ok(messages);
×
2443
    }
2444
    
2445
    @POST
2446
    @AuthRequired
2447
    @Consumes("application/json")
2448
    @Path("/requestSignedUrl")
2449
    public Response getSignedUrl(@Context ContainerRequestContext crc, JsonObject urlInfo) {
2450
        AuthenticatedUser superuser = null;
×
2451
        try {
2452
            superuser = getRequestAuthenticatedUserOrDie(crc);
×
2453
        } catch (WrappedResponse wr) {
×
2454
            return wr.getResponse();
×
2455
        }
×
2456
        if (superuser == null || !superuser.isSuperuser()) {
×
2457
            return error(Response.Status.FORBIDDEN, "Requesting signed URLs is restricted to superusers.");
×
2458
        }
2459
        
2460
        String userId = urlInfo.getString("user");
×
2461
        String key=null;
×
2462
        if (userId != null) {
×
2463
            AuthenticatedUser user = authSvc.getAuthenticatedUser(userId);
×
2464
            // If a user param was sent, we sign the URL for them, otherwise on behalf of
2465
            // the superuser who made this api call
2466
            if (user != null) {
×
2467
                ApiToken apiToken = authSvc.findApiTokenByUser(user);
×
2468
                if (apiToken != null && !apiToken.isExpired() && !apiToken.isDisabled()) {
×
2469
                    key = apiToken.getTokenString();
×
2470
                }
2471
            } else {
×
2472
                userId = superuser.getUserIdentifier();
×
2473
                // We ~know this exists - the superuser just used it and it was unexpired/not
2474
                // disabled. (ToDo - if we want this to work with workflow tokens (or as a
2475
                // signed URL), we should do more checking as for the user above))
2476
                key = authSvc.findApiTokenByUser(superuser).getTokenString();
×
2477
            }
2478
            if (key == null) {
×
2479
                return error(Response.Status.CONFLICT, "Do not have a valid user with apiToken");
×
2480
            }
2481
            key = JvmSettings.API_SIGNING_SECRET.lookupOptional().orElse("") + key;
×
2482
        }
2483
        
2484
        String baseUrl = urlInfo.getString("url");
×
2485
        int timeout = urlInfo.getInt(URLTokenUtil.TIMEOUT, 10);
×
2486
        String method = urlInfo.getString(URLTokenUtil.HTTP_METHOD, "GET");
×
2487
        
2488
        String signedUrl = UrlSignerUtil.signUrl(baseUrl, timeout, userId, method, key); 
×
2489
        
2490
        return ok(Json.createObjectBuilder().add(URLTokenUtil.SIGNED_URL, signedUrl));
×
2491
    }
2492
 
2493
    @DELETE
2494
    @Path("/clearThumbnailFailureFlag")
2495
    public Response clearThumbnailFailureFlag() {
2496
        em.createNativeQuery("UPDATE dvobject SET previewimagefail = FALSE").executeUpdate();
×
2497
        return ok("Thumbnail Failure Flags cleared.");
×
2498
    }
2499
    
2500
    @DELETE
2501
    @Path("/clearThumbnailFailureFlag/{id}")
2502
    public Response clearThumbnailFailureFlagByDatafile(@PathParam("id") String fileId) {
2503
        try {
2504
            DataFile df = findDataFileOrDie(fileId);
×
2505
            Query deleteQuery = em.createNativeQuery("UPDATE dvobject SET previewimagefail = FALSE where id = ?");
×
2506
            deleteQuery.setParameter(1, df.getId());
×
2507
            deleteQuery.executeUpdate();
×
2508
            return ok("Thumbnail Failure Flag cleared for file id=: " + df.getId() + ".");
×
2509
        } catch (WrappedResponse r) {
×
2510
            logger.info("Could not find file with the id: " + fileId);
×
2511
            return error(Status.BAD_REQUEST, "Could not find file with the id: " + fileId);
×
2512
        }
2513
    }
2514

2515
    /**
2516
     * For testing only. Download a file from /tmp.
2517
     */
2518
    @GET
2519
    @AuthRequired
2520
    @Path("/downloadTmpFile")
2521
    public Response downloadTmpFile(@Context ContainerRequestContext crc, @QueryParam("fullyQualifiedPathToFile") String fullyQualifiedPathToFile) {
2522
        try {
2523
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2524
            if (!user.isSuperuser()) {
×
2525
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2526
            }
2527
        } catch (WrappedResponse wr) {
×
2528
            return wr.getResponse();
×
2529
        }
×
2530
        java.nio.file.Path normalizedPath = Paths.get(fullyQualifiedPathToFile).normalize();
×
2531
        if (!normalizedPath.toString().startsWith("/tmp")) {
×
2532
            return error(Status.BAD_REQUEST, "Path must begin with '/tmp' but after normalization was '" + normalizedPath +"'.");
×
2533
        }
2534
        try {
2535
            return ok(new FileInputStream(fullyQualifiedPathToFile));
×
2536
        } catch (IOException ex) {
×
2537
            return error(Status.BAD_REQUEST, ex.toString());
×
2538
        }
2539
    }
2540

2541
    @GET
2542
    @Path("/featureFlags")
2543
    public Response getFeatureFlags() {
2544
        Map<String, String> map = new TreeMap<>();
×
2545
        for (FeatureFlags flag : FeatureFlags.values()) {
×
2546
            map.put(flag.name(), flag.enabled() ? "enabled" : "disabled");
×
2547
        }
2548
        return ok(Json.createObjectBuilder(map));
×
2549
    }
2550

2551
    @GET
2552
    @Path("/featureFlags/{flag}")
2553
    public Response getFeatureFlag(@PathParam("flag") String flagIn) {
2554
        try {
2555
            FeatureFlags flag = FeatureFlags.valueOf(flagIn);
×
2556
            JsonObjectBuilder job = Json.createObjectBuilder();
×
2557
            job.add("enabled", flag.enabled());
×
2558
            return ok(job);
×
2559
        } catch (IllegalArgumentException ex) {
×
2560
            return error(Status.NOT_FOUND, "Feature flag not found. Try listing all feature flags.");
×
2561
        }
2562
    }
2563

2564
    @GET
2565
    @AuthRequired
2566
    @Path("/datafiles/auditFiles")
2567
    public Response getAuditFiles(@Context ContainerRequestContext crc,
2568
                                  @QueryParam("firstId") Long firstId, @QueryParam("lastId") Long lastId,
2569
                                  @QueryParam("datasetIdentifierList") String datasetIdentifierList) throws WrappedResponse {
2570
        try {
2571
            AuthenticatedUser user = getRequestAuthenticatedUserOrDie(crc);
×
2572
            if (!user.isSuperuser()) {
×
2573
                return error(Response.Status.FORBIDDEN, "Superusers only.");
×
2574
            }
2575
        } catch (WrappedResponse wr) {
×
2576
            return wr.getResponse();
×
2577
        }
×
2578

2579
        int datasetsChecked = 0;
×
2580
        long startId = (firstId == null ? 0 : firstId);
×
2581
        long endId = (lastId == null ? Long.MAX_VALUE : lastId);
×
2582

2583
        List<String> datasetIdentifiers;
2584
        if (datasetIdentifierList == null || datasetIdentifierList.isEmpty()) {
×
2585
            datasetIdentifiers = Collections.emptyList();
×
2586
        } else {
2587
            startId = 0;
×
2588
            endId = Long.MAX_VALUE;
×
2589
            datasetIdentifiers = List.of(datasetIdentifierList.split(","));
×
2590
        }
2591
        if (endId < startId) {
×
2592
            return badRequest("Invalid Parameters: lastId must be equal to or greater than firstId");
×
2593
        }
2594

2595
        NullSafeJsonBuilder jsonObjectBuilder = NullSafeJsonBuilder.jsonObjectBuilder();
×
2596
        JsonArrayBuilder jsonDatasetsArrayBuilder = Json.createArrayBuilder();
×
2597
        JsonArrayBuilder jsonFailuresArrayBuilder = Json.createArrayBuilder();
×
2598

2599
        if (startId > 0) {
×
2600
            jsonObjectBuilder.add("firstId", startId);
×
2601
        }
2602
        if (endId < Long.MAX_VALUE) {
×
2603
            jsonObjectBuilder.add("lastId", endId);
×
2604
        }
2605

2606
        // compile the list of ids to process
2607
        List<Long> datasetIds;
2608
        if (datasetIdentifiers.isEmpty()) {
×
2609
            datasetIds = datasetService.findAllLocalDatasetIds();
×
2610
        } else {
2611
            datasetIds = new ArrayList<>(datasetIdentifiers.size());
×
2612
            JsonArrayBuilder jab = Json.createArrayBuilder();
×
2613
            datasetIdentifiers.forEach(id -> {
×
2614
                String dId = id.trim();
×
2615
                jab.add(dId);
×
2616
                Dataset d = datasetService.findByGlobalId(dId);
×
2617
                if (d != null) {
×
2618
                    datasetIds.add(d.getId());
×
2619
                } else {
2620
                    NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
×
2621
                    job.add("datasetIdentifier",dId);
×
2622
                    job.add("reason","Not Found");
×
2623
                    jsonFailuresArrayBuilder.add(job);
×
2624
                }
2625
            });
×
2626
            jsonObjectBuilder.add("datasetIdentifierList", jab);
×
2627
        }
2628

2629
        for (Long datasetId : datasetIds) {
×
2630
            if (datasetId < startId) {
×
2631
                continue;
×
2632
            } else if (datasetId > endId) {
×
2633
                break;
×
2634
            }
2635
            Dataset dataset;
2636
            try {
2637
                dataset = findDatasetOrDie(String.valueOf(datasetId));
×
2638
                datasetsChecked++;
×
2639
            } catch (WrappedResponse e) {
×
2640
                NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
×
2641
                job.add("datasetId", datasetId);
×
2642
                job.add("reason", e.getMessage());
×
2643
                jsonFailuresArrayBuilder.add(job);
×
2644
                continue;
×
2645
            }
×
2646

2647
            List<String> missingFiles = new ArrayList<>();
×
2648
            List<String> missingFileMetadata = new ArrayList<>();
×
2649
            try {
2650
                Predicate<String> filter = s -> true;
×
2651
                StorageIO<DvObject> datasetIO = DataAccess.getStorageIO(dataset);
×
2652
                final List<String> result = datasetIO.cleanUp(filter, true);
×
2653
                // add files that are in dataset files but not in cleanup result or DataFiles with missing FileMetadata
2654
                dataset.getFiles().forEach(df -> {
×
2655
                    try {
2656
                        StorageIO<DataFile> datafileIO = df.getStorageIO();
×
2657
                        String storageId = df.getStorageIdentifier();
×
2658
                        FileMetadata fm = df.getFileMetadata();
×
2659
                        if (!datafileIO.exists()) {
×
2660
                            missingFiles.add(storageId + "," + (fm != null ?
×
2661
                                    (fm.getDirectoryLabel() != null || !fm.getDirectoryLabel().isEmpty() ? "directoryLabel,"+fm.getDirectoryLabel()+"," : "")
×
2662
                                            +"label,"+fm.getLabel() : "type,"+df.getContentType()));
×
2663
                        }
2664
                        if (fm == null) {
×
2665
                            missingFileMetadata.add(storageId + ",dataFileId," + df.getId());
×
2666
                        }
2667
                    } catch (IOException e) {
×
2668
                        NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
×
2669
                        job.add("dataFileId", df.getId());
×
2670
                        job.add("reason", e.getMessage());
×
2671
                        jsonFailuresArrayBuilder.add(job);
×
2672
                    }
×
2673
                });
×
2674
            } catch (IOException e) {
×
2675
                NullSafeJsonBuilder job = NullSafeJsonBuilder.jsonObjectBuilder();
×
2676
                job.add("datasetId", datasetId);
×
2677
                job.add("reason", e.getMessage());
×
2678
                jsonFailuresArrayBuilder.add(job);
×
2679
            }
×
2680

2681
            JsonObjectBuilder job = Json.createObjectBuilder();
×
2682
            if (!missingFiles.isEmpty() || !missingFileMetadata.isEmpty()) {
×
2683
                job.add("id", dataset.getId());
×
2684
                job.add("pid", dataset.getProtocol() + ":" + dataset.getAuthority() + "/" + dataset.getIdentifier());
×
2685
                job.add("persistentURL", dataset.getPersistentURL());
×
2686
                if (!missingFileMetadata.isEmpty()) {
×
2687
                    JsonArrayBuilder jabMissingFileMetadata = Json.createArrayBuilder();
×
2688
                    missingFileMetadata.forEach(mm -> {
×
2689
                        String[] missingMetadata = mm.split(",");
×
2690
                        NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
×
2691
                                .add("storageIdentifier", missingMetadata[0])
×
2692
                                .add(missingMetadata[1], missingMetadata[2]);
×
2693
                        jabMissingFileMetadata.add(jobj);
×
2694
                    });
×
2695
                    job.add("missingFileMetadata", jabMissingFileMetadata);
×
2696
                }
2697
                if (!missingFiles.isEmpty()) {
×
2698
                    JsonArrayBuilder jabMissingFiles = Json.createArrayBuilder();
×
2699
                    missingFiles.forEach(mf -> {
×
2700
                        String[] missingFile = mf.split(",");
×
2701
                        NullSafeJsonBuilder jobj = NullSafeJsonBuilder.jsonObjectBuilder()
×
2702
                                .add("storageIdentifier", missingFile[0]);
×
2703
                        for (int i = 2; i < missingFile.length; i+=2) {
×
2704
                            jobj.add(missingFile[i-1], missingFile[i]);
×
2705
                        }
2706
                        jabMissingFiles.add(jobj);
×
2707
                    });
×
2708
                    job.add("missingFiles", jabMissingFiles);
×
2709
                }
2710
                jsonDatasetsArrayBuilder.add(job);
×
2711
            }
2712
        }
×
2713

2714
        jsonObjectBuilder.add("datasetsChecked", datasetsChecked);
×
2715
        jsonObjectBuilder.add("datasets", jsonDatasetsArrayBuilder);
×
2716
        jsonObjectBuilder.add("failures", jsonFailuresArrayBuilder);
×
2717

2718
        return ok(jsonObjectBuilder);
×
2719
    }
2720
}
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