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

IQSS / dataverse / #22693

03 Jul 2024 01:09PM CUT coverage: 20.626% (-0.09%) from 20.716%
#22693

push

github

web-flow
Merge pull request #10664 from IQSS/develop

merge develop into master for 6.3

195 of 1852 new or added lines in 82 files covered. (10.53%)

72 existing lines in 33 files now uncovered.

17335 of 84043 relevant lines covered (20.63%)

0.21 hits per line

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

13.03
/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java
1
package edu.harvard.iq.dataverse.api;
2

3
import edu.harvard.iq.dataverse.*;
4
import edu.harvard.iq.dataverse.actionlogging.ActionLogServiceBean;
5
import static edu.harvard.iq.dataverse.api.Datasets.handleVersion;
6
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
7
import edu.harvard.iq.dataverse.authorization.DataverseRole;
8
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
9
import edu.harvard.iq.dataverse.authorization.groups.GroupServiceBean;
10
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
11
import edu.harvard.iq.dataverse.authorization.users.User;
12
import edu.harvard.iq.dataverse.confirmemail.ConfirmEmailServiceBean;
13
import edu.harvard.iq.dataverse.datacapturemodule.DataCaptureModuleServiceBean;
14
import edu.harvard.iq.dataverse.engine.command.Command;
15
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
16
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
17
import edu.harvard.iq.dataverse.engine.command.exception.IllegalCommandException;
18
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;
19
import edu.harvard.iq.dataverse.engine.command.impl.GetDraftDatasetVersionCommand;
20
import edu.harvard.iq.dataverse.engine.command.impl.GetLatestAccessibleDatasetVersionCommand;
21
import edu.harvard.iq.dataverse.engine.command.impl.GetLatestPublishedDatasetVersionCommand;
22
import edu.harvard.iq.dataverse.engine.command.impl.GetSpecificPublishedDatasetVersionCommand;
23
import edu.harvard.iq.dataverse.engine.command.exception.RateLimitCommandException;
24
import edu.harvard.iq.dataverse.externaltools.ExternalToolServiceBean;
25
import edu.harvard.iq.dataverse.license.LicenseServiceBean;
26
import edu.harvard.iq.dataverse.pidproviders.PidUtil;
27
import edu.harvard.iq.dataverse.locality.StorageSiteServiceBean;
28
import edu.harvard.iq.dataverse.metrics.MetricsServiceBean;
29
import edu.harvard.iq.dataverse.search.savedsearch.SavedSearchServiceBean;
30
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
31
import edu.harvard.iq.dataverse.util.BundleUtil;
32
import edu.harvard.iq.dataverse.util.FileUtil;
33
import edu.harvard.iq.dataverse.util.SystemConfig;
34
import edu.harvard.iq.dataverse.util.json.JsonParser;
35
import edu.harvard.iq.dataverse.util.json.JsonUtil;
36
import edu.harvard.iq.dataverse.util.json.NullSafeJsonBuilder;
37
import edu.harvard.iq.dataverse.validation.PasswordValidatorServiceBean;
38
import jakarta.ejb.EJB;
39
import jakarta.ejb.EJBException;
40
import jakarta.json.*;
41
import jakarta.json.JsonValue.ValueType;
42
import jakarta.persistence.EntityManager;
43
import jakarta.persistence.NoResultException;
44
import jakarta.persistence.PersistenceContext;
45
import jakarta.servlet.http.HttpServletRequest;
46
import jakarta.validation.constraints.NotNull;
47
import jakarta.ws.rs.container.ContainerRequestContext;
48
import jakarta.ws.rs.core.Context;
49
import jakarta.ws.rs.core.MediaType;
50
import jakarta.ws.rs.core.Response;
51
import jakarta.ws.rs.core.Response.ResponseBuilder;
52
import jakarta.ws.rs.core.Response.Status;
53

54
import java.io.InputStream;
55
import java.net.URI;
56
import java.util.Arrays;
57
import java.util.Collections;
58
import java.util.UUID;
59
import java.util.concurrent.Callable;
60
import java.util.logging.Level;
61
import java.util.logging.Logger;
62

63
import static org.apache.commons.lang3.StringUtils.isNumeric;
64

65
/**
66
 * Base class for API beans
67
 * @author michael
68
 */
69
public abstract class AbstractApiBean {
1✔
70

71
    private static final Logger logger = Logger.getLogger(AbstractApiBean.class.getName());
1✔
72
    private static final String DATAVERSE_KEY_HEADER_NAME = "X-Dataverse-key";
73
    private static final String PERSISTENT_ID_KEY=":persistentId";
74
    private static final String ALIAS_KEY=":alias";
75
    public static final String STATUS_WF_IN_PROGRESS = "WORKFLOW_IN_PROGRESS";
76
    public static final String DATAVERSE_WORKFLOW_INVOCATION_HEADER_NAME = "X-Dataverse-invocationID";
77
    public static final String RESPONSE_MESSAGE_AUTHENTICATED_USER_REQUIRED = "Only authenticated users can perform the requested operation";
78

79
    /**
80
     * Utility class to convey a proper error response using Java's exceptions.
81
     */
82
    public static class WrappedResponse extends Exception {
83
        private final Response response;
84

85
        public WrappedResponse(Response response) {
1✔
86
            this.response = response;
1✔
87
        }
1✔
88

89
        public WrappedResponse( Throwable cause, Response response ) {
90
            super( cause );
×
91
            this.response = response;
×
92
        }
×
93

94
        public Response getResponse() {
95
            return response;
1✔
96
        }
97

98
        /**
99
         * Creates a new response, based on the original response and the passed message.
100
         * Typical use would be to add a better error message to the HTTP response.
101
         * @param message additional message to be added to the response.
102
         * @return A Response with updated message field.
103
         */
104
        public Response refineResponse( String message ) {
105
            final Status statusCode = Response.Status.fromStatusCode(response.getStatus());
×
106
            String baseMessage = getWrappedMessageWhenJson();
×
107

108
            if ( baseMessage == null ) {
×
109
                final Throwable cause = getCause();
×
110
                baseMessage = (cause!=null ? cause.getMessage() : "");
×
111
            }
112
            return error(statusCode, message+" "+baseMessage);
×
113
        }
114

115
        /**
116
         * In the common case of the wrapped response being of type JSON,
117
         * return the message field it has (if any).
118
         * @return the content of a message field, or {@code null}.
119
         * @throws JsonException when JSON parsing fails.
120
         */
121
        String getWrappedMessageWhenJson() {
122
            if ( response.getMediaType().equals(MediaType.APPLICATION_JSON_TYPE) ) {
×
123
                Object entity = response.getEntity();
×
124
                if ( entity == null ) return null;
×
125

126
                JsonObject obj = JsonUtil.getJsonObject(entity.toString());
×
127
                if ( obj.containsKey("message") ) {
×
128
                    JsonValue message = obj.get("message");
×
129
                    return message.getValueType() == ValueType.STRING ? obj.getString("message") : message.toString();
×
130
                } else {
131
                    return null;
×
132
                }
133

134
            } else {
135
                return null;
×
136
            }
137
        }
138
    }
139

140
    @EJB
141
    protected EjbDataverseEngine engineSvc;
142

143
    @EJB
144
    protected DvObjectServiceBean dvObjectSvc;
145
    
146
    @EJB
147
    protected DatasetServiceBean datasetSvc;
148
    
149
    @EJB
150
    protected DataFileServiceBean fileService;
151

152
    @EJB
153
    protected DataverseServiceBean dataverseSvc;
154

155
    @EJB
156
    protected AuthenticationServiceBean authSvc;
157

158
    @EJB
159
    protected DatasetFieldServiceBean datasetFieldSvc;
160

161
    @EJB
162
    protected MetadataBlockServiceBean metadataBlockSvc;
163

164
    @EJB
165
    protected LicenseServiceBean licenseSvc;
166

167
    @EJB
168
    protected UserServiceBean userSvc;
169

170
        @EJB
171
        protected DataverseRoleServiceBean rolesSvc;
172

173
    @EJB
174
    protected SettingsServiceBean settingsSvc;
175

176
    @EJB
177
    protected RoleAssigneeServiceBean roleAssigneeSvc;
178

179
    @EJB
180
    protected PermissionServiceBean permissionSvc;
181

182
    @EJB
183
    protected GroupServiceBean groupSvc;
184

185
    @EJB
186
    protected ActionLogServiceBean actionLogSvc;
187

188
    @EJB
189
    protected SavedSearchServiceBean savedSearchSvc;
190

191
    @EJB
192
    protected ConfirmEmailServiceBean confirmEmailSvc;
193

194
    @EJB
195
    protected UserNotificationServiceBean userNotificationSvc;
196

197
    @EJB
198
    protected DatasetVersionServiceBean datasetVersionSvc;
199

200
    @EJB
201
    protected SystemConfig systemConfig;
202

203
    @EJB
204
    protected DataCaptureModuleServiceBean dataCaptureModuleSvc;
205
    
206
    @EJB
207
    protected DatasetLinkingServiceBean dsLinkingService;
208
    
209
    @EJB
210
    protected DataverseLinkingServiceBean dvLinkingService;
211

212
    @EJB
213
    protected PasswordValidatorServiceBean passwordValidatorService;
214

215
    @EJB
216
    protected ExternalToolServiceBean externalToolService;
217

218
    @EJB
219
    DataFileServiceBean fileSvc;
220

221
    @EJB
222
    StorageSiteServiceBean storageSiteSvc;
223

224
    @EJB
225
    MetricsServiceBean metricsSvc;
226
    
227
    @EJB 
228
    DvObjectServiceBean dvObjSvc;
229
    
230
    @EJB 
231
    GuestbookResponseServiceBean gbRespSvc;
232

233
    @PersistenceContext(unitName = "VDCNet-ejbPU")
234
    protected EntityManager em;
235

236
    @Context
237
    protected HttpServletRequest httpRequest;
238

239
    /**
240
     * For pretty printing (indenting) of JSON output.
241
     */
242
    public enum Format {
×
243

244
        PRETTY
×
245
    }
246

247
    private final LazyRef<JsonParser> jsonParserRef = new LazyRef<>(new Callable<JsonParser>() {
1✔
248
        @Override
249
        public JsonParser call() throws Exception {
250
            return new JsonParser(datasetFieldSvc, metadataBlockSvc,settingsSvc, licenseSvc);
×
251
        }
252
    });
253

254
    /**
255
     * Functional interface for handling HTTP requests in the APIs.
256
     *
257
     * @see #response(edu.harvard.iq.dataverse.api.AbstractApiBean.DataverseRequestHandler, edu.harvard.iq.dataverse.authorization.users.User)
258
     */
259
    protected static interface DataverseRequestHandler {
260
        Response handle( DataverseRequest u ) throws WrappedResponse;
261
    }
262

263

264
    /* ===================== *\
265
     *  Utility Methods      *
266
     *  Get that DSL feelin' *
267
    \* ===================== */
268

269
    protected JsonParser jsonParser() {
270
        return jsonParserRef.get();
×
271
    }
272

273
    protected boolean parseBooleanOrDie( String input ) throws WrappedResponse {
274
        if (input == null ) throw new WrappedResponse( badRequest("Boolean value missing"));
1✔
275
        input = input.trim();
1✔
276
        if ( Util.isBoolean(input) ) {
1✔
277
            return Util.isTrue(input);
1✔
278
        } else {
279
            throw new WrappedResponse( badRequest("Illegal boolean value '" + input + "'"));
1✔
280
        }
281
    }
282

283
     /**
284
     * Returns the {@code key} query parameter from the current request, or {@code null} if
285
     * the request has no such parameter.
286
     * @param key Name of the requested parameter.
287
     * @return Value of the requested parameter in the current request.
288
     */
289
    protected String getRequestParameter( String key ) {
290
        return httpRequest.getParameter(key);
×
291
    }
292

293
    protected String getRequestApiKey() {
294
        String headerParamApiKey = httpRequest.getHeader(DATAVERSE_KEY_HEADER_NAME);
×
295
        String queryParamApiKey = httpRequest.getParameter("key");
×
296
                
297
        return headerParamApiKey!=null ? headerParamApiKey : queryParamApiKey;
×
298
    }
299

300
    protected User getRequestUser(ContainerRequestContext crc) {
301
        return (User) crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER);
1✔
302
    }
