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

openmrs / openmrs-core / 19875839285

02 Dec 2025 10:38PM UTC coverage: 65.302% (-0.002%) from 65.304%
19875839285

Pull #5550

github

web-flow
Merge cb31e75c2 into fc8ab9e75
Pull Request #5550: TRUNK-6490 - Update logging level to WARN for update of search index

0 of 1 new or added line in 1 file covered. (0.0%)

3 existing lines in 1 file now uncovered.

23730 of 36339 relevant lines covered (65.3%)

0.65 hits per line

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

64.34
/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
         * Hibernate session factory
72
         */
73
        private SessionFactory sessionFactory;
74
        
75
        @Autowired
76
        private SearchSessionFactory searchSessionFactory;
77
        
78
        private UserDAO userDao;
79
        
80
        /**
81
         * Session factory to use for this DAO. This is usually injected by spring and its application
82
         * context.
83
         * 
84
         * @param sessionFactory
85
         */
86
        public void setSessionFactory(SessionFactory sessionFactory) {
87
                this.sessionFactory = sessionFactory;
1✔
88
        }
1✔
89
        
90
        public void setUserDAO(UserDAO userDao) {
91
                this.userDao = userDao;
1✔
92
        }
1✔
93

94
        /**
95
         * @see org.openmrs.api.db.ContextDAO#authenticate(java.lang.String, java.lang.String)
96
         */
97
        @Override
98
        @Transactional(noRollbackFor = ContextAuthenticationException.class)
99
        public User authenticate(String login, String password) throws ContextAuthenticationException {
100
                String errorMsg = "Invalid username and/or password: " + login;
1✔
101

102
                Session session = sessionFactory.getCurrentSession();
1✔
103

104
                User candidateUser = null;
1✔
105

106
                if (StringUtils.isNotBlank(login)) {
1✔
107
                        // loginWithoutDash is used to compare to the system id
108
                        String loginWithDash = login;
1✔
109
                        if (login.matches("\\d{2,}")) {
1✔
110
                                loginWithDash = login.substring(0, login.length() - 1) + "-" + login.charAt(login.length() - 1);
1✔
111
                        }
112

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

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

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

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

161
                        Object[] passwordAndSalt = (Object[]) session
1✔
162
                                .createNativeQuery("select password, salt from users where user_id = ?1")
1✔
163
                                .addScalar("password", StandardBasicTypes.STRING).addScalar("salt", StandardBasicTypes.STRING)
1✔
164
                                .setParameter(1, candidateUser.getUserId()).uniqueResult();
1✔
165

166
                        String passwordOnRecord = (String) passwordAndSalt[0];
1✔
167
                        String saltOnRecord = (String) passwordAndSalt[1];
1✔
168

169
                        // if the username and password match, hydrate the user and return it
170
                        if (passwordOnRecord != null && Security.hashMatches(passwordOnRecord, password + saltOnRecord)) {
1✔
171
                                // hydrate the user object
172
                                candidateUser.getAllRoles().size();
1✔
173
                                candidateUser.getUserProperties().size();
1✔
174
                                candidateUser.getPrivileges().size();
1✔
175

176
                                // only clean up if the were some login failures, otherwise all should be clean
177
                                int attempts = getUsersLoginAttempts(candidateUser);
1✔
178
                                if (attempts > 0) {
1✔
179
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOGIN_ATTEMPTS, OpenmrsConstants.ZERO_LOGIN_ATTEMPTS_VALUE);
1✔
180
                                        candidateUser.removeUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP);
1✔
181
                                }
182
                                setLastLoginTime(candidateUser);
1✔
183
                                saveUserProperties(candidateUser);
1✔
184

185
                                // skip out of the method early (instead of throwing the exception)
186
                                // to indicate that this is the valid user
187
                                return candidateUser;
1✔
188
                        } else {
189
                                // the user failed the username/password, increment their
190
                                // attempts here and set the "lockout" timestamp if necessary
191
                                int attempts = getUsersLoginAttempts(candidateUser);
1✔
192

193
                                attempts++;
1✔
194

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

209
                                if (attempts > allowedFailedLoginCount) {
1✔
210
                                        // set the user as locked out at this exact time
211
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOCKOUT_TIMESTAMP, String.valueOf(System
1✔
212
                                                .currentTimeMillis()));
1✔
213
                                } else {
214
                                        candidateUser.setUserProperty(OpenmrsConstants.USER_PROPERTY_LOGIN_ATTEMPTS, String.valueOf(attempts));
1✔
215
                                }
216

217
                                saveUserProperties(candidateUser);
1✔
218
                        }
219
                }
220

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

388
        /**
389
         * @see org.openmrs.api.db.ContextDAO#evictEntity(OpenmrsObject)
390
         */
391
        @Override
392
        public void evictEntity(OpenmrsObject obj) {
393
                sessionFactory.getCache().evictEntity(obj.getClass(), obj.getId());
1✔
394
        }
1✔
395

396
        /**
397
         * @see org.openmrs.api.db.ContextDAO#evictAllEntities(Class)
398
         */
399
        @Override
400
        public void evictAllEntities(Class<?> entityClass) {
401
                sessionFactory.getCache().evictEntityRegion(entityClass);
1✔
402
                sessionFactory.getCache().evictCollectionRegions();
1✔
403
                sessionFactory.getCache().evictQueryRegions();
1✔
404
        }
1✔
405

406
        /**
407
         * @see org.openmrs.api.db.ContextDAO#clearEntireCache()
408
         */
409
        @Override
410
        public void clearEntireCache() {
411
                sessionFactory.getCache().evictAllRegions();
1✔
412
        }
1✔
413
        
414
        /**
415
         * @see org.openmrs.api.db.ContextDAO#refreshEntity(Object)
416
         */
417
        @Override
418
        public void refreshEntity(Object obj) {
419
                sessionFactory.getCurrentSession().refresh(obj);
1✔
420
        }
1✔
421

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

537
                //Purge all search indexes of the given type
538
                Search.mapping(sessionFactory).scope(type).workspace().purge();
1✔
539
                
540
                FlushMode flushMode = session.getHibernateFlushMode();
1✔
541
                CacheMode cacheMode = session.getCacheMode();
1✔
542
                try {
543
                        session.setHibernateFlushMode(FlushMode.MANUAL);
1✔
544
                        session.setCacheMode(CacheMode.IGNORE);
1✔
545

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

573
        @Override
574
        @Transactional
575
        public void updateSearchIndex(Class<?>... types) {
576
                try {
577
                        searchSessionFactory.getSearchSession().massIndexer(types).startAndWait();
×
578
                } catch (InterruptedException e) {
×
579
                        throw new RuntimeException(e);
×
580
                }
×
581
        }
×
582

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

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

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

652
        /**
653
         * @see ContextDAO#getDatabaseConnection() 
654
         */
655
        public Connection getDatabaseConnection() {
656
                try {
657
                        return SessionFactoryUtils.getDataSource(sessionFactory).getConnection();
×
658
                }
659
                catch (SQLException e) {
×
660
                        throw new RuntimeException("Unable to retrieve a database connection", e);
×
661
                }
662
        }
663
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc