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

openmrs / openmrs-core / 37033370540

02 Oct 2026 04:21PM UTC coverage: 66.43% (+0.1%) from 66.293%
37033370540

push

github

ibacher
TRUNK-6810: Extend cluster-safety to RolePrivilegeCache (#6627)

281 of 297 new or added lines in 9 files covered. (94.61%)

2 existing lines in 1 file now uncovered.

25927 of 39029 relevant lines covered (66.43%)

0.66 hits per line

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

53.8
/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.GlobalPropertyCache;
26
import org.openmrs.api.cache.RolePrivilegeCache;
27
import org.openmrs.api.db.ContextDAO;
28
import org.openmrs.api.db.hibernate.HibernateContextDAO;
29
import org.openmrs.module.DaemonToken;
30
import org.openmrs.module.Module;
31
import org.openmrs.module.ModuleException;
32
import org.openmrs.module.ModuleFactory;
33
import org.openmrs.scheduler.jobrunr.JobRequestHandlerAdapter;
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 allowed to create DaemonThreads.
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
                JobRequestHandlerAdapter.setDaemonCallerKey(CALLER_KEY);
1 ✔
77
                RolePrivilegeCache.setDaemonCallerKey(CALLER_KEY);
1 ✔
78
                GlobalPropertyCache.setDaemonCallerKey(CALLER_KEY);
1 ✔
79
                // WebDaemon lives in the web module, which the api module cannot reference at compile time, so
80
                // hand it the key reflectively.
81
                try {
82
                        Class.forName("org.openmrs.web.WebDaemon").getMethod("setDaemonCallerKey", CallerKey.class).invoke(null,
×
83
                            CALLER_KEY);
84
                }
85
                catch (ClassNotFoundException e) {
1 ✔
86
                        log.debug("Could not load WebDaemon class", e);
1 ✔
87
                }
88
                catch (ReflectiveOperationException e) {
×
89
                        log.error("Exception caught while trying to provide DaemonCallerKey to WebDaemon", e);
×
90
                }
1 ✔
91
        }
1 ✔
92

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

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

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

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

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

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

150
                return module;
×
151
        }
152

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

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

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

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

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

200
                return null;
×
201
        }
202

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

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

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

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

247
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
×
248

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

254
                return thread;
×
255
        }
256

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

273
                return runInDaemonThreadInternal(callable);
×
274
        }
275

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

292
                return runInDaemonThreadInternal(runnable);
×
293
        }
294

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

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

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

UNCOV
327
                return runInDaemonThreadInternal(callable);
×
328
        }
329

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

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

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

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

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

385
                DaemonThread thread = new DaemonThread() {
×
386

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

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

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

424
                return runInDaemonThreadInternal(callable);
×
425
        }
426

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

441
                return runInDaemonThreadInternal(runnable);
×
442
        }
443

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

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

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

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

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

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

505
                return result;
1 ✔
506
        }
507

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

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

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

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

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

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

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

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

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

588
        @FunctionalInterface
589
        public interface DaemonTask {
590

591
                void run() throws Exception;
592
        }
593

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

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

623
        public static String getDaemonUserUuid() {
624
                return DAEMON_USER_UUID;
1 ✔
625
        }
626
}
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