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

openmrs / openmrs-core / 30399987753

28 Jul 2026 09:16PM UTC coverage: 66.493% (+0.2%) from 66.313%
30399987753

push

github

ibacher
Fix Mockito inline mocks on JDK 8 and 11 (#6403)

24796 of 37291 relevant lines covered (66.49%)

0.67 hits per line

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

65.82
/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.Task;
33
import org.openmrs.scheduler.timer.TimerSchedulerTask;
34
import org.openmrs.util.OpenmrsThreadPoolHolder;
35
import org.slf4j.Logger;
36
import org.slf4j.LoggerFactory;
37
import org.springframework.context.support.AbstractRefreshableApplicationContext;
38

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

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

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

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

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

56
        /**
57
         * Capability token passed to expected callers that are allowed to act with daemon permissions.
58
         *
59
         * @since 3.0.0, 2.9.0, 2.8.9
60
         */
61
        public static final class CallerKey {
62

63
                private CallerKey() {
64
                }
65
        }
66

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

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

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

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

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

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

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

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

149
                return module;
×
150
        }
151

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

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

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

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

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

199
                return null;
×
200
        }
201

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

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

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

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

246
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
1✔
247

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

253
                return thread;
1✔
254
        }
255

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

272
                return runInDaemonThreadInternal(callable);
1✔
273
        }
274

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

291
                return runInDaemonThreadInternal(runnable);
×
292
        }
293

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

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

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

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

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

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

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

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

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

384
                DaemonThread thread = new DaemonThread() {
×
385

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

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

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

423
                return runInDaemonThreadInternal(callable);
×
424
        }
425

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

440
                return runInDaemonThreadInternal(runnable);
×
441
        }
442

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

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

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

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

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

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

504
                return result;
1✔
505
        }
506

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

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

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

532
        /**
533
         * Executes the given task in a new thread that is authenticated as the daemon user. <br>
534
         * <br>
535
         * This is guarded by the daemon caller key, which is issued only to {@link TimerSchedulerTask}.
536
         *
537
         * @param task the task to run
538
         * @param callerKey the {@link CallerKey} proving the caller is permitted to execute scheduled tasks
539
         * <strong>Should</strong> not be called from other methods other than TimerSchedulerTask
540
         * <strong>Should</strong> not throw error if called from a TimerSchedulerTask class
541
         */
542
        public static void executeScheduledTask(final Task task, CallerKey callerKey) throws Exception {
543
                requireDaemonCaller(callerKey, "Scheduler.timer.task.only");
1✔
544

545
                Future<?> scheduleTaskFuture = runInDaemonThreadInternal(() -> TimerSchedulerTask.execute(task));
1✔
546

547
                // wait for the "executeTaskThread" thread to finish
548
                try {
549
                        scheduleTaskFuture.get();
1✔
550
                } catch (InterruptedException e) {
×
551
                        // ignore
552
                } catch (ExecutionException e) {
×
553
                        if (e.getCause() instanceof Exception) {
×
554
                                throw (Exception) e.getCause();
×
555
                        } else {
556
                                throw new RuntimeException(e.getCause());
×
557
                        }
558
                }
1✔
559
        }
1✔
560

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

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

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

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

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

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

616
        public static String getDaemonUserUuid() {
617
                return DAEMON_USER_UUID;
1✔
618
        }
619
}
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