303

304
    /**
305
     * Gets the authenticated user from the ContainerRequestContext user property. If the user from the property
306
     * is not authenticated, throws a wrapped "authenticated user required" user (HTTP UNAUTHORIZED) response.
307
     * @param crc a ContainerRequestContext implementation
308
     * @return The authenticated user
309
     * @throws edu.harvard.iq.dataverse.api.AbstractApiBean.WrappedResponse in case the user is not authenticated.
310
     *
311
     * TODO:
312
     *  This method is designed to comply with existing authorization logic, based on the old findAuthenticatedUserOrDie method.
313
     *  Ideally, as for authentication, a filter could be implemented for authorization, which would extract and encapsulate the
314
     *  authorization logic from the AbstractApiBean.
315
     */
316
    protected AuthenticatedUser getRequestAuthenticatedUserOrDie(ContainerRequestContext crc) throws WrappedResponse {
317
        User requestUser = (User) crc.getProperty(ApiConstants.CONTAINER_REQUEST_CONTEXT_USER);
×
318
        if (requestUser.isAuthenticated()) {
×
319
            return (AuthenticatedUser) requestUser;
×
320
        } else {
321
            throw new WrappedResponse(authenticatedUserRequired());
×
322
        }
323
    }
324

325
    /* ========= *\
326
     *  Finders  *
327
    \* ========= */
328
    protected RoleAssignee findAssignee(String identifier) {
329
        try {
330
            RoleAssignee roleAssignee = roleAssigneeSvc.getRoleAssignee(identifier);
×
331
            return roleAssignee;
×
332
        } catch (EJBException ex) {
×
333
            Throwable cause = ex;
×
334
            while (cause.getCause() != null) {
×
335
                cause = cause.getCause();
×
336
            }
337
            logger.log(Level.INFO, "Exception caught looking up RoleAssignee based on identifier ''{0}'': {1}", new Object[]{identifier, cause.getMessage()});
×
338
            return null;
×
339
        }
340
    }
341

342
    /**
343
     * @param apiKey the key to find the user with
344
     * @return the user, or null
345
     */
346
    protected AuthenticatedUser findUserByApiToken( String apiKey ) {
347
        return authSvc.lookupUser(apiKey);
×
348
    }
