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

openmrs / openmrs-core / 29845725671

21 Jul 2026 03:48PM UTC coverage: 63.994% (+0.01%) from 63.981%
29845725671

push

github

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

54 of 80 new or added lines in 6 files covered. (67.5%)

7 existing lines in 6 files now uncovered.

24227 of 37858 relevant lines covered (63.99%)

0.64 hits per line

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

68.0
/api/src/main/java/org/openmrs/api/db/hibernate/HibernateContextDAO.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.db.hibernate;
11

12
import java.io.File;
13
import java.net.URL;
14
import java.sql.Connection;
15
import java.sql.SQLException;
16
import java.util.HashMap;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Properties;
20
import java.util.concurrent.Future;
21
import java.util.concurrent.TimeUnit;
22

23
import org.apache.commons.lang3.StringUtils;
24
import org.hibernate.CacheMode;
25
import org.hibernate.FlushMode;
26
import org.hibernate.HibernateException;
27
import org.hibernate.ScrollableResults;
28
import org.hibernate.Session;
29
import org.hibernate.SessionFactory;
30
import org.hibernate.search.mapper.orm.Search;
31
import org.hibernate.search.mapper.orm.session.SearchSession;
32
import org.hibernate.search.mapper.orm.work.SearchIndexingPlan;
33
import org.hibernate.stat.QueryStatistics;
34
import org.hibernate.stat.Statistics;
35
import org.hibernate.type.StandardBasicTypes;
36
import org.openmrs.GlobalProperty;
37
import org.openmrs.OpenmrsObject;
38
import org.openmrs.User;
39
import org.openmrs.api.context.Context;
40
import org.openmrs.api.context.ContextAuthenticationException;
41
import org.openmrs.api.context.Daemon;
42
import org.openmrs.api.db.ContextDAO;
43
import org.openmrs.api.db.UserDAO;
44
import org.openmrs.api.db.hibernate.search.session.SearchSessionFactory;
45
import org.openmrs.util.OpenmrsConstants;
46
import org.openmrs.util.OpenmrsUtil;
47
import org.openmrs.util.PrivilegeConstants;
48
import org.openmrs.util.Security;
49
import org.slf4j.Logger;
50
import org.slf4j.LoggerFactory;
51
import org.springframework.beans.factory.annotation.Autowired;
52
import org.springframework.orm.hibernate5.SessionFactoryUtils;
53
import org.springframework.orm.hibernate5.SessionHolder;
54
import org.springframework.transaction.annotation.Transactional;
55
import org.springframework.transaction.support.TransactionSynchronizationManager;
56

57
/**
58
 * Hibernate specific implementation of the {@link ContextDAO}. These methods should not be used
59
 * directly, instead, the methods on the static {@link Context} file should be used.
60
 * 
61
 * @see ContextDAO
62
 * @see Context
63
 */
64
public class HibernateContextDAO implements ContextDAO {
1✔
65
        
66
        private static final Logger log = LoggerFactory.getLogger(HibernateContextDAO.class);
1✔
67
        
68
        private static final Long DEFAULT_UNLOCK_ACCOUNT_WAITING_TIME = TimeUnit.MILLISECONDS.convert(5L, TimeUnit.MINUTES);
1✔
69

70
        /**
71
         * The capability that proves to {@link Daemon} this class is allowed to act with daemon
72
     * permissions.
73
         */
74
        private static volatile Daemon.CallerKey daemonCallerKey;
75

76
        /**
77
         * Hibernate session factory
78
         */
79
        private SessionFactory sessionFactory;
80
        
81
        @Autowired
82
        private SearchSessionFactory searchSessionFactory;
83
        
84
        private UserDAO userDao;
85
        
86
        /**
87
         * Session factory to use for this DAO. This is usually injected by spring and its application
88
         * context.
89
         * 
90
         * @param sessionFactory
91
         */
92
        public void setSessionFactory(SessionFactory sessionFactory) {
93
                this.sessionFactory = sessionFactory;
1✔
94
        }
1✔
95
        
96
        public void setUserDAO(UserDAO userDao) {
97
                this.userDao = userDao;
1✔
98
        }
1✔
99

100
        /**
101
         * @see org.openmrs.api.db.ContextDAO#authenticate(java.lang.String, java.lang.String)
102
         */
103
        @Override
104
        @Transactional(noRollbackFor = ContextAuthenticationException.class)
105
        public User authenticate(String login, String password) throws ContextAuthenticationException {
106
                String errorMsg = "Invalid username and/or password: " + login;
1✔
107

108
                Session session = sessionFactory.getCurrentSession();
1✔
109

110
                User candidateUser = null;
1✔
111

112
                if (StringUtils.isNotBlank(login)) {
1✔
113
                        // loginWithoutDash is used to compare to the system id
114
                        String loginWithDash = login;
1✔
115
                        if (login.matches("\\d{2,}")) {
1✔
116
                                loginWithDash = login.substring(0, login.length() - 1) + "-" + login.charAt(login.length() - 1);
1✔
117
                        }
118

119
                        try {
120
                                candidateUser = session.createQuery(
1✔
121
                                        "from User u where (u.username = ?1 or u.systemId = ?2 or u.systemId = ?3) and u.retired = false",
122
                                        User.class)
123
                                        .setParameter(1, login).setParameter(2, login).setParameter(3, loginWithDash).uniqueResult();
1✔
124
                        }
125
                        catch (HibernateException he) {
×
126
                                log.error("Got hibernate exception while logging in: '{}'", login, he);
×
127
                        }
128
                        catch (Exception e) {
×
129
                                log.error("Got regular exception while logging in: '{}'", login, e);
×
130
                        }
1✔
131
                }
132

133
                // only continue if this is a valid username and a nonempty password
134
                if (candidateUser != null && password != null) {
1✔
135
                        log.debug("Candidate user id: {}", candidateUser.getUserId());
1✔
136

137
                        String lockoutTimeString = candidateUser.getUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP, null);
1✔
138
                        long lockoutTime = -1;
1✔
139
                        if (StringUtils.isNotBlank(lockoutTimeString) && !"0".equals(lockoutTimeString)) {
1✔
140
                                try {
141
                                        // putting this in a try/catch in case the admin decided to put junk into the property
142
                                        lockoutTime = Long.parseLong(lockoutTimeString);
1✔
143
                                }
144
                                catch (NumberFormatException e) {
×
145
                                        log.warn("bad value stored in {} user property: {}", OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP,
×
146
                                                lockoutTimeString);
147
                                }
1✔
148
                        }
149

150
                        // if they've been locked out, don't continue with the authentication
151
                        if (lockoutTime > 0) {
1✔
152
                                // unlock them after x mins, otherwise reset the timestamp
153
                                // to now and make them wait another x mins
154
                                final Long unlockTime = getUnlockTimeMs();
1✔
155
                                if (System.currentTimeMillis() - lockoutTime > unlockTime) {
1✔
156
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOGIN_ATTEMPTS, OpenmrsConstants.ZERO_LOGIN_ATTEMPTS_VALUE);
×
157
                                        candidateUser.removeUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP);
×
158
                                        saveUserProperties(candidateUser);
×
159
                                } else {
160
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP, String.valueOf(System
1✔
161
                                                .currentTimeMillis()));
1✔
162
                                        throw new ContextAuthenticationException(
1✔
163
                                                "Invalid number of connection attempts. Please try again later.");
164
                                }
165
                        }
166

167
                        Object[] passwordAndSalt = (Object[]) session
1✔
168
                                .createNativeQuery("select password, salt from users where user_id = ?1")
1✔
169
                                .addScalar("password", StandardBasicTypes.STRING).addScalar("salt", StandardBasicTypes.STRING)
1✔
170
                                .setParameter(1, candidateUser.getUserId()).uniqueResult();
1✔
171

172
                        String passwordOnRecord = (String) passwordAndSalt[0];
1✔
173
                        String saltOnRecord = (String) passwordAndSalt[1];
1✔
174

175
                        // if the username and password match, hydrate the user and return it
176
                        if (passwordOnRecord != null && Security.hashMatches(passwordOnRecord, password + saltOnRecord)) {
1✔
177
                                // hydrate the user object
178
                                candidateUser.getAllRoles().size();
1✔
179
                                candidateUser.getUserProperties().size();
1✔
180
                                candidateUser.getPrivileges().size();
1✔
181

182
                                // only clean up if the were some login failures, otherwise all should be clean
183
                                int attempts = getUsersLoginAttempts(candidateUser);
1✔
184
                                if (attempts > 0) {
1✔
185
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOGIN_ATTEMPTS, OpenmrsConstants.ZERO_LOGIN_ATTEMPTS_VALUE);
1✔
186
                                        candidateUser.removeUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP);
