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

openmrs / openmrs-core / 30399801741

28 Jul 2026 09:13PM UTC coverage: 64.333% (+0.2%) from 64.136%
30399801741

push

github

web-flow
Fix Mockito inline mocks on JDK 8 and 11 (#6403)

24619 of 38268 relevant lines covered (64.33%)

0.64 hits per line

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

54.78
/api/src/main/java/org/openmrs/api/context/Daemon.java
1
/**
2
 * This Source Code Form is subject to the terms of the Mozilla Public License,
3
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
4
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
5
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
6
 *
7
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
8
 * graphic logo is a trademark of OpenMRS Inc.
9
 */
10
package org.openmrs.api.context;
11

12
import java.util.List;
13
import java.util.concurrent.Callable;
14
import java.util.concurrent.CountDownLatch;
15
import java.util.concurrent.ExecutionException;
16
import java.util.concurrent.Future;
17
import java.util.stream.Collectors;
18

19
import org.apache.commons.collections.CollectionUtils;
20
import org.openmrs.Role;
21
import org.openmrs.User;
22
import org.openmrs.api.APIAuthenticationException;
23
import org.openmrs.api.APIException;
24
import org.openmrs.api.OpenmrsService;
25
import org.openmrs.api.cache.RolePrivilegeCache;
26
import org.openmrs.api.db.ContextDAO;
27
import org.openmrs.api.db.hibernate.HibernateContextDAO;
28
import org.openmrs.module.DaemonToken;
29
import org.openmrs.module.Module;
30
import org.openmrs.module.ModuleException;
31
import org.openmrs.module.ModuleFactory;
32
import org.openmrs.scheduler.jobrunr.JobRequestHandlerAdapter;
33
import org.openmrs.util.OpenmrsThreadPoolHolder;
34
import org.slf4j.Logger;
35
import org.slf4j.LoggerFactory;
36
import org.springframework.context.support.AbstractRefreshableApplicationContext;
37

38
/**
39
 * This class allows certain tasks to run with elevated privileges. Primary use is scheduling and
40
 * module startup when there is no user to authenticate as.
41
 */
42
public final class Daemon {
43

44
        /**
45
         * The uuid defined for the daemon user object
46
         */
47
        static final String DAEMON_USER_UUID = "A4F30A1B-5EB9-11DF-A648-37A07F9C90FB";
48

49
        private static final ThreadLocal<Boolean> isDaemonThread = new ThreadLocal<>();
1✔
50

51
        private static final ThreadLocal<User> daemonThreadUser = new ThreadLocal<>();
1✔
52

53
        private static final Logger log = LoggerFactory.getLogger("org.openmrs.api");
1✔
54

55
        /**
56
         * Capability token passed to expected callers allowed to create DaemonThreads.
57
         *
58
         * @since 3.0.0, 2.9.0, 2.8.9
59
         */
60
        public static final class CallerKey {
61

62
                private CallerKey() {
63
                }
64
        }
65

66
        /**
67
         * The single {@link CallerKey} instance. Must not be exposed to the public API
68
         */
69
        private static final CallerKey CALLER_KEY = new CallerKey();
1✔
70

71
        static {
72
                // send the key to our known collaborators
73
                HibernateContextDAO.setDaemonCallerKey(CALLER_KEY);
1✔
74
                ModuleFactory.setDaemonCallerKey(CALLER_KEY);
1✔
75
                JobRequestHandlerAdapter.setDaemonCallerKey(CALLER_KEY);
1✔
76
                RolePrivilegeCache.setDaemonCallerKey(CALLER_KEY);
1✔
77
                // WebDaemon lives in the web module, which the api module cannot reference at compile time, so
78
                // hand it the key reflectively.
79
                try {
80
                        Class.forName("org.openmrs.web.WebDaemon").getMethod("setDaemonCallerKey", CallerKey.class).invoke(null,
×
81
                            CALLER_KEY);
82
                }
83
                catch (ClassNotFoundException e) {
1✔
84
                        log.debug("Could not load WebDaemon class", e);
1✔
85
                }
86
                catch (ReflectiveOperationException e) {
×
87
                        log.error("Exception caught while trying to provide DaemonCallerKey to WebDaemon", e);
×
88
                }
1✔
89
        }
1✔
90

91
        /**
92
         * private constructor to override the default constructor to prevent it from being instantiated.
93
         */
94
        private Daemon() {
95
        }
96

97
        /**
98
         * Forces this class to initialize, which distributes the caller key to its trusted collaborators.
99
         *
100
         * @since 3.0.0, 2.9.0, 2.8.9
101
         */
102
        public static void ensureInitialized() {
103
                // Merely invoking a static method guarantees Daemon's static initializer has run; there is
104
                // deliberately nothing else to do here.
105
        }
×
106

107
        /**
108
         * @see #startModule(Module, boolean, AbstractRefreshableApplicationContext, CallerKey)
109
         */
110
        public static Module startModule(Module module) throws ModuleException {
111
                return startModule(module, false, null, CALLER_KEY);
×
112
        }
113

114
        /**
115
         * This method should not be called directly. The {@link ModuleFactory#startModule(Module)} method
116
         * uses this to start the given module in a new thread that is authenticated as the daemon user.
117
         * <br>
118
         * If a non-null application context is passed in, it gets refreshed to make the module's services
119
         * available
120
         *
121
         * @param module the module to start
122
         * @param isOpenmrsStartup Specifies whether this module is being started at application startup or
123
         *            not
124
         * @param applicationContext the spring application context instance to refresh
125
         * @param callerKey the {@link CallerKey} proving the caller is permitted to start modules
126
         * @return the module returned from {@link ModuleFactory#startModuleInternal(Module)}
127
         */
128
        public static Module startModule(final Module module, final boolean isOpenmrsStartup,
129
                final AbstractRefreshableApplicationContext applicationContext, CallerKey callerKey) throws ModuleException {
130
                requireDaemonCaller(callerKey, "Module.factory.only");
1✔
131

132
                Future<Module> moduleStartFuture = runInDaemonThreadInternal(
1✔
133
                    () -> ModuleFactory.startModuleInternal(module, isOpenmrsStartup, applicationContext));
1✔
134

135
                // wait for the "startModule" thread to finish
136
                try {
137
                        return moduleStartFuture.get();
1✔
138
                } catch (InterruptedException e) {
×
139
                        // ignore
140
                } catch (ExecutionException e) {
×
141
                        if (e.getCause() instanceof ModuleException) {
×
142
                                throw (ModuleException) e.getCause();
×
143
                        } else {
144
                                throw new ModuleException("Unable to start module " + module.getName(), e);
×
145
                        }
146
                }
×
147

148
                return module;
×
149
        }
150

151
        /**
152
         * This method should not be called directly; it is guarded by the daemon caller key, which is
153
         * issued only to the {@link ContextDAO} implementation.
154
         * <p>
155
         * <strong>Should</strong> only allow the creation of new users, not the edition of existing ones
156
         *
157
         * @param user A new user to be created.
158
         * @param password The password to set for the new user.
159
         * @param roleNames A list of role names to fetch the roles to add to the user.
160
         * @param callerKey the {@link CallerKey} proving the caller is permitted to create users
161
         * @return The newly created user
162
         * @since 2.3.0
163
         */
164
        public static User createUser(User user, String password, List<String> roleNames, CallerKey callerKey) throws Exception {
165
                requireDaemonCaller(callerKey, "Context.DAO.only");
1✔
166

167
                // create a new thread and execute that task in it
168
                Future<User> userFuture = runInDaemonThreadInternal(() -> {
1✔
169
                        if ((user.getId() != null && Context.getUserService().getUser(user.getId()) != null)
1✔
170
                                || Context.getUserService().getUserByUuid(user.getUuid()) != null
1✔
171
                                || Context.getUserService().getUserByUsername(user.getUsername()) != null || (user.getEmail() != null
1✔
172
                                        && Context.getUserService().getUserByUsernameOrEmail(user.getEmail()) != null)) {
×
173
                                throw new APIException("User.creating.already.exists", new Object[] { user.getDisplayString() });
1✔
174
                        }
175

176
                        if (!CollectionUtils.isEmpty(roleNames)) {
1✔
177
                                List<Role> roles = roleNames.stream().map(roleName -> Context.getUserService().getRole(roleName))
1✔
178
                                        .collect(Collectors.toList());
1✔
179
                                roles.forEach(user::addRole);
1✔
180
                        }
181

182
                        return Context.getUserService().createUser(user, password);
1✔
183
                });
184

185
                // wait for the 'create user' thread to finish
186
                try {
187
                        return userFuture.get();
1✔
188
                } catch (InterruptedException e) {
×
189
                        // ignore
190
                } catch (ExecutionException e) {
1✔
191
                        if (e.getCause() instanceof Exception) {
1✔
192
                                throw (Exception) e.getCause();
1✔
193
                        } else {
194
                                throw new RuntimeException(e.getCause());
×
195
                        }
196
                }
×
197

198
                return null;
×
199
        }
200

201
        /**
202
         * Call this method if you are inside a Daemon thread (for example in a Module activator or a
203
         * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
204
         * method from a Daemon thread.
205
         *
206
         * @param runnable what to run in a new thread
207
         * @return the newly spawned {@link Thread}
208
         * @deprecated As of 2.7.0, consider using {@link #runNewDaemonTask(Runnable)} instead
209
         */
210
        @Deprecated
211
        public static Thread runInNewDaemonThread(final Runnable runnable) {
212
                // make sure we're already in a daemon thread
213
                if (!isDaemonThread()) {
1✔
214
                        throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
1✔
215
                }
216

217
                // the previous implementation ensured that Thread.start() was called before this function returned
218
                // since we cannot guarantee that the executor will run the thread when `execute()` is called, we need another
219
                // mechanism to ensure the submitted Runnable was actually started.
220
                final CountDownLatch countDownLatch = new CountDownLatch(1);
×
221

222
                // we should consider making DaemonThread public, so the caller can access returnedObject and exceptionThrown
223
                DaemonThread thread = new DaemonThread() {
×
224

225
                        @Override
226
                        public void run() {
227
                                isDaemonThread.set(true);
×
228
                                try {
229
                                        Context.openSession();
×
230
                                        countDownLatch.countDown();
×
231
                                        //Suppressing sonar issue "squid:S1217"
232
                                        //We intentionally do not start a new thread yet, rather wrap the run call in a session.
233
                                        runnable.run();
×
234
                                } finally {
235
                                        try {
236
                                                Context.closeSession();
×
237
                                        } finally {
238
                                                isDaemonThread.remove();
×
239
                                                daemonThreadUser.remove();
×
240
                                        }
241
                                }
242
                        }
×
243
                };
244

245
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
×
246

247
                // do not return until the thread is actually started to emulate the previous behaviour
248
                try {
249
                        countDownLatch.await();
×
250
                } catch (InterruptedException ignored) {}
×
251

252
                return thread;
×
253
        }
254

255
        /**
256
         * Call this method if you are inside a Daemon thread (for example in a Module activator or a
257
         * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
258
         * method from a Daemon thread.
259
         *
260
         * @param callable what to run in a new thread
261
         * @return a future that completes when the task is done;
262
         * @since 2.7.0
263
         */
264
        @SuppressWarnings({ "squid:S1217", "unused" })
265
        public static <T> Future<T> runInNewDaemonThread(final Callable<T> callable) {
266
                // make sure we're already in a daemon thread
267
                if (!isDaemonThread()) {
1✔
268
                        throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
1✔
269
                }
270

271
                return runInDaemonThreadInternal(callable);
×
272
        }
273

274
        /**
275
         * Call this method if you are inside a Daemon thread (for example in a Module activator or a
276
         * scheduled task) and you want to start up a new parallel Daemon thread. You may only call this
277
         * method from a Daemon thread.
278
         *
279
         * @param runnable what to run in a new thread
280
         * @return a future that completes when the task is done;
281
         * @since 2.7.0
282
         */
283
        @SuppressWarnings({ "squid:S1217", "unused" })
284
        public static Future<?> runNewDaemonTask(final Runnable runnable) {
285
                // make sure we're already in a daemon thread
286
                if (!isDaemonThread()) {
1✔
287
                        throw new APIAuthenticationException("Only daemon threads can spawn new daemon threads");
1✔
288
                }
289

290
                return runInDaemonThreadInternal(runnable);
×
291
        }
292

293
        /**
294
         * Runs the given task on a new daemon thread, authorized by a {@link CallerKey}. This exists
295
         * strictly for internal use by trusted core entry points (such as {@code WebDaemon} startup) that
296
         * must launch daemon work from a non-daemon thread.
297
         *
298
         * @param runnable what to run in a new daemon thread
299
         * @param callerKey the {@link CallerKey} proving the caller is a trusted daemon entry point
300
         * @return a future that completes when the task is done
301
         * @since 3.0.0, 2.9.0, 2.8.9
302
         */
303
        @SuppressWarnings({ "squid:S1217", "unused" })
304
        public static Future<?> runNewDaemonTask(final Runnable runnable, CallerKey callerKey) {
305
                requireDaemonCaller(callerKey, "runNewDaemonTask can only be called by an authorized daemon entry point");
1✔
306

307
                return runInDaemonThreadInternal(runnable);
1✔
308
        }
309

310
        /**
311
         * Runs the given task on a new daemon thread, authorized by a {@link CallerKey}, and returns a
312
         * {@link Future} for its result without blocking. This exists strictly for internal use by trusted
313
         * core entry points that must launch daemon work from a non-daemon thread and want to await the
314
         * result themselves (for example coalescing concurrent callers onto a single future).
315
         *
316
         * @param callable what to run in a new daemon thread
317
         * @param callerKey the {@link CallerKey} proving the caller is a trusted daemon entry point
318
         * @return a future that completes with the task's result
319
         * @since 3.0.0, 2.9.0, 2.8.9
320
         */
321
        @SuppressWarnings("squid:S1217")
322
        public static <T> Future<T> runNewDaemonTask(final Callable<T> callable, CallerKey callerKey) {
323
                requireDaemonCaller(callerKey, "runNewDaemonTask can only be called by an authorized daemon entry point");
1✔
324

325
                return runInDaemonThreadInternal(callable);
1✔
326
        }
327

328
        /**
329
         * @return true if the current thread was started by this class and so is a daemon thread that has
330
         *         all privileges
331
         * @see Context#hasPrivilege(String)
332
         */
333
        public static boolean isDaemonThread() {
334
                Boolean b = isDaemonThread.get();
1✔
335
                return b != null && b;
1✔
336
        }
337

338
        /**
339
         * Calls the {@link OpenmrsService#onStartup()} method, as a daemon, for an instance implementing
340
         * the {@link OpenmrsService} interface.
341
         *
342
         * @param service instance implementing the {@link OpenmrsService} interface.
343
         * @param callerKey the {@link CallerKey} proving the caller is permitted to run service startup
344
         * @since 1.9
345
         */
346
        public static void runStartupForService(final OpenmrsService service, CallerKey callerKey) throws ModuleException {
347
                requireDaemonCaller(callerKey, "Service.context.only");
1✔
348

349
                Future<?> future = runInDaemonThreadInternal(service::onStartup);
1✔
350

351
                // wait for the "onStartup" thread to finish
352
                try {
353
                        future.get();
1✔
354
                } catch (InterruptedException e) {
×
355
                        // ignore
356
                } catch (ExecutionException e) {
×
357
                        if (e.getCause() instanceof ModuleException) {
×
358
                                throw (ModuleException) e.getCause();
×
359
                        } else {
360
                                throw new ModuleException("Unable to run onStartup() method of service {}",
×
361
                                        service.getClass().getSimpleName(), e);
×
362
                        }
363
                }
1✔
364
        }
1✔
365

366
        /**
367
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
368
         *
369
         * @param runnable an object implementing the {@link Runnable} interface.
370
         * @param token the token required to run code as the daemon user
371
         * @return the newly spawned {@link Thread}
372
         * @since 1.9.2
373
         * @deprecated Since 2.7.0 use {@link #runInDaemonThreadWithoutResult(Runnable, DaemonToken)}
374
         *             instead
375
         */
376
        @Deprecated
377
        @SuppressWarnings({ "squid:S1217", "unused" })
378
        public static Thread runInDaemonThread(final Runnable runnable, DaemonToken token) {
379
                if (!ModuleFactory.isTokenValid(token)) {
×
380
                        throw new ContextAuthenticationException("Invalid token " + token);
×
381
                }
382

383
                DaemonThread thread = new DaemonThread() {
×
384

385
                        @Override
386
                        public void run() {
387
                                isDaemonThread.set(true);
×
388
                                try {
389
                                        Context.openSession();
×
390
                                        //Suppressing sonar issue "squid:S1217"
391
                                        //We intentionally do not start a new thread yet, rather wrap the run call in a session.
392
                                        runnable.run();
×
393
                                } finally {
394
                                        try {
395
                                                Context.closeSession();
×
396
                                        } finally {
397
                                                isDaemonThread.remove();
×
398
                                                daemonThreadUser.remove();
×
399
                                        }
400
                                }
401
                        }
×
402
                };
403

404
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
×
405
                return thread;
×
406
        }
407

408
        /**
409
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
410
         *
411
         * @param callable an object implementing the {@link Callable<T>} interface to be run
412
         * @param token the token required to run code as the daemon user
413
         * @return the newly spawned {@link Thread}
414
         * @since 2.7.0
415
         */
416
        @SuppressWarnings({ "squid:S1217", "unused" })
417
        public static <T> Future<T> runInDaemonThread(final Callable<T> callable, DaemonToken token) {
418
                if (!ModuleFactory.isTokenValid(token)) {
×
419
                        throw new ContextAuthenticationException("Invalid token");
×
420
                }
421

422
                return runInDaemonThreadInternal(callable);
×
423
        }
424

425
        /**
426
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
427
         *
428
         * @param runnable an object implementing the {@link Runnable} interface to be run
429
         * @param token the token required to run code as the daemon user
430
         * @return the newly spawned {@link Thread}
431
         * @since 2.7.0
432
         */
433
        @SuppressWarnings("squid:S1217")
434
        public static Future<?> runInDaemonThreadWithoutResult(final Runnable runnable, DaemonToken token) {
435
                if (!ModuleFactory.isTokenValid(token)) {
×
436
                        throw new ContextAuthenticationException("Invalid token");
×
437
                }
438

439
                return runInDaemonThreadInternal(runnable);
×
440
        }
441

442
        /**
443
         * Executes the given runnable in a new thread that is authenticated as the daemon user and wait for
444
         * the thread to finish.
445
         *
446
         * @param runnable an object implementing the {@link Runnable} interface.
447
         * @param token the token required to run code as the daemon user
448
         * @since 2.7.0
449
         */
450
        public static void runInDaemonThreadAndWait(final Runnable runnable, DaemonToken token) {
451
                Future<?> daemonThread = runInDaemonThreadWithoutResult(runnable, token);
×
452

453
                try {
454
                        daemonThread.get();
×
455
                } catch (InterruptedException | ExecutionException e) {
×
456
                        // Ignored
457
                }
×
458
        }
×
459

460
        private static <T> Future<T> runInDaemonThreadInternal(Callable<T> callable) {
461
                return OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
1✔
462
                        isDaemonThread.set(true);
1✔
463
                        try {
464
                                Context.openSession();
1✔
465
                                return callable.call();
1✔
466
                        } finally {
467
                                try {
468
                                        Context.closeSession();
1✔
469
                                } finally {
470
                                        isDaemonThread.remove();
1✔
471
                                        daemonThreadUser.remove();
1✔
472
                                }
473
                        }
474
                });
475
        }
