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

IQSS / dataverse / #22002

01 Apr 2024 07:56PM CUT coverage: 20.716% (+0.5%) from 20.173%
#22002

push

github

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

Merge 6.2 into master

704 of 2679 new or added lines in 152 files covered. (26.28%)

81 existing lines in 49 files now uncovered.

17160 of 82836 relevant lines covered (20.72%)

0.21 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/workflow/WorkflowServiceBean.java
1
package edu.harvard.iq.dataverse.workflow;
2

3
import edu.harvard.iq.dataverse.Dataset;
4
import edu.harvard.iq.dataverse.DatasetLock;
5
import edu.harvard.iq.dataverse.DatasetServiceBean;
6
import edu.harvard.iq.dataverse.DataverseRequestServiceBean;
7
import edu.harvard.iq.dataverse.DvObjectServiceBean;
8
import edu.harvard.iq.dataverse.EjbDataverseEngine;
9
import edu.harvard.iq.dataverse.RoleAssigneeServiceBean;
10
import edu.harvard.iq.dataverse.UserNotification;
11
import edu.harvard.iq.dataverse.UserNotificationServiceBean;
12
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
13
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
14
import edu.harvard.iq.dataverse.engine.command.CommandContext;
15
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
16
import edu.harvard.iq.dataverse.engine.command.impl.FinalizeDatasetPublicationCommand;
17
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
18
import edu.harvard.iq.dataverse.util.SystemConfig;
19
import edu.harvard.iq.dataverse.workflow.WorkflowContext.TriggerType;
20
import edu.harvard.iq.dataverse.workflow.internalspi.InternalWorkflowStepSP;
21
import edu.harvard.iq.dataverse.workflow.step.Failure;
22
import edu.harvard.iq.dataverse.workflow.step.Pending;
23
import edu.harvard.iq.dataverse.workflow.step.Success;
24
import edu.harvard.iq.dataverse.workflow.step.WorkflowStep;
25
import edu.harvard.iq.dataverse.workflow.step.WorkflowStepData;
26
import edu.harvard.iq.dataverse.workflow.step.WorkflowStepResult;
27
import edu.harvard.iq.dataverse.workflows.WorkflowComment;
28

29
import java.sql.Timestamp;
30
import java.time.Instant;
31
import java.util.HashMap;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.Optional;
35
import java.util.logging.Level;
36
import java.util.logging.Logger;
37
import jakarta.ejb.Asynchronous;
38
import jakarta.ejb.EJB;
39
import jakarta.ejb.Stateless;
40
import jakarta.ejb.TransactionAttribute;
41
import jakarta.ejb.TransactionAttributeType;
42
import jakarta.inject.Inject;
43
import jakarta.persistence.EntityManager;
44
import jakarta.persistence.PersistenceContext;
45
import jakarta.persistence.TypedQuery;
46

47
/**
48
 * Service bean for managing and executing {@link Workflow}s
49
 *
50
 * @author michael
51
 */
52
@Stateless
53
public class WorkflowServiceBean {
54

55
    private static final Logger logger = Logger.getLogger(WorkflowServiceBean.class.getName());
×
56
    private static final String WORKFLOW_ID_KEY = "WorkflowServiceBean.WorkflowId:";
57

58
    @PersistenceContext(unitName = "VDCNet-ejbPU")
59
    EntityManager em;
60
    
61
    @EJB
62
    DatasetServiceBean datasets;
63
    
64
    @EJB
65
    DvObjectServiceBean dvObjects;
66

67
    @EJB
68
    SettingsServiceBean settings;
69

70
    @EJB
71
    RoleAssigneeServiceBean roleAssignees;
72
    
73
    @EJB 
74
    SystemConfig systemConfig;
75

76
    @EJB
77
    UserNotificationServiceBean userNotificationService;
78
    
79
    @EJB
80
    EjbDataverseEngine engine;
81
    
82
    @Inject
83
    DataverseRequestServiceBean dvRequestService;
84
    
85
    final Map<String, WorkflowStepSPI> providers = new HashMap<>();
×
86

87
    public WorkflowServiceBean() {
×
88
        providers.put(":internal", new InternalWorkflowStepSP());
×
89

90
//        Re-enable code below, if we allow .jars in the classpath to provide WorkflowStepProviders.
91
//        ServiceLoader<WorkflowStepSPI> loader = ServiceLoader.load(WorkflowStepSPI.class);
92
//        try {
93
//            for ( WorkflowStepSPI wss : loader ) {
94
//                logger.log(Level.INFO, "Found WorkflowStepProvider: {0}", wss.getClass().getCanonicalName());
95
//                providers.put( wss.getClass().getCanonicalName(), wss );
96
//            }
97
//            logger.log(Level.INFO, "Searching for Workflow Step Providers done.");
98
//        } catch (NoClassDefFoundError ncdfe) {
99
//            logger.log(Level.WARNING, "Class not found: " + ncdfe.getMessage(), ncdfe);
100
//        } catch (ServiceConfigurationError serviceError) {
101
//            logger.log(Level.WARNING, "Service Error loading workflow step providers: " + serviceError.getMessage(), serviceError);
102
//        }
103
        
104
    }
×
105
    
106
    /**
107
     * Starts executing workflow {@code wf} under the passed context.
108
     *
109
     * @param wf the workflow to execute.
110
     * @param ctxt the context in which the workflow is executed.
111
     * @throws CommandException If the dataset could not be locked.
112
     */
113
    //ToDo - should this be @Async? or just the forward() method?
114
    @Asynchronous
115
    public void start(Workflow wf, WorkflowContext ctxt, boolean findDataset) throws CommandException {
116
        /*
117
         * Workflows appear to start running prior to the caller's transaction
118
         * completing which can result in exceptions in setting the lock below. To avoid
119
         * this, there are two work-arounds - wait briefly for that transaction to end,
120
         * or refresh the dataset from the db - so the lock is written based on the
121
         * current db state. The latter works for pre-publication workflows (since the
122
         * only changes to the Dataset in the Publish command are edits to the version
123
         * number in the draft version (which aren't valid for the draft anyway)), while
124
         * the former is required for post-publication workflows which may need to see
125
         * the final version number, update times and other changes made in the Finalize
126
         * Publication command. Not waiting saves significant time when many datasets
127
         * are processed, so is prefereable when it makes sense.
128
         * 
129
         * This code should be reconsidered if/when the launching of pre/post
130
         * publication workflows is moved to command onSuccess methods (and when
131
         * onSuccess methods are guaranteed to be after the transaction completes (see
132
         * #7568) or other changes are made that can guarantee the dataset in the
133
         * WorkflowContext is up-to-date/usable in further transactions in the workflow.
134
         * (e.g. if this method is not asynchronous)
135
         * 
136
         */
137

138
        if (!findDataset) {
×
139
            /*
140
             * Sleep here briefly to make sure the database update from the callers
141
             * transaction completes which avoids any concurrency/optimistic lock issues.
142
             * Note: 1 second appears long enough, but shorter delays may work.
143
             * One example:
144
             * the Dataverses.importDataset()/importDatasetDDI() calls with release=yes will
145
             * trigger a prepublish workflow on a dataset that isn't committed to the
146
             * database until the API call completes.
147
             */
148
            try {
149
                Thread.sleep(1000);
×
150
            } catch (Exception ex) {
×
151
                logger.warning("Failed to sleep for a second.");
×
152
            }
×
153
        }
154
        //Refresh will only em.find the dataset if findDataset is true. (otherwise the dataset is em.merged)
155
        ctxt = refresh(ctxt, retrieveRequestedSettings( wf.getRequiredSettings()), getCurrentApiToken(ctxt.getRequest().getAuthenticatedUser()), findDataset);
×
156
        lockDataset(ctxt, new DatasetLock(DatasetLock.Reason.Workflow, ctxt.getRequest().getAuthenticatedUser()));
×
157
        forward(wf, ctxt);
×
158
    }
×
159
    
160

161
    private ApiToken getCurrentApiToken(AuthenticatedUser au) {
162
        if (au != null) {
×
163
            CommandContext ctxt = engine.getContext();
×
164
            ApiToken token = ctxt.authentication().findApiTokenByUser(au);
×
165
            if (token == null) {
×
166
                //No un-expired token
167
                token = ctxt.authentication().generateApiTokenForUser(au);
×
168
            }
169
            return token;
×
170
        }
171
        return null;
×
172
    }
173

174
    private Map<String, Object> retrieveRequestedSettings(Map<String, String> requiredSettings) {
175
        Map<String, Object> retrievedSettings = new HashMap<String, Object>();
×
176
        for (String setting : requiredSettings.keySet()) {
×
177
            String settingType = requiredSettings.get(setting);
×
178
            switch (settingType) {
×
179
            case "string": {
180
                retrievedSettings.put(setting, settings.get(setting));
×
181
                break;
×
182
            }
183
            case "boolean": {
184
                retrievedSettings.put(setting, settings.isTrue(settingType, false));
×
185
                break;
×
186
            }
187
            case "long": {
188
                retrievedSettings.put(setting,
×
189
                        settings.getValueForKeyAsLong(SettingsServiceBean.Key.valueOf(setting)));
×
190
                break;
191
            }
192
            }
193
        }
×
194
        return retrievedSettings;
×
195
    }
196

197
    /**
198
     * Starting the resume process for a pending workflow. We first delete the
199
     * pending workflow to minimize double invocation, and then asynchronously
200
     * resume the work.
201
     *
202
     * @param pending The workflow to resume.
203
     * @param body the response from the remote system.
204
     * @see
205
     * #doResume(edu.harvard.iq.dataverse.workflow.PendingWorkflowInvocation,
206
     * java.lang.String)
207
     */
208
    @Asynchronous
209
    public void resume(PendingWorkflowInvocation pending, String body) {
210
        em.remove(em.merge(pending));
×
211
        doResume(pending, body);
×
212
    }
×
213
    
214
    
215
    @Asynchronous
216
    private void forward(Workflow wf, WorkflowContext ctxt) {
217
        executeSteps(wf, ctxt, 0);
×
218
    }
×
219
    
220
    private void doResume(PendingWorkflowInvocation pending, String body) {
221
        Workflow wf = pending.getWorkflow();
×
222
        List<WorkflowStepData> stepsLeft = wf.getSteps().subList(pending.getPendingStepIdx(), wf.getSteps().size());
×
223
        
224
        WorkflowStep pendingStep = createStep(stepsLeft.get(0));
×
225
        WorkflowContext newCtxt = pending.reCreateContext(roleAssignees);
×
226
        final WorkflowContext ctxt = refresh(newCtxt,retrieveRequestedSettings( wf.getRequiredSettings()), getCurrentApiToken(newCtxt.getRequest().getAuthenticatedUser()));
×
227
        WorkflowStepResult res = pendingStep.resume(ctxt, pending.getLocalData(), body);
×
228
        if (res instanceof Failure) {
×
229
            logger.warning(((Failure) res).getReason());
×
230
            userNotificationService.sendNotification(ctxt.getRequest().getAuthenticatedUser(), Timestamp.from(Instant.now()), UserNotification.Type.WORKFLOW_FAILURE, ctxt.getDataset().getLatestVersion().getId(), ((Failure) res).getMessage());
×
231
            //UserNotification isn't meant to be a long-term record and doesn't store the comment, so we'll also keep it as a workflow comment
232
            WorkflowComment wfc = new WorkflowComment(ctxt.getDataset().getLatestVersion(), WorkflowComment.Type.WORKFLOW_FAILURE, ((Failure) res).getMessage(), ctxt.getRequest().getAuthenticatedUser());
×
233
            datasets.addWorkflowComment(wfc);
×
234
            rollback(wf, ctxt, (Failure) res, pending.getPendingStepIdx() - 1);
×
235
        } else if (res instanceof Pending) {
×
236
            pauseAndAwait(wf, ctxt, (Pending) res, pending.getPendingStepIdx());
×
237
        } else {
238
            if (res instanceof Success) {
×
239
                logger.info(((Success) res).getReason());
×
240
                userNotificationService.sendNotification(ctxt.getRequest().getAuthenticatedUser(), Timestamp.from(Instant.now()), UserNotification.Type.WORKFLOW_SUCCESS, ctxt.getDataset().getLatestVersion().getId(), ((Success) res).getMessage());
×
241
                //UserNotification isn't meant to be a long-term record and doesn't store the comment, so we'll also keep it as a workflow comment
242
                WorkflowComment wfc = new WorkflowComment(ctxt.getDataset().getLatestVersion(), WorkflowComment.Type.WORKFLOW_SUCCESS, ((Success) res).getMessage(), ctxt.getRequest().getAuthenticatedUser());
×
243
                datasets.addWorkflowComment(wfc);
×
244
        }
245
            executeSteps(wf, ctxt, pending.getPendingStepIdx() + 1);
×
246
        }
247
    }
×
248

249
    @Asynchronous
250
    private void rollback(Workflow wf, WorkflowContext ctxt, Failure failure, int lastCompletedStepIdx) {
251
        ctxt = refresh(ctxt);
×
252
        final List<WorkflowStepData> steps = wf.getSteps();
×
253
        
254
        for ( int stepIdx = lastCompletedStepIdx; stepIdx >= 0; --stepIdx ) {
×
255
            WorkflowStepData wsd = steps.get(stepIdx);
×
256
            WorkflowStep step = createStep(wsd);
×
257
            
258
            try {
259
                logger.log(Level.INFO, "Workflow {0} step {1}: Rollback", new Object[]{ctxt.getInvocationId(), stepIdx});
×
260
                rollbackStep(step, ctxt, failure);
×
261
                
262
            } catch (Exception e) {
×
263
                logger.log(Level.WARNING, "Workflow " + ctxt.getInvocationId() 
×
264
                                          + " step " + stepIdx + ": Rollback error: " + e.getMessage(), e);
×
265
            }
×
266

267
        }
268
        
269
        logger.log( Level.INFO, "Removing workflow lock");
×
270
        try {
271
            unlockDataset(ctxt);
×
272
        } catch (CommandException ex) {
×
273
            logger.log(Level.SEVERE, "Error restoring dataset locks state after rollback: " + ex.getMessage(), ex);
×
274
        }
×
275
    }
×
276
    
277
    /**
278
     * Execute the passed workflow, starting from {@code initialStepIdx}.
279
     * @param wf    The workflow to run.
280
     * @param ctxt  Execution context to run the workflow in.  
281
     * @param initialStepIdx 0-based index of the first step to run.
282
     */
283
    private void executeSteps(Workflow wf, WorkflowContext ctxt, int initialStepIdx ) {
284
        final List<WorkflowStepData> steps = wf.getSteps();
×
285
        
286
        for ( int stepIdx = initialStepIdx; stepIdx < steps.size(); stepIdx++ ) {
×
287
            WorkflowStepData wsd = steps.get(stepIdx);
×
288
            WorkflowStep step = createStep(wsd);
×
289
            WorkflowStepResult res = runStep(step, ctxt);
×
290
            
291
            try {
292
                if (res == WorkflowStepResult.OK) {
×
293
                    logger.log(Level.INFO, "Workflow {0} step {1}: OK", new Object[]{ctxt.getInvocationId(), stepIdx});
×
294
                    em.merge(ctxt.getDataset());
×
295
                    ctxt = refresh(ctxt);
×
296
                } else if (res instanceof Failure) {
×
297
                    logger.log(Level.WARNING, "Workflow {0} failed: {1}", new Object[]{ctxt.getInvocationId(), ((Failure) res).getReason()});
×
298
                    rollback(wf, ctxt, (Failure) res, stepIdx-1 );
×
299
                    return;
×
300

301
                } else if (res instanceof Pending) {
×
302
                    pauseAndAwait(wf, ctxt, (Pending) res, stepIdx);
×
303
                    return;
×
304
                }
305
                
306
            } catch ( Exception e ) {
×
307
                logger.log(Level.WARNING, "Workflow {0} step {1}: Uncought exception:", new Object[]{ctxt.getInvocationId(), e.getMessage()});
×
308
                logger.log(Level.WARNING, "Trace:", e);
×
309
                rollback(wf, ctxt, (Failure) res, stepIdx-1 );
×
310
                return;
×
311
            }
×
312
        }
313
        
314
        workflowCompleted(wf, ctxt);
×
315
        
316
    }
×
317
    
318
    //////////////////////////////////////////////////////////////
319
    // Internal methods to run each step in its own transaction.
320
    //
321
    
322
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
323
    WorkflowStepResult runStep( WorkflowStep step, WorkflowContext ctxt ) {
324
        return step.run(ctxt);
×
325
    }
326
    
327
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
328
    WorkflowStepResult resumeStep( WorkflowStep step, WorkflowContext ctxt, Map<String,String> localData, String externalData ) {
329
        return step.resume(ctxt, localData, externalData);
×
330
    }
331
    
332
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
333
    void rollbackStep( WorkflowStep step, WorkflowContext ctxt, Failure reason ) {
334
        step.rollback(ctxt, reason);
×
335
    }
×
336
    
337
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
338
    void lockDataset(WorkflowContext ctxt, DatasetLock datasetLock) throws CommandException {
339
        /*
340
         * Note that this method directly adds a lock to the database rather than adding
341
         * it via engine.submit(new AddLockCommand(ctxt.getRequest(), ctxt.getDataset(),
342
         * datasetLock)); which would update the dataset's list of locks, etc. An
343
         * em.find() for the dataset would get a Dataset that has an updated list of
344
         * locks, but this copy would not have any changes made in a calling command
345
         * (e.g. for a PostPublication workflow, the fact that the latest version is
346
         * 'released' is not yet in the database.
347
         */
348
        datasetLock.setDataset(ctxt.getDataset());
×
349
        em.persist(datasetLock);
×
350
        //flush creates the id
351
        em.flush();
×
352
        ctxt.setLockId(datasetLock.getId());
×
353
    }
×
354

355
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
356
    void unlockDataset(WorkflowContext ctxt) throws CommandException {
357
        /*
358
         * Since the lockDataset command above directly persists a lock to the database,
359
         * the ctxt.getDataset() is not updated and its list of locks can't be used.
360
         * Using the named query below will find the workflow lock and remove it
361
         * (actually all workflow locks for this Dataset but only one workflow should be
362
         * active).
363
         */
364
        TypedQuery<DatasetLock> lockCounter = em.createNamedQuery("DatasetLock.getLocksByDatasetId", DatasetLock.class);
×
365
        lockCounter.setParameter("datasetId", ctxt.getDataset().getId());
×
366
        List<DatasetLock> locks = lockCounter.getResultList();
×
367
        for (DatasetLock lock : locks) {
×
368
            if (lock.getReason() == DatasetLock.Reason.Workflow) {
×
369
                ctxt.getDataset().removeLock(lock);
×
370
                em.remove(lock);
×
371
            }
372
        }
×
373
        em.flush();
×
374
    }
×
375
    
376
    //
377
    //
378
    //////////////////////////////////////////////////////////////
379
    
380
    private void pauseAndAwait(Workflow wf, WorkflowContext ctxt, Pending pendingRes, int idx) {
381
        PendingWorkflowInvocation pending = new PendingWorkflowInvocation(wf, ctxt, pendingRes);
×
382
        pending.setPendingStepIdx(idx);
×
383
        em.persist(pending);
×
384
    }
×
385

386
    private void workflowCompleted(Workflow wf, WorkflowContext ctxt) {
387
        logger.log(Level.INFO, "Workflow {0} completed.", ctxt.getInvocationId());
×
388
        
389
            try {
390
        if ( ctxt.getType() == TriggerType.PrePublishDataset ) {
×
391
                ctxt = refresh(ctxt);
×
392
                //Now lock for FinalizePublication - this block mirrors that in PublishDatasetCommand
393
                AuthenticatedUser user = ctxt.getRequest().getAuthenticatedUser();
×
394
                DatasetLock lock = new DatasetLock(DatasetLock.Reason.finalizePublication, user);
×
NEW
395
                Dataset dataset = ctxt.getDataset();
×
NEW
396
                lock.setDataset(dataset);
×
397
                boolean registerGlobalIdsForFiles = 
×
NEW
398
                        systemConfig.isFilePIDsEnabledForCollection(ctxt.getDataset().getOwner()) &&
×
NEW
399
                                dvObjects.getEffectivePidGenerator(dataset).canCreatePidsLike(dataset.getGlobalId());
×
400
                
401
                boolean validatePhysicalFiles = systemConfig.isDatafileValidationOnPublishEnabled();
×
402
                String info = "Publishing the dataset; "; 
×
403
                info += registerGlobalIdsForFiles ? "Registering PIDs for Datafiles; " : "";
×
404
                info += validatePhysicalFiles ? "Validating Datafiles Asynchronously" : "";
×
405
                lock.setInfo(info);
×
406
                lockDataset(ctxt, lock);
×
407
                ctxt.getDataset().addLock(lock);
×
408
                
409
                unlockDataset(ctxt);
×
410
                ctxt.setLockId(null); //the workflow lock
×
411
                //Refreshing merges the dataset
412
                ctxt = refresh(ctxt);
×
413
                //Then call Finalize
414
                engine.submit(new FinalizeDatasetPublicationCommand(ctxt.getDataset(), ctxt.getRequest(), ctxt.getDatasetExternallyReleased()));
×
415
            } else {
×
416
                logger.fine("Removing workflow lock");
×
417
                unlockDataset(ctxt);
×
418
            }
419
            } catch (CommandException ex) {
×
420
                logger.log(Level.SEVERE, "Exception finalizing workflow " + ctxt.getInvocationId() +": " + ex.getMessage(), ex);
×
421
                rollback(wf, ctxt, new Failure("Exception while finalizing the publication: " + ex.getMessage()), wf.steps.size()-1);
×
422
            }
×
423
        
424
    }
×
425

426
    public List<Workflow> listWorkflows() {
427
        return em.createNamedQuery("Workflow.listAll").getResultList();
×
428
    }
429

430
    public Optional<Workflow> getWorkflow(long workflowId) {
431
        return Optional.ofNullable(em.find(Workflow.class, workflowId));
×
432
    }
433

434
    public Workflow save(Workflow workflow) {
435
        if (workflow.getId() == null) {
×
436
            em.persist(workflow);
×
437
            em.flush();
×
438
            return workflow;
×
439
        } else {
440
            return em.merge(workflow);
×
441
        }
442
    }
443

444
    /**
445
     * Deletes the workflow with the passed id (if possible).
446
     * @param workflowId id of the workflow to be deleted.
447
     * @return {@code true} iff the workflow was deleted, {@code false} if it was not found.
448
     * @throws IllegalArgumentException iff the workflow is a default workflow.
449
     */
450
    public boolean deleteWorkflow(long workflowId) {
451
        Optional<Workflow> doomedOpt = getWorkflow(workflowId);
×
452
        if (doomedOpt.isPresent()) {
×
453
            // validate that this is not the default workflow
454
            for ( WorkflowContext.TriggerType tp : WorkflowContext.TriggerType.values() ) {
×
455
                String defaultWorkflowId = settings.get(workflowSettingKey(tp));
×
456
                if (defaultWorkflowId != null
×
457
                        && Long.parseLong(defaultWorkflowId) == doomedOpt.get().getId()) {
×
458
                    throw new IllegalArgumentException("Workflow " + workflowId + " cannot be deleted as it is the default workflow for trigger " + tp.name() );
×
459
                }
460
            }
461

462
            em.remove(doomedOpt.get());
×
463
            return true;
×
464
        } else {
465
            return false;
×
466
        }
467
    }
468

469
    public List<PendingWorkflowInvocation> listPendingInvocations() {
470
        return em.createNamedQuery("PendingWorkflowInvocation.listAll")
×
471
                .getResultList();
×
472
    }
473

474
    public PendingWorkflowInvocation getPendingWorkflow(String invocationId) {
475
        return em.find(PendingWorkflowInvocation.class, invocationId);
×
476
    }
477

478
    public Optional<Workflow> getDefaultWorkflow( WorkflowContext.TriggerType type ) {
479
        String defaultWorkflowId = settings.get(workflowSettingKey(type));
×
480
        if (defaultWorkflowId == null) {
×
481
            return Optional.empty();
×
482
        }
483
        return getWorkflow(Long.parseLong(defaultWorkflowId));
×
484
    }
485

486
    /**
487
     * Sets the workflow of the default it.
488
     *
489
     * @param id Id of the default workflow, or {@code null}, for disabling the
490
     * default workflow.
491
     * @param type type of the workflow.
492
     */
493
    public void setDefaultWorkflowId(WorkflowContext.TriggerType type, Long id) {
494
        String workflowKey = workflowSettingKey(type);
×
495
        if (id == null) {
×
496
            settings.delete(workflowKey);
×
497
        } else {
498
            settings.set(workflowKey, id.toString());
×
499
        }
500
    }
×
501

502
    private String workflowSettingKey(WorkflowContext.TriggerType type) {
503
        return WORKFLOW_ID_KEY+type.name();
×
504
    }
505

506
    private WorkflowStep createStep(WorkflowStepData wsd) {
507
        WorkflowStepSPI provider = providers.get(wsd.getProviderId());
×
508
        if (provider == null) {
×
509
            logger.log(Level.SEVERE, "Cannot find a step provider with id ''{0}''", wsd.getProviderId());
×
510
            throw new IllegalArgumentException("Bad WorkflowStepSPI id: '" + wsd.getProviderId() + "'");
×
511
        }
512
        return provider.getStep(wsd.getStepType(), wsd.getStepParameters());
×
513
    }
514
    
515
    private WorkflowContext refresh( WorkflowContext ctxt ) {
516
            return refresh(ctxt, ctxt.getSettings(), ctxt.getApiToken());
×
517
    }
518

519
    private WorkflowContext refresh(WorkflowContext ctxt, Map<String, Object> settings, ApiToken apiToken) {
520
        return refresh(ctxt, settings, apiToken, false);
×
521
    }
522

523
    private WorkflowContext refresh(WorkflowContext ctxt, Map<String, Object> settings, ApiToken apiToken,
524
            boolean findDataset) {
525
        /*
526
         * An earlier version of this class used em.find() to 'refresh' the Dataset in
527
         * the context. For a PostPublication workflow, this had the consequence of
528
         * hiding/removing changes to the Dataset made in the
529
         * FinalizeDatasetPublicationCommand (i.e. the fact that the draft version is
530
         * now released and has a version number). It is not clear to me if the em.merge
531
         * below is needed or if it handles the case of resumed workflows. (The overall
532
         * method is needed to allow the context to be updated in the start() method
533
         * with the settings and APItoken retrieved by the WorkflowServiceBean) - JM -
534
         * 9/18.
535
         */
536
        /*
537
         * Introduced the findDataset boolean to optionally revert above change.
538
         * Refreshing the Dataset just before trying to set the workflow lock greatly
539
         * reduces the number of OptimisticLockExceptions. JvM 2/21
540
         */
541
        WorkflowContext newCtxt;
542
        if (findDataset) {
×
543
            newCtxt = new WorkflowContext(ctxt.getRequest(), datasets.find(ctxt.getDataset().getId()),
×
544
                    ctxt.getNextVersionNumber(), ctxt.getNextMinorVersionNumber(), ctxt.getType(), settings, apiToken,
×
545
                    ctxt.getDatasetExternallyReleased(), ctxt.getInvocationId(), ctxt.getLockId());
×
546
        } else {
547
            newCtxt = new WorkflowContext(ctxt.getRequest(), em.merge(ctxt.getDataset()), ctxt.getNextVersionNumber(),
×
548
                    ctxt.getNextMinorVersionNumber(), ctxt.getType(), settings, apiToken,
×
549
                    ctxt.getDatasetExternallyReleased(), ctxt.getInvocationId(), ctxt.getLockId());
×
550
        }
551
        return newCtxt;
×
552
    }
553

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