349

350
    protected Dataverse findDataverseOrDie( String dvIdtf ) throws WrappedResponse {
351
        Dataverse dv = findDataverse(dvIdtf);
1✔
352
        if ( dv == null ) {
1✔
353
            throw new WrappedResponse(error( Response.Status.NOT_FOUND, "Can't find dataverse with identifier='" + dvIdtf + "'"));
1✔
354
        }
355
        return dv;
1✔
356
    }
357
    
358
    protected DataverseLinkingDataverse findDataverseLinkingDataverseOrDie(String dataverseId, String linkedDataverseId) throws WrappedResponse {
359
        DataverseLinkingDataverse dvld;
360
        Dataverse dataverse = findDataverseOrDie(dataverseId);
×
361
        Dataverse linkedDataverse = findDataverseOrDie(linkedDataverseId);
×
362
        try {
363
            dvld = dvLinkingService.findDataverseLinkingDataverse(dataverse.getId(), linkedDataverse.getId());
×
364
            if (dvld == null) {
×
365
                throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.dataverselinking.error.not.found.ids", Arrays.asList(dataverseId, linkedDataverseId))));
×
366
            }
367
            return dvld;
×
368
        } catch (NumberFormatException nfe) {
×
369
            throw new WrappedResponse(
×
370
                    badRequest(BundleUtil.getStringFromBundle("find.dataverselinking.error.not.found.bad.ids", Arrays.asList(dataverseId, linkedDataverseId))));
×
371
        }
372
    }
373

374
    protected Dataset findDatasetOrDie(String id) throws WrappedResponse {
NEW
375
        return findDatasetOrDie(id, false);
×
376
    }
377

378
    protected Dataset findDatasetOrDie(String id, boolean deep) throws WrappedResponse {
379
        Long datasetId;
380
        Dataset dataset;
381
        if (id.equals(PERSISTENT_ID_KEY)) {
×
382
            String persistentId = getRequestParameter(PERSISTENT_ID_KEY.substring(1));
×
383
            if (persistentId == null) {
×
384
                throw new WrappedResponse(
×
385
                        badRequest(BundleUtil.getStringFromBundle("find.dataset.error.dataset_id_is_null", Collections.singletonList(PERSISTENT_ID_KEY.substring(1)))));
×
386
            }
387
            GlobalId globalId;
388
            try {
NEW
389
                globalId = PidUtil.parseAsGlobalID(persistentId);
×
NEW
390
            } catch (IllegalArgumentException e) {
×
NEW
391
                throw new WrappedResponse(
×
NEW
392
                    badRequest(BundleUtil.getStringFromBundle("find.dataset.error.dataset.not.found.bad.id", Collections.singletonList(persistentId))));
×
NEW
393
            }
×
NEW
394
            datasetId = dvObjSvc.findIdByGlobalId(globalId, DvObject.DType.Dataset);
×
NEW
395
            if (datasetId == null) {
×
NEW
396
                datasetId = dvObjSvc.findIdByAltGlobalId(globalId, DvObject.DType.Dataset);
×
397
            }
NEW
398
            if (datasetId == null) {
×
NEW
399
                throw new WrappedResponse(
×
NEW
400
                    notFound(BundleUtil.getStringFromBundle("find.dataset.error.dataset_id_is_null", Collections.singletonList(PERSISTENT_ID_KEY.substring(1)))));
×
401
            }
402
        } else {
×
403
            try {
NEW
404
                datasetId = Long.parseLong(id);
×
405
            } catch (NumberFormatException nfe) {
×
406
                throw new WrappedResponse(
×
407
                        badRequest(BundleUtil.getStringFromBundle("find.dataset.error.dataset.not.found.bad.id", Collections.singletonList(id))));
×
UNCOV
408
            }
×
409
        }
NEW
410
        if (deep) {
×
NEW
411
            dataset = datasetSvc.findDeep(datasetId);
×
412
        } else {
NEW
413
            dataset = datasetSvc.find(datasetId);
×
414
        }
NEW
415
        if (dataset == null) {
×
NEW
416
            throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.dataset.error.dataset.not.found.id", Collections.singletonList(id))));
×
417
        }
NEW
418
        return dataset;
×
419
    }
420

421
    protected DatasetVersion findDatasetVersionOrDie(final DataverseRequest req, String versionNumber, final Dataset ds, boolean includeDeaccessioned, boolean checkPermsWhenDeaccessioned) throws WrappedResponse {
422
        DatasetVersion dsv = execCommand(handleVersion(versionNumber, new Datasets.DsVersionHandler<Command<DatasetVersion>>() {
×
423

424
            @Override
425
            public Command<DatasetVersion> handleLatest() {
426
                return new GetLatestAccessibleDatasetVersionCommand(req, ds, includeDeaccessioned, checkPermsWhenDeaccessioned);
×
427
            }
428

429
            @Override
430
            public Command<DatasetVersion> handleDraft() {
431
                return new GetDraftDatasetVersionCommand(req, ds);
×
432
            }
433

434
            @Override
435
            public Command<DatasetVersion> handleSpecific(long major, long minor) {
436
                return new GetSpecificPublishedDatasetVersionCommand(req, ds, major, minor, includeDeaccessioned, checkPermsWhenDeaccessioned);
×
437
            }
438

439
            @Override
440
            public Command<DatasetVersion> handleLatestPublished() {
441
                return new GetLatestPublishedDatasetVersionCommand(req, ds, includeDeaccessioned, checkPermsWhenDeaccessioned);
×
442
            }
443
        }));
444
        return dsv;
×
445
    }
446

447
    protected DataFile findDataFileOrDie(String id) throws WrappedResponse {
448
        DataFile datafile;
449
        if (id.equals(PERSISTENT_ID_KEY)) {
×
450
            String persistentId = getRequestParameter(PERSISTENT_ID_KEY.substring(1));
×
451
            if (persistentId == null) {
×
452
                throw new WrappedResponse(
×
453
                        badRequest(BundleUtil.getStringFromBundle("find.dataset.error.dataset_id_is_null", Collections.singletonList(PERSISTENT_ID_KEY.substring(1)))));
×
454
            }
455
            datafile = fileService.findByGlobalId(persistentId);
×
456
            if (datafile == null) {
×
457
                throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.datafile.error.dataset.not.found.persistentId", Collections.singletonList(persistentId))));
×
458
            }
459
            return datafile;
×
460
        } else {
461
            try {
462
                datafile = fileService.find(Long.parseLong(id));
×
463
                if (datafile == null) {
×
464
                    throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.datafile.error.datafile.not.found.id", Collections.singletonList(id))));
×
465
                }
466
                return datafile;
×
467
            } catch (NumberFormatException nfe) {
×
468
                throw new WrappedResponse(
×
469
                        badRequest(BundleUtil.getStringFromBundle("find.datafile.error.datafile.not.found.bad.id", Collections.singletonList(id))));
×
470
            }
471
        }
472
    }
473
       
474
    protected DataverseRole findRoleOrDie(String id) throws WrappedResponse {
475
        DataverseRole role;
476
        if (id.equals(ALIAS_KEY)) {
×
477
            String alias = getRequestParameter(ALIAS_KEY.substring(1));
×
478
            try {
479
                return em.createNamedQuery("DataverseRole.findDataverseRoleByAlias", DataverseRole.class)
×
480
                        .setParameter("alias", alias)
×
481
                        .getSingleResult();
×
482

483
            //Should not be a multiple result exception due to table constraint
484
            } catch (NoResultException nre) {
×
485
                throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.dataverse.role.error.role.not.found.alias", Collections.singletonList(alias))));
×
486
            }
487

488
        } else {
489

490
            try {
491
                role = rolesSvc.find(Long.parseLong(id));
×
492
                if (role == null) {
×
493
                    throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.dataverse.role.error.role.not.found.id", Collections.singletonList(id))));
×
494
                } else {
495
                    return role;
×
496
                }
497

498
            } catch (NumberFormatException nfe) {
×
499
                throw new WrappedResponse(
×
500
                        badRequest(BundleUtil.getStringFromBundle("find.dataverse.role.error.role.not.found.bad.id", Collections.singletonList(id))));
×
501
            }
502
        }
503
    }
504
    
505
    protected DatasetLinkingDataverse findDatasetLinkingDataverseOrDie(String datasetId, String linkingDataverseId) throws WrappedResponse {
506
        DatasetLinkingDataverse dsld;
507
        Dataverse linkingDataverse = findDataverseOrDie(linkingDataverseId);
×
508

509
        if (datasetId.equals(PERSISTENT_ID_KEY)) {
×
510
            String persistentId = getRequestParameter(PERSISTENT_ID_KEY.substring(1));
×
511
            if (persistentId == null) {
×
512
                throw new WrappedResponse(
×
513
                        badRequest(BundleUtil.getStringFromBundle("find.dataset.error.dataset_id_is_null", Collections.singletonList(PERSISTENT_ID_KEY.substring(1)))));
×
514
            }
515
            
516
            Dataset dataset = datasetSvc.findByGlobalId(persistentId);
×
517
            if (dataset == null) {
×
518
                throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.dataset.error.dataset.not.found.persistentId", Collections.singletonList(persistentId))));
×
519
            }
520
            datasetId = dataset.getId().toString();
×
521
        } 
522
        try {
523
            dsld = dsLinkingService.findDatasetLinkingDataverse(Long.parseLong(datasetId), linkingDataverse.getId());
×
524
            if (dsld == null) {
×
525
                throw new WrappedResponse(notFound(BundleUtil.getStringFromBundle("find.datasetlinking.error.not.found.ids", Arrays.asList(datasetId, linkingDataverse.getId().toString()))));
×
526
            }
527
            return dsld;
×
528
        } catch (NumberFormatException nfe) {
×
529
            throw new WrappedResponse(
×
530
                    badRequest(BundleUtil.getStringFromBundle("find.datasetlinking.error.not.found.bad.ids", Arrays.asList(datasetId, linkingDataverse.getId().toString()))));
×
531
        }
532
    }
533

534
    protected DataverseRequest createDataverseRequest( User u )  {
535
        return new DataverseRequest(u, httpRequest);
1✔
536
    }
537

538
        protected Dataverse findDataverse( String idtf ) {
539
                return isNumeric(idtf) ? dataverseSvc.find(Long.parseLong(idtf))
1✔
540
                                                                   : dataverseSvc.findByAlias(idtf);
1✔
541
        }
542

543
        protected DvObject findDvo( Long id ) {
544
                return em.createNamedQuery("DvObject.findById", DvObject.class)
×
545
                                .setParameter("id", id)
×
546
                                .getSingleResult();
×
547
        }
548

549
    /**
550
     * Tries to find a DvObject. If the passed id can be interpreted as a number,
551
     * it tries to get the DvObject by its id. Else, it tries to get a {@link Dataverse}
552
     * with that alias. If that fails, tries to get a {@link Dataset} with that global id.
553
     * @param id a value identifying the DvObject, either numeric of textual.
554
     * @return A DvObject, or {@code null}
555
     * @throws WrappedResponse
556
     */
557
    @NotNull
558
    protected DvObject findDvo(@NotNull final String id) throws WrappedResponse {
NEW
559
        DvObject d = null;
×
NEW
560
        if (isNumeric(id)) {
×
NEW
561
            d = findDvo(Long.valueOf(id));
×
562
        } else {
NEW
563
            d = dataverseSvc.findByAlias(id);
×
564
        }
NEW
565
        if (d == null) {
×
NEW
566
            return findDatasetOrDie(id);
×
567
        }
NEW
568
        return d;
×
569
    }
570

571
    protected <T> T failIfNull( T t, String errorMessage ) throws WrappedResponse {
572
        if ( t != null ) return t;
1✔
573
        throw new WrappedResponse( error( Response.Status.BAD_REQUEST,errorMessage) );
×
574
    }
575

576
    protected MetadataBlock findMetadataBlock(Long id)  {
577
        return metadataBlockSvc.findById(id);
×
578
    }
579
    protected MetadataBlock findMetadataBlock(String idtf) throws NumberFormatException {
580
        return metadataBlockSvc.findByName(idtf);
1✔
581
    }
582

583
    protected DatasetFieldType findDatasetFieldType(String idtf) throws NumberFormatException {
584
        return isNumeric(idtf) ? datasetFieldSvc.find(Long.parseLong(idtf))
×
585
                : datasetFieldSvc.findByNameOpt(idtf);
×
586
    }
587

588
    /* =================== *\
589
     *  Command Execution  *
590
    \* =================== */
591

592
    /**
593
     * Executes a command, and returns the appropriate result/HTTP response.
594
     * @param <T> Return type for the command
595
     * @param cmd The command to execute.
596
     * @return Value from the command
597
     * @throws edu.harvard.iq.dataverse.api.AbstractApiBean.WrappedResponse Unwrap and return.
598
     * @see #response(java.util.concurrent.Callable)
599
     */
600
    protected <T> T execCommand( Command<T> cmd ) throws WrappedResponse {
601
        try {
602
            return engineSvc.submit(cmd);
1✔
603

604
        } catch (RateLimitCommandException ex) {
×
605
            throw new WrappedResponse(rateLimited(ex.getMessage()));
×
606
        } catch (IllegalCommandException ex) {
×
607
            //for 8859 for api calls that try to update datasets with TOA out of compliance
608
                if (ex.getMessage().toLowerCase().contains("terms of use")){
×
609
                    throw new WrappedResponse(ex, conflict(ex.getMessage()));
×
610
                }
611
            throw new WrappedResponse( ex, forbidden(ex.getMessage() ) );
×
612
        } catch (PermissionException ex) {
×
613
            /**
614
             * TODO Is there any harm in exposing ex.getLocalizedMessage()?
615
             * There's valuable information in there that can help people reason
616
             * about permissions! The formatting of the error would need to be
617
             * cleaned up but here's an example the helpful information:
618
             *
619
             * "User :guest is not permitted to perform requested action.Can't
620
             * execute command
621
             * edu.harvard.iq.dataverse.engine.command.impl.MoveDatasetCommand@50b150d9,
622
             * because request [DataverseRequest user:[GuestUser
623
             * :guest]@127.0.0.1] is missing permissions [AddDataset,
624
             * PublishDataset] on Object mra"
625
             *
626
             * Right now, the error that's visible via API (and via GUI
627
             * sometimes?) doesn't have much information in it:
628
             *
629
             * "User @jsmith is not permitted to perform requested action."
630
             */
631
            throw new WrappedResponse(error(Response.Status.UNAUTHORIZED,
×
632
                                                    "User " + cmd.getRequest().getUser().getIdentifier() + " is not permitted to perform requested action.") );
×
633

634
        } catch (CommandException ex) {
×
635
            Logger.getLogger(AbstractApiBean.class.getName()).log(Level.SEVERE, "Error while executing command " + cmd, ex);
×
636
            throw new WrappedResponse(ex, error(Status.INTERNAL_SERVER_ERROR, ex.getMessage()));
×
637
        }
638
    }
639

640
    /**
641
     * A syntactically nicer way of using {@link #execCommand(edu.harvard.iq.dataverse.engine.command.Command)}.
642
     * @param hdl The block to run.
643
     * @return HTTP Response appropriate for the way {@code hdl} executed.
644
     */
645
    protected Response response( Callable<Response> hdl ) {
646
        try {
647
            return hdl.call();
×
648
        } catch ( WrappedResponse rr ) {
×
649
            return rr.getResponse();
×
650
        } catch ( Exception ex ) {
×
651
            return handleDataverseRequestHandlerException(ex);
×
652
        }
653
    }
654

655
    /***
656
     * The preferred way of handling a request that requires a user. The method
657
     * receives a user and handles it to the handler for doing the actual work.
658
     *
659
     * @param hdl handling code block.
660
     * @param user the associated request user.
661
     * @return HTTP Response appropriate for the way {@code hdl} executed.
662
     */
663
    protected Response response(DataverseRequestHandler hdl, User user) {
664
        try {
665
            return hdl.handle(createDataverseRequest(user));
×
666
        } catch ( WrappedResponse rr ) {
×
667
            return rr.getResponse();
×
668
        } catch ( Exception ex ) {
×
669
            return handleDataverseRequestHandlerException(ex);
×
670
        }
671
    }
672

673
    private Response handleDataverseRequestHandlerException(Exception ex) {
674
        String incidentId = UUID.randomUUID().toString();
×
675
        logger.log(Level.SEVERE, "API internal error " + incidentId +": " + ex.getMessage(), ex);
×
676
        return Response.status(500)
×
677
                .entity(Json.createObjectBuilder()
×
678
                        .add("status", "ERROR")
×
679
                        .add("code", 500)
×
680
                        .add("message", "Internal server error. More details available at the server logs.")
×
681
                        .add("incidentId", incidentId)
×
682
                        .build())
×
683
                .type("application/json").build();
×
684
    }
685

686
    /* ====================== *\
687
     *  HTTP Response methods *
688
    \* ====================== */
689

690
    protected Response ok( JsonArrayBuilder bld ) {
691
        return Response.ok(Json.createObjectBuilder()
×
692
            .add("status", ApiConstants.STATUS_OK)
×
693
            .add("data", bld).build())
×
694
            .type(MediaType.APPLICATION_JSON).build();
×
695
    }
696

697
    protected Response ok( JsonArrayBuilder bld , long totalCount) {
698
        return Response.ok(Json.createObjectBuilder()
×
699
                        .add("status", ApiConstants.STATUS_OK)
×
700
                        .add("totalCount", totalCount)
×
701
                        .add("data", bld).build())
×
702
                .type(MediaType.APPLICATION_JSON).build();
×
703
    }
704

705
    protected Response ok( JsonArray ja ) {
706
        return Response.ok(Json.createObjectBuilder()
×
707
            .add("status", ApiConstants.STATUS_OK)
×
708
            .add("data", ja).build())
×
709
            .type(MediaType.APPLICATION_JSON).build();
×
710
    }
711

712
    protected Response ok( JsonObjectBuilder bld ) {
713
        return Response.ok( Json.createObjectBuilder()
×
714
            .add("status", ApiConstants.STATUS_OK)
×
715
            .add("data", bld).build() )
×
716
            .type(MediaType.APPLICATION_JSON)
×
717
            .build();
×
718
    }
719
    
720
    protected Response ok( JsonObject jo ) {
721
        return Response.ok( Json.createObjectBuilder()
×
722
                .add("status", ApiConstants.STATUS_OK)
×
723
                .add("data", jo).build() )
×
724
                .type(MediaType.APPLICATION_JSON)
×
725
                .build();    
×
726
    }
727

728
    protected Response ok( String msg ) {
729
        return Response.ok().entity(Json.createObjectBuilder()
1✔
730
            .add("status", ApiConstants.STATUS_OK)
1✔
731
            .add("data", Json.createObjectBuilder().add("message",msg)).build() )
1✔
732
            .type(MediaType.APPLICATION_JSON)
1✔
733
            .build();
1✔
734
    }
735
    
736
    protected Response ok( String msg, JsonObjectBuilder bld  ) {
737
        return Response.ok().entity(Json.createObjectBuilder()
×
738
            .add("status", ApiConstants.STATUS_OK)
×
739
            .add("message", Json.createObjectBuilder().add("message",msg))     
×
740
            .add("data", bld).build())      
×
741
            .type(MediaType.APPLICATION_JSON)
×
742
            .build();
×
743
    }
744

745
    protected Response ok( boolean value ) {
746
        return Response.ok().entity(Json.createObjectBuilder()
×
747
            .add("status", ApiConstants.STATUS_OK)
×
748
            .add("data", value).build() ).build();
×
749
    }
750

751
    protected Response ok(long value) {
752
        return Response.ok().entity(Json.createObjectBuilder()
×
753
                .add("status", ApiConstants.STATUS_OK)
×
754
                .add("data", value).build()).build();
×
755
    }
756

757
    /**
758
     * @param data Payload to return.
759
     * @param mediaType Non-JSON media type.
760
     * @param downloadFilename - add Content-Disposition header to suggest filename if not null
761
     * @return Non-JSON response, such as a shell script.
762
     */
763
    protected Response ok(String data, MediaType mediaType, String downloadFilename) {
764
        ResponseBuilder res =Response.ok().entity(data).type(mediaType);
×
765
        if(downloadFilename != null) {
×
766
            res = res.header("Content-Disposition", "attachment; filename=" + downloadFilename);
×
767
        }
768
        return res.build();
×
769
    }
770

771
    protected Response ok(InputStream inputStream) {
772
        ResponseBuilder res = Response.ok().entity(inputStream).type(MediaType.valueOf(FileUtil.MIME_TYPE_UNDETERMINED_DEFAULT));
×
773
        return res.build();
×
774
    }
775

776
    protected Response created( String uri, JsonObjectBuilder bld ) {
777
        return Response.created( URI.create(uri) )
×
778
                .entity( Json.createObjectBuilder()
×
779
                .add("status", "OK")
×
780
                .add("data", bld).build())
×
781
                .type(MediaType.APPLICATION_JSON)
×
782
                .build();
×
783
    }
784
    
785
    protected Response accepted(JsonObjectBuilder bld) {
786
        return Response.accepted()
×
787
                .entity(Json.createObjectBuilder()
×
788
                        .add("status", STATUS_WF_IN_PROGRESS)
×
789
                        .add("data",bld).build()
×
790
                ).build();
×
791
    }
792
    
793
    protected Response accepted() {
794
        return Response.accepted()
×
795
                .entity(Json.createObjectBuilder()
×
796
                        .add("status", STATUS_WF_IN_PROGRESS).build()
×
797
                ).build();
×
798
    }
799

800
    protected Response notFound( String msg ) {
801
        return error(Status.NOT_FOUND, msg);
×
802
    }
803

804
    protected Response badRequest( String msg ) {
805
        return error( Status.BAD_REQUEST, msg );
1✔
806
    }
807

808
    protected Response forbidden( String msg ) {
809
        return error( Status.FORBIDDEN, msg );
×
810
    }
811

812
    protected Response rateLimited( String msg ) {
813
        return error( Status.TOO_MANY_REQUESTS, msg );
×
814
    }
815

816
    protected Response conflict( String msg ) {
817
        return error( Status.CONFLICT, msg );
×
818
    }
819

820
    protected Response authenticatedUserRequired() {
821
        return error(Status.UNAUTHORIZED, RESPONSE_MESSAGE_AUTHENTICATED_USER_REQUIRED);
×
822
    }
823

824
    protected Response permissionError( PermissionException pe ) {
825
        return permissionError( pe.getMessage() );
×
826
    }
827

828
    protected Response permissionError( String message ) {
829
        return unauthorized( message );
×
830
    }
831
    
832
    protected Response unauthorized( String message ) {
833
        return error( Status.UNAUTHORIZED, message );
×
834
    }
835

836
    protected static Response error( Status sts, String msg ) {
837
        return Response.status(sts)
1✔
838
                .entity( NullSafeJsonBuilder.jsonObjectBuilder()
1✔
839
                        .add("status", ApiConstants.STATUS_ERROR)
1✔
840
                        .add( "message", msg ).build()
1✔
841
                ).type(MediaType.APPLICATION_JSON_TYPE).build();
1✔
842
    }
843
}
844

845
class LazyRef<T> {
846
    private interface Ref<T> {
847
        T get();
848
    }
849

850
    private Ref<T> ref;
851

852
    public LazyRef( final Callable<T> initer ) {
1✔
853
        ref = () -> {
1✔
854
            try {
855
                final T t = initer.call();
×
856
                ref = () -> t;
×
857
                return ref.get();
×
858
            } catch (Exception ex) {
×
859
                Logger.getLogger(LazyRef.class.getName()).log(Level.SEVERE, null, ex);
×
860
                return null;
×
861
            }
862
        };
863
    }
1✔
864

865
    public T get()  {
866
        return ref.get();
×
867
    }
868
}
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