476

477
        private static Future<?> runInDaemonThreadInternal(Runnable runnable) {
478
                // for Threads, we used to guarantee that Thread.start() was called before the function returned
479
                // since we cannot guarantee that the executor actually started executing the thread, we use a CountDownLatch
480
                // to emulate this behaviour when the user submits a Thread. Other runnables are unaffected.
481
                CountDownLatch countDownLatch = getCountDownLatch(runnable instanceof Thread);
1✔
482

483
                Future<?> result = OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
1✔
484
                        isDaemonThread.set(true);
1✔
485
                        try {
486
                                Context.openSession();
1✔
487
                                countDownLatch.countDown();
1✔
488
                                runnable.run();
1✔
489
                        } finally {
490
                                try {
491
                                        Context.closeSession();
1✔
492
                                } finally {
493
                                        isDaemonThread.remove();
1✔
494
                                        daemonThreadUser.remove();
1✔
495
                                }
496
                        }
497
                });
1✔
498

499
                try {
500
                        countDownLatch.await();
1✔
501
                } catch (InterruptedException ignored) {}
1✔
502

503
                return result;
1✔
504
        }
505

506
        private static CountDownLatch getCountDownLatch(boolean isThread) {
507
                return isThread ? new CountDownLatch(1) : new CountDownLatch(0);
1✔
508
        }
509

510
        /**
511
         * @return the capability required to invoke Daemon's guarded entry points. Package-private, so it
512
         *         is reachable only by trusted collaborators.
513
         */
514
        static CallerKey callerKey() {
515
                return CALLER_KEY;
1✔
516
        }
517

518
        /**
519
         * Rejects the call unless the supplied key is the genuine {@link #CALLER_KEY}. Fails closed for any
520
         * other value (including null).
521
         *
522
         * @param callerKey the key presented by the caller
523
         * @param messageCode the message (code) identifying the guarded operation
524
         */
525
        private static void requireDaemonCaller(CallerKey callerKey, String messageCode) {
526
                if (callerKey != CALLER_KEY) {
1✔
527
                        throw new APIException(messageCode, new Object[] { "an unauthorized caller" });
1✔
528
                }
529
        }
1✔
530

531
        /**
532
         * Executes the given task as the given user. <br>
533
         * <br>
534
         * This is guarded by the daemon caller key, which is issued only to {@link JobRequestHandlerAdapter}.
535
         * <p>
536
         * <strong>Should</strong> not be called from other methods other than JobRequestHandlerAdapter
537
         * <strong>Should</strong> not throw error if called from a JobRequestHandlerAdapter class
538
         *
539
         * @param userSystemId the user to run as
540
         * @param runnable the task to run
541
         * @param callerKey the {@link CallerKey} proving the caller is permitted to execute scheduled tasks
542
         * @since 2.9.0
543
         */