1✔
187
                                }
188
                                setLastLoginTime(candidateUser);
1✔
189
                                saveUserProperties(candidateUser);
1✔
190

191
                                // skip out of the method early (instead of throwing the exception)
192
                                // to indicate that this is the valid user
193
                                return candidateUser;
1✔
194
                        } else {
195
                                // the user failed the username/password, increment their
196
                                // attempts here and set the "lockout" timestamp if necessary
197
                                int attempts = getUsersLoginAttempts(candidateUser);
1✔
198

199
                                attempts++;
1✔
200

201
                                int allowedFailedLoginCount = 7;
1✔
202
                                try {
203
                                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
204
                                        allowedFailedLoginCount = Integer.parseInt(Context.getAdministrationService().getGlobalProperty(
1✔
205
                                                OpenmrsConstants.GP_ALLOWED_FAILED_LOGINS_BEFORE_LOCKOUT).trim());
×
206
                                }
207
                                catch (Exception ex) {
1✔
208
                                        log.error("Unable to convert the global property {} to a valid integer. Using default value of 7.",
1✔
209
                                                OpenmrsConstants.GP_ALLOWED_FAILED_LOGINS_BEFORE_LOCKOUT);
210
                                }
211
                                finally {
212
                                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
213
                                }
214

215
                                if (attempts > allowedFailedLoginCount) {
1✔
216
                                        // set the user as locked out at this exact time
217
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP, String.valueOf(System
1✔
218
                                                .currentTimeMillis()));
1✔
219
                                } else {
220
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOGIN_ATTEMPTS, String.valueOf(attempts));
1✔
221
                                }
222

223
                                saveUserProperties(candidateUser);
1✔
224
                        }
225
                }
226

227
                // throw this exception only once in the same place with the same
228
                // message regardless of username/pw combo entered
229
                log.info("Failed login attempt (login={}) - {}", login, errorMsg);
1✔
230
                throw new ContextAuthenticationException(errorMsg);
1✔
231
        }
232
        
233
        private void setLastLoginTime(User candidateUser) {
234
                candidateUser.setUserProperty(
1✔
235
                        OpenmrsConstants.USER_PROPERTY_LAST_LOGIN_TIMESTAMP,
236
                        String.valueOf(System.currentTimeMillis())
1✔
237
                );
238
        }
1✔
239
        
240
        private Long getUnlockTimeMs() {
241
                try {
242
                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
243
                        String unlockTimeGPValue = Context.getAdministrationService().getGlobalProperty(
1✔
244
                                OpenmrsConstants.GP_UNLOCK_ACCOUNT_WAITING_TIME);
245
                        if (StringUtils.isNotBlank(unlockTimeGPValue)) {
1✔
246
                                return convertUnlockAccountWaitingTimeGP(unlockTimeGPValue);
×
247
                        } else {
248
                                return DEFAULT_UNLOCK_ACCOUNT_WAITING_TIME;
1✔
249
                        }
250
                }
251
                finally {
252
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
1✔
253
                }
254
        }
255
        
256
        private Long convertUnlockAccountWaitingTimeGP(String waitingTime) {
257
                try {
258
                        return TimeUnit.MILLISECONDS.convert(Long.valueOf(waitingTime), TimeUnit.MINUTES);
×
259
                } catch (Exception ex) {
×
260
                        log.error("Unable to convert the global property "
×
261
                                        + OpenmrsConstants.GP_UNLOCK_ACCOUNT_WAITING_TIME
262
                                        + "to a valid Long. Using default value of 5");
263
                        return DEFAULT_UNLOCK_ACCOUNT_WAITING_TIME;
×
264
                }
265
        }
266
        
267
        /**
268
         * @see org.openmrs.api.db.ContextDAO#getUserByUuid(java.lang.String)
269
         */
270
        @Override
271
        @Transactional(readOnly = true)
272
        public User getUserByUuid(String uuid) {
273
                
274
                // don't flush here in case we're in the AuditableInterceptor.  Will cause a StackOverflowEx otherwise
275
                FlushMode flushMode = sessionFactory.getCurrentSession().getHibernateFlushMode();
1✔
276
                sessionFactory.getCurrentSession().setHibernateFlushMode(FlushMode.MANUAL);
1✔
277
                
278
                User u = HibernateUtil.getUniqueEntityByUUID(sessionFactory, User.class, uuid);
1✔
279
                
280
                // reset the flush mode to whatever it was before
281
                sessionFactory.getCurrentSession().setHibernateFlushMode(flushMode);
1✔
282
                
283
                return u;
1✔
284
        }
285
        
286
        /**
287
         * @see org.openmrs.api.db.ContextDAO#getUserByUsername(String)
288
         */
289
        @Override
290
        @Transactional(readOnly = true)
291
        public User getUserByUsername(String username) {
292
                return userDao.getUserByUsername(username);
×
293
        }
294
        
295
        /**
296
         * @throws Exception 
297
         * @see org.openmrs.api.db.ContextDAO#createUser(User, String)
298
         */
299
        @Override
300
        @Transactional
301
        public User createUser(User user, String password, List<String> roleNames) throws Exception {
302
                return Daemon.createUser(user, password, roleNames, daemonCallerKey());
1✔
303
        }
304
        
305
        /**
306
         * Call the UserService to save the given user while proxying the privileges needed to do so.
307
         * 
308
         * @param user the User to save
309
         */
310
        private void saveUserProperties(User user) {
311
                sessionFactory.getCurrentSession().update(user);
1✔
312
        }
1✔
313
        
314
        /**
315
         * Get the integer stored for the given user that is their number of login attempts
316
         * 
317
         * @param user the user to check
318
         * @return the # of login attempts for this user defaulting to zero if none defined
319
         */
320
        private int getUsersLoginAttempts(User user) {
321
                String attemptsString = user.getUserProperty(OpenmrsConstants.USER_PROPERTY_LOGIN_ATTEMPTS, "0");
1✔
322
                int attempts = 0;
1✔
323
                try {
324
                        attempts = Integer.parseInt(attemptsString);
1✔
325
                }
326
                catch (NumberFormatException e) {
×
327
                        // skip over errors and leave the attempts at zero
328
                }
1✔
329
                return attempts;
1✔
330
        }
331
        
332
        /**
333
         * @see org.openmrs.api.context.Context#openSession()
334
         */
335
        private boolean participate = false;
1✔
336
        
337
        @Override
338
        public void openSession() {
339
                log.debug("HibernateContext: Opening Hibernate Session");
1✔
340
                if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
1✔
341
                        log.debug("Participating in existing session ({})", sessionFactory.hashCode());
1✔
342
                        participate = true;
1✔
343
                } else {
344
                        log.debug("Registering session with synchronization manager ({})", sessionFactory.hashCode());
1✔
345
                        Session session = sessionFactory.openSession();
1✔
346
                        session.setHibernateFlushMode(FlushMode.MANUAL);
1✔
347
                        TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
1✔
348
                }
349
        }
1✔
350
        
351
        /**
352
         * @see org.openmrs.api.context.Context#closeSession()
353
         */
354
        @Override
355
        public void closeSession() {
356
                log.debug("HibernateContext: closing Hibernate Session");
1✔
357
                if (!participate) {
1✔
358
                        log.debug("Unbinding session from synchronization manager (" + sessionFactory.hashCode() + ")");
1✔
359
                        
360
                        if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
1✔
361
                                Object value = TransactionSynchronizationManager.unbindResource(sessionFactory);
1✔
362
                                try {
363
                                        if (value instanceof SessionHolder) {
1✔
364
                                                Session session = ((SessionHolder) value).getSession();
1✔
365
                                                SessionFactoryUtils.closeSession(session);
1✔
366
                                        }
367
                                }
368
                                catch (RuntimeException e) {
×
369
                                        log.error("Unexpected exception on closing Hibernate Session", e);
×
370
                                }
1✔
371
                        }
1✔
372
                } else {
373
                        log.debug("Participating in existing session, so not releasing session through synchronization manager");
1✔
374
                }
375
        }
1✔
376
        
377
        /**
378
         * @see org.openmrs.api.db.ContextDAO#clearSession()
379
         */
380
        @Override
381
        @Transactional
382
        public void clearSession() {
383
                sessionFactory.getCurrentSession().clear();
1✔
384
        }
1✔
385
        
386
        /**
387
         * @see org.openmrs.api.db.ContextDAO#evictFromSession(java.lang.Object)
388
         */
389
        @Override
390
        public void evictFromSession(Object obj) {
391
                sessionFactory.getCurrentSession().evict(obj);
1✔
392
        }
1✔
393

394
        /**
395
         * @see org.openmrs.api.db.ContextDAO#evictEntity(OpenmrsObject)
396
         */
397
        @Override
398
        public void evictEntity(OpenmrsObject obj) {
399
                sessionFactory.getCache().evictEntity(obj.getClass(), obj.getId());
1✔
400
        }
1✔
401

402
        /**
403
         * @see org.openmrs.api.db.ContextDAO#evictAllEntities(Class)
404
         */
405
        @Override
406
        public void evictAllEntities(Class<?> entityClass) {
407
                sessionFactory.getCache().evictEntityRegion(entityClass);
1✔
408
                sessionFactory.getCache().evictCollectionRegions();
1✔
409
                sessionFactory.getCache().evictQueryRegions();
1✔
410
        }
1✔
411

412
        /**
413
         * @see org.openmrs.api.db.ContextDAO#clearEntireCache()
414
         */
415
        @Override
416
        public void clearEntireCache() {
417
                sessionFactory.getCache().evictAllRegions();
1✔
418
        }
1✔
419
        
420
        /**
421
         * @see org.openmrs.api.db.ContextDAO#refreshEntity(Object)
422
         */
423
        @Override
424
        public void refreshEntity(Object obj) {
425
                sessionFactory.getCurrentSession().refresh(obj);
1✔
426
        }
1✔
427

428
        /**
429
         * @see org.openmrs.api.db.ContextDAO#flushSession()
430
         */
431
        @Override
432
        @Transactional
433
        public void flushSession() {
434
                sessionFactory.getCurrentSession().flush();
1✔
435
        }
1✔
436
        
437
        /**
438
         * @see org.openmrs.api.context.Context#startup(Properties)
439
         */
440
        @Override
441
        @Transactional
442
        public void startup(Properties properties) {
443
        }
×
444
        
445
        /**
446
         * @see org.openmrs.api.context.Context#shutdown()
447
         */
448
        @Override
449
        public void shutdown() {
450
                if (log.isInfoEnabled()) {
×
451
                        showUsageStatistics();
×
452
                }
453
                
454
                if (sessionFactory != null) {
×
455
                        
456
                        log.debug("Closing any open sessions");
×
457
                        closeSession();
×
458
                        
459
                        log.debug("Shutting down threadLocalSession factory");
×
460
                        if (!sessionFactory.isClosed()) {
×
461
                                sessionFactory.close();
×
462
                        }
463
                        
464
                        log.debug("The threadLocalSession has been closed");
×
465
                        
466
                } else {
467
                        log.error("SessionFactory is null");
×
468
                }
469
                
470
        }
×
471
        
472
        /**
473
         * Convenience method to print out the hibernate cache usage stats to the log
474
         */
475
        private void showUsageStatistics() {
476
                if (sessionFactory.getStatistics().isStatisticsEnabled()) {
×
477
                        log.debug("Getting query statistics: ");
×
478
                        Statistics stats = sessionFactory.getStatistics();
×
479
                        for (String query : stats.getQueries()) {
×
480
                                log.info("QUERY: " + query);
×
481
                                QueryStatistics qstats = stats.getQueryStatistics(query);
×
482
                                log.info("Cache Hit Count : " + qstats.getCacheHitCount());
×
483
                                log.info("Cache Miss Count: " + qstats.getCacheMissCount());
×
484
                                log.info("Cache Put Count : " + qstats.getCachePutCount());
×
485
                                log.info("Execution Count : " + qstats.getExecutionCount());
×
486
                                log.info("Average time    : " + qstats.getExecutionAvgTime());
×
487
                                log.info("Row Count       : " + qstats.getExecutionRowCount());
×
488
                        }
489
                }
490
        }
×
491
        
492
        /**
493
         * Takes the default properties defined in /metadata/api/hibernate/hibernate.default.properties
494
         * and merges it into the user-defined runtime properties
495
         * 
496
         * @see org.openmrs.api.db.ContextDAO#mergeDefaultRuntimeProperties(java.util.Properties)
497
         * <strong>Should</strong> merge default runtime properties
498
         */
499
        @Override
500
        public void mergeDefaultRuntimeProperties(Properties runtimeProperties) {
501
                
502
                Map<String, String> cache = new HashMap<>();
1✔
503
                // loop over runtime properties and precede each with "hibernate" if
504
                // it isn't already
505
                for (Map.Entry<Object, Object> entry : runtimeProperties.entrySet()) {
1✔
506
                        Object key = entry.getKey();
1✔
507
                        String prop = (String) key;
1✔
508
                        String value = (String) entry.getValue();
1✔
509
                        log.trace("Setting property: " + prop + ":" + value);
1✔
510
                        if (!prop.startsWith("hibernate") && !runtimeProperties.containsKey("hibernate." + prop)) {
1✔
511
                                cache.put("hibernate." + prop, value);
1✔
512
                        }
513
                }
1✔
514
                runtimeProperties.putAll(cache);
1✔
515
                
516
                // load in the default hibernate properties from hibernate.default.properties
517
                Properties props = new Properties();
1✔
518
                URL url = getClass().getResource("/hibernate.default.properties");
1✔
519
                File file = new File(url.getPath());
1✔
520
                OpenmrsUtil.loadProperties(props, file);
1✔
521
                
522
                // add in all default properties that don't exist in the runtime
523
                // properties yet
524
                for (Map.Entry<Object, Object> entry : props.entrySet()) {
1✔
525
                        if (!runtimeProperties.containsKey(entry.getKey())) {
1✔
526
                                runtimeProperties.put(entry.getKey(), entry.getValue());
1✔
527
                        }
528
                }
1✔
529
        }
1✔
530
        
531
        @Override
532
        @Transactional
533
        public void updateSearchIndexForType(Class<?> type) {
534
                Session session = sessionFactory.getCurrentSession();
1✔
535
                SearchSession searchSession = searchSessionFactory.getSearchSession();
1✔
536
                SearchIndexingPlan indexingPlan = searchSession.indexingPlan();
1✔
537
                
538
                //Prepare session for batch work
539
                session.flush();
1✔
540
                indexingPlan.execute();
1✔
541
                session.clear();
1✔
542

543
                //Purge all search indexes of the given type
544
                Search.mapping(sessionFactory).scope(type).workspace().purge();
1✔
545
                
546
                FlushMode flushMode = session.getHibernateFlushMode();
1✔
547
                CacheMode cacheMode = session.getCacheMode();
1✔
548
                try {
549
                        session.setHibernateFlushMode(FlushMode.MANUAL);
1✔
550
                        session.setCacheMode(CacheMode.IGNORE);
1✔
551

552
                        //Scrollable results will avoid loading too many objects in memory
553
                        try (ScrollableResults results = HibernateUtil.getScrollableResult(sessionFactory, type, 1000)) {
1✔
554
                                int index = 0;
1✔
555
                                while (results.next()) {
1✔
556
                                        index++;
1✔
557
                                        //index each element
558
                                        indexingPlan.addOrUpdate(results.get(0));
1✔
559
                                        if (index % 1000 == 0) {
1✔
560
                                                //apply changes to search indexes
561
                                                indexingPlan.execute();
1✔
562
                                                //free memory since the queue is processed
563
                                                session.clear();
1✔
564
                                                // reset index to avoid overflows
565
                                                index = 0;
1✔
566
                                        }
567
                                }
568
                        } finally {
569
                                indexingPlan.execute();
1✔
570
                                session.clear();
1✔
571
                        }
572
                }
573
                finally {
574
                        session.setHibernateFlushMode(flushMode);
1✔
575
                        session.setCacheMode(cacheMode);
1✔
576
                }
577
        }
1✔
578

579
        @Override
580
        @Transactional
581
        public void updateSearchIndex(Class<?>... types) {
582
                try {
583
                        searchSessionFactory.getSearchSession().massIndexer(types).startAndWait();
×
584
                } catch (InterruptedException e) {
×
585
                        throw new RuntimeException(e);
×
586
                }
×
587
        }
×
588

589
        /**
590
         * @see org.openmrs.api.db.ContextDAO#updateSearchIndexForObject(java.lang.Object)
591
         */
592
        @Override
593
        @Transactional
594
        public void updateSearchIndexForObject(Object object) {
595
                SearchIndexingPlan indexingPlan = searchSessionFactory.getSearchSession().indexingPlan();
×
596
                indexingPlan.addOrUpdate(object);
×
597
                indexingPlan.execute();
×
598
        }
×
599
        
600
        /**
601
         * @see org.openmrs.api.db.ContextDAO#setupSearchIndex()
602
         */
603
        @Override
604
        public void setupSearchIndex() {
605
                try {
606
                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
607
                        String gp = Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GP_SEARCH_INDEX_VERSION, "");
×
608

609
                        if (!OpenmrsConstants.SEARCH_INDEX_VERSION.toString().equals(gp)) {
×
610
                                updateSearchIndex();
×
611
                        }
612
                }
613
                finally {
614
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
615
                }
616
        }
×
617
        
618
        /**
619
         * @see ContextDAO#updateSearchIndex()
620
         */
621
        @Override
622
        public void updateSearchIndex() {
623
                try {
624
                        log.warn("Updating the search index... It may take a few minutes.");
×
625
                        searchSessionFactory.getSearchSession().massIndexer().startAndWait();
×
626
                        Context.addProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
627
                        GlobalProperty gp = Context.getAdministrationService().getGlobalPropertyObject(
×
628
                            OpenmrsConstants.GP_SEARCH_INDEX_VERSION);
629
                        if (gp == null) {
×
630
                                gp = new GlobalProperty(OpenmrsConstants.GP_SEARCH_INDEX_VERSION);
×
631
                        }
632
                        gp.setPropertyValue(OpenmrsConstants.SEARCH_INDEX_VERSION.toString());
×
633
                        Context.getAdministrationService().saveGlobalProperty(gp);
×
634
                        log.info("Finished updating the search index");
×
635
                }
636
                catch (Exception e) {
×
637
                        throw new RuntimeException("Failed to update the search index", e);
×
638
                }
639
                finally {
640
                        Context.removeProxyPrivilege(PrivilegeConstants.GET_GLOBAL_PROPERTIES);
×
641
                }
642
        }
×
643

644
        /**
645
         * @see ContextDAO#updateSearchIndexAsync()
646
         */
647
        @Override
648
        public Future<?> updateSearchIndexAsync() {
649
                try {
650
                        log.info("Started asynchronously updating the search index...");
×
651
                        return searchSessionFactory.getSearchSession().massIndexer().start().toCompletableFuture();
×
652
                }
653
                catch (Exception e) {
×
654
                        throw new RuntimeException("Failed to start asynchronous search index update", e);
×
655
                }
656
        }
657

658
        /**
659
         * @see ContextDAO#getDatabaseConnection() 
660
         */
661
        public Connection getDatabaseConnection() {
662
                try {
663
                        return SessionFactoryUtils.getDataSource(sessionFactory).getConnection();
×
664
                }
665
                catch (SQLException e) {
×
666
                        throw new RuntimeException("Unable to retrieve a database connection", e);
×
667
                }
668
        }
669

670
        /**
671
         * Receives the {@link Daemon} caller key. Called only by {@link Daemon} during its initialization.
672
         *
673
         * @param callerKey the caller key issued by {@link Daemon}
674
         * @since 3.0.0, 2.9.0, 2.8.9
675
         */
676
        public static void setDaemonCallerKey(Daemon.CallerKey callerKey) {
677
                if (callerKey != null && daemonCallerKey == null) {
1✔
678
                        daemonCallerKey = callerKey;
1✔
679
                }
680
        }
1✔
681

682
        private static Daemon.CallerKey daemonCallerKey() {
683
                if (daemonCallerKey == null) {
1✔
684
                        // Guarantee Daemon has initialized and therefore handed us the key, regardless of the order in
685
                        // which the two classes were first loaded.
NEW
686
                        Daemon.ensureInitialized();
×
687
                }
688
                return daemonCallerKey;
1✔
689
        }
690
}
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