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

openmrs / openmrs-core / 29845974054

21 Jul 2026 03:51PM UTC coverage: 66.106% (-0.004%) from 66.11%
29845974054

push

github

web-flow
TRUNK-6713: Switch to saner DaemonThread protection scheme (#6361)

60 of 90 new or added lines in 6 files covered. (66.67%)

6 existing lines in 4 files now uncovered.

24347 of 36830 relevant lines covered (66.11%)

0.66 hits per line

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

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

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

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

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

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

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

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

147
                return module;
×
148
        }
149

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

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

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

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

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

197
                return null;
×
198
        }
199

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

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

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

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

244
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
1✔
245

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

251
                return thread;
1✔
252
        }
253

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

270
                return runInDaemonThreadInternal(callable);
1✔
271
        }
272

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

NEW
289
                return runInDaemonThreadInternal(runnable);
×
290
        }
291

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

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

309
        /**
310
         * @return true if the current thread was started by this class and so is a daemon thread that has
311
         *         all privileges
312
         * @see Context#hasPrivilege(String)
313
         */
314
        public static boolean isDaemonThread() {
315
                Boolean b = isDaemonThread.get();
1✔
316
                return b != null && b;
1✔
317
        }
318

319
        /**
320
         * Calls the {@link OpenmrsService#onStartup()} method, as a daemon, for an instance implementing
321
         * the {@link OpenmrsService} interface.
322
         *
323
         * @param service instance implementing the {@link OpenmrsService} interface.
324
         * @param callerKey the {@link CallerKey} proving the caller is permitted to run service startup
325
         * @since 1.9
326
         */
327
        public static void runStartupForService(final OpenmrsService service, CallerKey callerKey) throws ModuleException {
328
                requireDaemonCaller(callerKey, "Service.context.only");
1✔
329

330
                Future<?> future = runInDaemonThreadInternal(service::onStartup);
1✔
331

332
                // wait for the "onStartup" thread to finish
333
                try {
334
                        future.get();
1✔
NEW
335
                } catch (InterruptedException e) {
×
336
                        // ignore
NEW
337
                } catch (ExecutionException e) {
×
338
                        if (e.getCause() instanceof ModuleException) {
×
339
                                throw (ModuleException) e.getCause();
×
340
                        } else {
NEW
341
                                throw new ModuleException("Unable to run onStartup() method of service {}",
×
NEW
342
                                        service.getClass().getSimpleName(), e);
×
343
                        }
344
                }
1✔
345
        }
1✔
346

347
        /**
348
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
349
         *
350
         * @param runnable an object implementing the {@link Runnable} interface.
351
         * @param token the token required to run code as the daemon user
352
         * @return the newly spawned {@link Thread}
353
         * @since 1.9.2
354
         * @deprecated Since 2.7.0 use {@link #runInDaemonThreadWithoutResult(Runnable, DaemonToken)}
355
         *             instead
356
         */
357
        @Deprecated
358
        @SuppressWarnings({ "squid:S1217", "unused" })
359
        public static Thread runInDaemonThread(final Runnable runnable, DaemonToken token) {
360
                if (!ModuleFactory.isTokenValid(token)) {
×
361
                        throw new ContextAuthenticationException("Invalid token " + token);
×
362
                }
363

364
                DaemonThread thread = new DaemonThread() {
×
365

366
                        @Override
367
                        public void run() {
368
                                isDaemonThread.set(true);
×
369
                                try {
370
                                        Context.openSession();
×
371
                                        //Suppressing sonar issue "squid:S1217"
372
                                        //We intentionally do not start a new thread yet, rather wrap the run call in a session.
373
                                        runnable.run();
×
374
                                } finally {
375
                                        try {
376
                                                Context.closeSession();
×
377
                                        } finally {
378
                                                isDaemonThread.remove();
×
379
                                                daemonThreadUser.remove();
×
380
                                        }
381
                                }
382
                        }
×
383
                };
384

385
                OpenmrsThreadPoolHolder.threadExecutor.execute(thread);
×
386
                return thread;
×
387
        }
388

389
        /**
390
         * Executes the given runnable in a new thread that is authenticated as the daemon user.
391
         *
392
         * @param callable an object implementing the {@link Callable<T>} interface to be run
393
         * @param token the token required to run code as the daemon user
394
         * @return the newly spawned {@link Thread}
395
         * @since 2.7.0
396
         */
397
        @SuppressWarnings({ "squid:S1217", "unused" })
398
        public static <T> Future<T> runInDaemonThread(final Callable<T> callable, DaemonToken token) {
399
                if (!ModuleFactory.isTokenValid(token)) {
×
400
                        throw new ContextAuthenticationException("Invalid token");
×
401
                }
402

403
                return runInDaemonThreadInternal(callable);
×
404
        }
405

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

420
                return runInDaemonThreadInternal(runnable);
×
421
        }
422

423
        /**
424
         * Executes the given runnable in a new thread that is authenticated as the daemon user and wait for
425
         * the thread to finish.
426
         *
427
         * @param runnable an object implementing the {@link Runnable} interface.
428
         * @param token the token required to run code as the daemon user
429
         * @since 2.7.0
430
         */
431
        public static void runInDaemonThreadAndWait(final Runnable runnable, DaemonToken token) {
432
                Future<?> daemonThread = runInDaemonThreadWithoutResult(runnable, token);
×
433

434
                try {
435
                        daemonThread.get();
×
NEW
436
                } catch (InterruptedException | ExecutionException e) {
×
437
                        // Ignored
438
                }
×
439
        }
×
440

441
        private static <T> Future<T> runInDaemonThreadInternal(Callable<T> callable) {
442
                return OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
1✔
443
                        isDaemonThread.set(true);
1✔
444
                        try {
445
                                Context.openSession();
1✔
446
                                return callable.call();
1✔
447
                        } finally {
448
                                try {
449
                                        Context.closeSession();
1✔
450
                                } finally {
451
                                        isDaemonThread.remove();
1✔
452
                                        daemonThreadUser.remove();
1✔
453
                                }
454
                        }
455
                });
456
        }
457

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

464
                Future<?> result = OpenmrsThreadPoolHolder.threadExecutor.submit(() -> {
1✔
465
                        isDaemonThread.set(true);
1✔
466
                        try {
467
                                Context.openSession();
1✔
468
                                countDownLatch.countDown();
1✔
469
                                runnable.run();
1✔
470
                        } finally {
471
                                try {
472
                                        Context.closeSession();
1✔
473
                                } finally {
474
                                        isDaemonThread.remove();
1✔
475
                                        daemonThreadUser.remove();
1✔
476
                                }
477
                        }
478
                });
1✔
479

480
                try {
481
                        countDownLatch.await();
1✔
482
                } catch (InterruptedException ignored) {}
1✔
483

484
                return result;
1✔
485
        }
486

487
        private static CountDownLatch getCountDownLatch(boolean isThread) {
488
                return isThread ? new CountDownLatch(1) : new CountDownLatch(0);
1✔
489
        }
490

491
        /**
492
         * @return the capability required to invoke Daemon's guarded entry points. Package-private, so it
493
         *         is reachable only by trusted collaborators.
494
         */
495
        static CallerKey callerKey() {
496
                return CALLER_KEY;
1✔
497
        }
498

499
        /**
500
         * Rejects the call unless the supplied key is the genuine {@link #CALLER_KEY}. Fails closed for any
501
         * other value (including null).
502
         *
503
         * @param callerKey the key presented by the caller
504
         * @param messageCode the message (code) identifying the guarded operation
505
         */
506
        private static void requireDaemonCaller(CallerKey callerKey, String messageCode) {
507
                if (callerKey != CALLER_KEY) {
1✔
508
                        throw new APIException(messageCode, new Object[] { "an unauthorized caller" });
1✔
509
                }
510
        }
1✔
511

512
        /**
513
         * Executes the given task in a new thread that is authenticated as the daemon user. <br>
514
         * <br>
515
         * This is guarded by the daemon caller key, which is issued only to {@link TimerSchedulerTask}.
516
         *
517
         * @param task the task to run
518
         * @param callerKey the {@link CallerKey} proving the caller is permitted to execute scheduled tasks
519
         * <strong>Should</strong> not be called from other methods other than TimerSchedulerTask
520
         * <strong>Should</strong> not throw error if called from a TimerSchedulerTask class
521
         */
522
        public static void executeScheduledTask(final Task task, CallerKey callerKey) throws Exception {
523
                requireDaemonCaller(callerKey, "Scheduler.timer.task.only");
1✔
524

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

527
                // wait for the "executeTaskThread" thread to finish
528
                try {
529
                        scheduleTaskFuture.get();
1✔
NEW
530
                } catch (InterruptedException e) {
×
531
                        // ignore
NEW
532
                } catch (ExecutionException e) {
×
NEW
533
                        if (e.getCause() instanceof Exception) {
×
NEW
534
                                throw (Exception) e.getCause();
×
535
                        } else {
NEW
536
                                throw new RuntimeException(e.getCause());
×
537
                        }
538
                }
1✔
539
        }
1✔
540

541
        /**
542
         * Thread class used by the {@link Daemon#startModule(Module)} method so that the returned object
543
         * and the exception thrown can be returned to calling class
544
         */
545
        protected static class DaemonThread extends Thread {
1✔
546

547
                /**
548
                 * The object returned from the method called in {@link #run()}
549
                 */
550
                protected Object returnedObject = null;
1✔
551

552
                /**
553
                 * The exception thrown (if any) by the method called in {@link #run()}
554
                 */
555
                protected Exception exceptionThrown = null;
1✔
556

557
                /**
558
                 * Gets the exception thrown (if any) by the method called in {@link #run()}
559
                 *
560
                 * @return the thrown exception (if any).
561
                 */
562
                public Exception getExceptionThrown() {
563
                        return exceptionThrown;
×
564
                }
565
        }
566

567
        /**
568
         * Checks whether user is Daemon. However, this is not the preferred method for checking to see if
569
         * the current thread is a daemon thread, rather use {@link #isDaemonThread()}. isDaemonThread is
570
         * preferred for checking to see if you are in that thread or if the current thread is daemon.
571
         *
572
         * @param user user whom we are checking if daemon
573
         * @return true if user is Daemon
574
         */
575
        public static boolean isDaemonUser(User user) {
576
                return DAEMON_USER_UUID.equals(user.getUuid());
1✔
577
        }
578

579
        /**
580
         * @return the current thread daemon user or null if not assigned
581
         * @since 2.0.0, 1.12.0, 1.11.6, 1.10.4, 1.9.11
582
         */
583
        public static User getDaemonThreadUser() {
584
                if (isDaemonThread()) {
1✔
585
                        User user = daemonThreadUser.get();
1✔
586
                        if (user == null) {
1✔
587
                                user = Context.getContextDAO().getUserByUuid(DAEMON_USER_UUID);
1✔
588
                                daemonThreadUser.set(user);
1✔
589
                        }
590
                        return user;
1✔
591
                } else {
592
                        return null;
×
593
                }
594
        }
595

596
        public static String getDaemonUserUuid() {
597
                return DAEMON_USER_UUID;
1✔
598
        }
599
}
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