544
        public static void executeScheduledTaskAsUser(String userSystemId, DaemonTask runnable, CallerKey callerKey)
545
                throws Exception {
546
                requireDaemonCaller(callerKey, "executeScheduledTaskAsUser can only be called from JobRequestHandlerAdapter");
1✔
547

548
                isDaemonThread.set(true);
1✔
549
                try {
550
                        Context.openSession();
1✔
551
                        Context.getUserContext().becomeUser(userSystemId);
×
552
                        isDaemonThread.remove();
×
553
                        runnable.run();
×
554
                } finally {
555
                        isDaemonThread.remove();
1✔
556
                        Context.closeSession();
1✔
557
                }
558
        }
×
559

560
        /**
561
         * Thread class used by the {@link Daemon#startModule(Module)} method so that the returned object
562
         * and the exception thrown can be returned to calling class
563
         */
564
        protected static class DaemonThread extends Thread {
×
565

566
                /**
567
                 * The object returned from the method called in {@link #run()}
568
                 */
569
                protected Object returnedObject = null;
×
570

571
                /**
572
                 * The exception thrown (if any) by the method called in {@link #run()}
573
                 */
574
                protected Exception exceptionThrown = null;
×
575

576
                /**
577
                 * Gets the exception thrown (if any) by the method called in {@link #run()}
578
                 *
579
                 * @return the thrown exception (if any).
580
                 */
581
                public Exception getExceptionThrown() {
582
                        return exceptionThrown;
×
583
                }
584
        }
585

586
        @FunctionalInterface
587
        public interface DaemonTask {
588

589
                void run() throws Exception;
590
        }
591

592
        /**
593
         * Checks whether user is Daemon. However, this is not the preferred method for checking to see if
594
         * the current thread is a daemon thread, rather use {@link #isDaemonThread()}. isDaemonThread is
595
         * preferred for checking to see if you are in that thread or if the current thread is daemon.
596
         *
597
         * @param user user whom we are checking if daemon
598
         * @return true if user is Daemon
599
         */
600
        public static boolean isDaemonUser(User user) {
601
                return DAEMON_USER_UUID.equals(user.getUuid());
1✔
602
        }
603

604
        /**
605
         * @return the current thread daemon user or null if not assigned
606
         * @since 2.0.0, 1.12.0, 1.11.6, 1.10.4, 1.9.11
607
         */
608
        public static User getDaemonThreadUser() {
609
                if (isDaemonThread()) {
1✔
610
                        User user = daemonThreadUser.get();
1✔
611
                        if (user == null) {
1✔
612
                                user = Context.getContextDAO().getUserByUuid(DAEMON_USER_UUID);
1✔
613
                                daemonThreadUser.set(user);
1✔
614
                        }
615
                        return user;
1✔
616
                } else {
617
                        return null;
×
618
                }
619
        }
620

621
        public static String getDaemonUserUuid() {
622
                return DAEMON_USER_UUID;
1✔
623
        }
624
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc