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

openmrs / openmrs-core / 29955961944

22 Jul 2026 08:38PM UTC coverage: 64.136% (+0.04%) from 64.101%
29955961944

push

github

ibacher
TRUNK-6711: Batch-load search result pages (#6363)

99 of 105 new or added lines in 4 files covered. (94.29%)

7 existing lines in 5 files now uncovered.

24316 of 37913 relevant lines covered (64.14%)

0.64 hits per line

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

95.79
/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePersonDAO.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 javax.persistence.criteria.CriteriaBuilder;
13
import javax.persistence.criteria.CriteriaQuery;
14
import javax.persistence.criteria.Expression;
15
import javax.persistence.criteria.Predicate;
16
import javax.persistence.criteria.Root;
17
import java.util.ArrayList;
18
import java.util.Date;
19
import java.util.LinkedHashSet;
20
import java.util.List;
21
import java.util.Objects;
22
import java.util.Set;
23
import java.util.function.Function;
24
import java.util.stream.Collectors;
25

26
import org.apache.commons.lang3.StringUtils;
27
import org.hibernate.SQLQuery;
28
import org.hibernate.Session;
29
import org.hibernate.SessionFactory;
30
import org.openmrs.Person;
31
import org.openmrs.PersonAddress;
32
import org.openmrs.PersonAttribute;
33
import org.openmrs.PersonAttributeType;
34
import org.openmrs.PersonName;
35
import org.openmrs.Relationship;
36
import org.openmrs.RelationshipType;
37
import org.openmrs.api.db.DAOException;
38
import org.openmrs.api.db.PersonDAO;
39
import org.openmrs.api.db.hibernate.search.SearchQueryUnique;
40
import org.openmrs.api.db.hibernate.search.session.SearchSessionFactory;
41
import org.openmrs.person.PersonMergeLog;
42
import org.openmrs.util.ConfigUtil;
43
import org.openmrs.util.OpenmrsConstants;
44
import org.slf4j.Logger;
45
import org.slf4j.LoggerFactory;
46

47
/**
48
 * Hibernate specific Person database methods. <br>
49
 * <br>
50
 * This class should not be used directly. All database calls should go through the Service layer. <br>
51
 * <br>
52
 * Proper use: <code>
53
 *   PersonService ps = Context.getPersonService();
54
 *   ps.getPeople("name", false);
55
 * </code>
56
 * @see org.openmrs.api.db.PersonDAO
57
 * @see org.openmrs.api.PersonService
58
 * @see org.openmrs.api.context.Context
59
 */
60
public class HibernatePersonDAO implements PersonDAO {
1✔
61
        
62
        private static final Logger log = LoggerFactory.getLogger(HibernatePersonDAO.class);
1✔
63
        
64
        /**
65
         * Hibernate session factory
66
         */
67
        private SessionFactory sessionFactory;
68
        
69
        private SearchSessionFactory searchSessionFactory;
70
        
71
        /**
72
         * Set session factory
73
         * 
74
         * @param sessionFactory
75
         */
76
        public void setSessionFactory(SessionFactory sessionFactory) {
77
                this.sessionFactory = sessionFactory;
1✔
78
        }
1✔
79

80
        public void setSearchSessionFactory(SearchSessionFactory searchSessionFactory) {
81
                this.searchSessionFactory = searchSessionFactory;
1✔
82
        }
1✔
83

84
        /**
85
         * This method executes a Lucene search on persons based on the soundex filter with one search name given
86
         * 
87
         * @param name a person should match using soundex representation
88
         * @param birthyear the birthyear the searched person should have 
89
         * @param includeVoided true if voided person should be included 
90
         * @param gender of the person to search for 
91
         * @return the set of Persons that match the search criteria 
92
         */
93
        private Set<Person> executeSoundexOnePersonNameQuery(String name, Integer birthyear, boolean includeVoided , String gender) {
94
                PersonQuery personQuery = new PersonQuery();
1✔
95

96
                List<Person> results = SearchQueryUnique.search(searchSessionFactory,
1✔
97
                    SearchQueryUnique.newQuery(PersonName.class,
1✔
98
                        f -> personQuery.getSoundexPersonNameQuery(f, name, birthyear, includeVoided, gender), "person.personId",
1✔
99
                        (List<PersonName> pNs) -> multiLoadPersons(pNs, pN -> pN.getPerson().getPersonId())),
1✔
100
                    null, HibernatePersonDAO.getMaximumSearchResults());
1✔
101

102
                return new LinkedHashSet<>(results);
1✔
103
        }
104
        
105
        
106
        /**
107
         * This method executes a Lucene search on persons based on the soundex filter with three name elements given
108
         *
109
         * @param name1 basically represents the first name to be searched for in a person name
110
         * @param name2 basically represents the middle name to be searched for in a person name         
111
         * @param name3 basically represents the family name to be searched for in a person name
112
         * @param birthyear the birthyear the searched person should have 
113
         * @param includeVoided true if voided person should be included 
114
         * @param gender of the person to search for 
115
         * @return the set of Persons that match the search criteria 
116
         */
117
        private Set<Person> executeSoundexThreePersonNamesQuery(String name1, String name2, String name3, Integer birthyear, 
118
                                                                                                                        boolean includeVoided , String gender) {
119
                PersonQuery personQuery = new PersonQuery();
1✔
120

121
                List<Person> results = SearchQueryUnique.search(searchSessionFactory,
1✔
122
                    SearchQueryUnique.newQuery(PersonName.class,
1✔
123
                        f -> personQuery.getSoundexPersonNameSearchOnThreeNames(f, name1, name2, name3, birthyear, includeVoided,
1✔
124
                            gender),
125
                        "person.personId", (List<PersonName> pNs) -> multiLoadPersons(pNs, pN -> pN.getPerson().getPersonId())),
1✔
126
                    null, HibernatePersonDAO.getMaximumSearchResults());
1✔
127

128
                return new LinkedHashSet<>(results);
1✔
129
        }
130
        
131
        /**
132
         * This method executes a Lucene search on persons based on the soundex filter with two search names given
133
         *
134
         * @param searchName1 the first entered name by the user to be searched for
135
         * @param searchName2 the second entered name by the user to be searched  for
136
         * @param birthyear the birthyear the searched person should have 
137
         * @param includeVoided true if voided person should be included 
138
         * @param gender of the person to search for 
139
         * @return the set of Persons that match the search criteria
140
         */
141
        private Set<Person> executeSoundexTwoPersonNamesQuery(String searchName1, String searchName2, Integer birthyear, 
142
                                                                                                                  boolean includeVoided , String gender) {
143
                PersonQuery personQuery = new PersonQuery();
1✔
144

145
                List<Person> results = SearchQueryUnique.search(searchSessionFactory,
1✔
146
                    SearchQueryUnique.newQuery(PersonName.class,
1✔
147
                        f -> personQuery.getSoundexPersonNameSearchOnTwoNames(f, searchName1, searchName2, birthyear, includeVoided,
1✔
148
                            gender),
149
                        "person.personId", (List<PersonName> pNs) -> multiLoadPersons(pNs, pN -> pN.getPerson().getPersonId())),
1✔
150
                    null, HibernatePersonDAO.getMaximumSearchResults());
1✔
151

152
                return new LinkedHashSet<>(results);
1✔
153
        }
154
        
155
        /**
156
         * This method executes a Lucence search based on the soundex filter with more than three search names given 
157
         * 
158
         * @param searchNames the names seperated by space that should be searched for 
159
         * @param birthyear the birthyear the searched person should have 
160
         * @param includeVoided true if voided person should be included 
161
         * @param gender of the person to search for 
162
         * @return the set of Persons that match the search criteria
163
         */
164
        private Set<Person> executeSoundexNPersonNamesQuery(String[] searchNames, Integer birthyear, boolean includeVoided, 
165
                                                                                                                String gender) {
166
                PersonQuery personQuery = new PersonQuery();
1✔
167

168
                List<Person> results = SearchQueryUnique.search(searchSessionFactory,
1✔
169
                    SearchQueryUnique.newQuery(PersonName.class,
1✔
170
                        f -> personQuery.getSoundexPersonNameSearchOnNNames(f, searchNames, birthyear, includeVoided, gender),
1✔
171
                        "person.personId", (List<PersonName> pNs) -> multiLoadPersons(pNs, pN -> pN.getPerson().getPersonId())),
1✔
172
                    null, HibernatePersonDAO.getMaximumSearchResults());
1✔
173

174
                return new LinkedHashSet<>(results);
1✔
175
                
176
        }
177
        
178
        /**
179
         * @see org.openmrs.api.PersonService#getSimilarPeople(String name, Integer birthyear, String gender)
180
         * @see org.openmrs.api.db.PersonDAO#getSimilarPeople(String name, Integer birthyear, String gender)
181
         */
182
        @Override
183
        @SuppressWarnings("unchecked")
184
        public Set<Person> getSimilarPeople(String name, Integer birthyear, String gender) throws DAOException {
185
                if (birthyear == null) {
1✔
186
                        birthyear = 0;
1✔
187
                }
188

189
                name = name.replaceAll("  ", " ");
1✔
190
                name = name.replace(", ", " ");
1✔
191
                String[] names = name.split(" ");
1✔
192
                
193
                if (names.length == 1) {
1✔
194
                        return  executeSoundexOnePersonNameQuery(name, birthyear, false, gender);
1✔
195
                } else if (names.length == 2) {
1✔
196
                        return executeSoundexTwoPersonNamesQuery(names[0], names[1], birthyear, false, gender);
1✔
197
                } else if (names.length == 3) {
1✔
198
                        return executeSoundexThreePersonNamesQuery(names[0], names[1], names[2], birthyear, false, gender);
1✔
199
                }
200
                else if (names.length > 3) {
1✔
201
                        return executeSoundexNPersonNamesQuery(names, birthyear, false, gender);
1✔
202
                }
203
                return new LinkedHashSet<>();
×
204
        }
205
        
206
        /**
207
         * @see org.openmrs.api.db.PersonDAO#getPeople(java.lang.String, java.lang.Boolean)
208
         * <strong>Should</strong> get no one by null
209
         * <strong>Should</strong> get every one by empty string
210
         * <strong>Should</strong> get no one by non-existing attribute
211
         * <strong>Should</strong> get no one by non-searchable attribute
212
         * <strong>Should</strong> get no one by voided attribute
213
         * <strong>Should</strong> get one person by attribute
214
         * <strong>Should</strong> get one person by random case attribute
215
         * <strong>Should</strong> get one person by searching for a mix of attribute and voided attribute
216
         * <strong>Should</strong> get multiple people by single attribute
217
         * <strong>Should</strong> get multiple people by multiple attributes
218
         * <strong>Should</strong> get no one by non-existing name
219
         * <strong>Should</strong> get one person by name
220
         * <strong>Should</strong> get one person by random case name
221
         * <strong>Should</strong> get multiple people by single name
222
         * <strong>Should</strong> get multiple people by multiple names
223
         * <strong>Should</strong> get no one by non-existing name and non-existing attribute
224
         * <strong>Should</strong> get no one by non-existing name and non-searchable attribute
225
         * <strong>Should</strong> get no one by non-existing name and voided attribute
226
         * <strong>Should</strong> get one person by name and attribute
227
         * <strong>Should</strong> get one person by name and voided attribute
228
         * <strong>Should</strong> get multiple people by name and attribute
229
         * <strong>Should</strong> get one person by given name
230
         * <strong>Should</strong> get multiple people by given name
231
         * <strong>Should</strong> get one person by middle name
232
         * <strong>Should</strong> get multiple people by middle name
233
         * <strong>Should</strong> get one person by family name
234
         * <strong>Should</strong> get multiple people by family name
235
         * <strong>Should</strong> get one person by family name2
236
         * <strong>Should</strong> get multiple people by family name2
237
         * <strong>Should</strong> get one person by multiple name parts
238
         * <strong>Should</strong> get multiple people by multiple name parts
239
         * <strong>Should</strong> get no one by voided name
240
         * <strong>Should</strong> not get voided person
241
         * <strong>Should</strong> not get dead person
242
         * <strong>Should</strong> get single dead person
243
         * <strong>Should</strong> get multiple dead people
244
         */
245
        @Override
246
        @SuppressWarnings("unchecked")
247
        public List<Person> getPeople(String searchString, Boolean dead, Boolean voided) {
248
                if (searchString == null) {
1✔
249
                        return new ArrayList<>();
1✔
250
                }
251

252
                int maxResults = HibernatePersonDAO.getMaximumSearchResults();
1✔
253

254
                boolean includeVoided = (voided != null) ? voided : false;
1✔
255

256
                if (StringUtils.isBlank(searchString)) {
1✔
257
                        Session session = sessionFactory.getCurrentSession();
1✔
258
                        CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
259
                        CriteriaQuery<Person> cq = cb.createQuery(Person.class);
1✔
260
                        Root<Person> root = cq.from(Person.class);
1✔
261

262
                        List<Predicate> predicates = new ArrayList<>();
1✔
263
                        if (dead != null) {
1✔
264
                                predicates.add(cb.equal(root.get("dead"), dead));
1✔
265
                        }
266

267
                        if (!includeVoided) {
1✔
268
                                predicates.add(cb.isFalse(root.get("personVoided")));
1✔
269
                        }
270

271
                        cq.where(predicates.toArray(new Predicate[]{}));
1✔
272

273
                        return session.createQuery(cq).setMaxResults(maxResults).getResultList();
1✔
274
                }
275

276
                PersonQuery personQuery = new PersonQuery();
1✔
277

278
                return SearchQueryUnique.search(searchSessionFactory, SearchQueryUnique
1✔
279
                        .newQuery(PersonName.class,
1✔
280
                            f -> personQuery.getPersonNameQueryWithOrParser(f, searchString, includeVoided, dead), "person.personId",
1✔
281
                            (List<PersonName> pNs) -> multiLoadPersons(pNs, pN -> pN.getPerson().getPersonId()))
1✔
282
                        .join(SearchQueryUnique.newQuery(PersonAttribute.class,
1✔
283
                            f -> personQuery.getPersonAttributeQueryWithOrParser(f, searchString, includeVoided), "person.personId",
1✔
284
                            (List<PersonAttribute> pAs) -> multiLoadPersons(pAs, pA -> pA.getPerson().getPersonId()))),
1✔
285
                    null, HibernatePersonDAO.getMaximumSearchResults());
1✔
286
        }
287
        
288
        @Override
289
        public List<Person> getPeople(String searchString, Boolean dead) {
290
                return getPeople(searchString, dead, null);
1✔
291
        }
292
        
293
        /**
294
         * Fetch the max results value from the global properties table
295
         * 
296
         * @return Integer value for the person search max results global property
297
         */
298
        public static Integer getMaximumSearchResults() {
299
                try {
300
                        String personSearchMaxGp = ConfigUtil
1✔
301
                                .getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_PERSON_SEARCH_MAX_RESULTS);
1✔
302
                        if (personSearchMaxGp == null) {
1✔
303
                                personSearchMaxGp = String.valueOf(OpenmrsConstants.GLOBAL_PROPERTY_PERSON_SEARCH_MAX_RESULTS_DEFAULT_VALUE);
1✔
304
                        }
305
                        return Integer.valueOf(personSearchMaxGp);
1✔
306
                }
307
                catch (Exception e) {
×
308
                        log.warn("Unable to convert the global property " + OpenmrsConstants.GLOBAL_PROPERTY_PERSON_SEARCH_MAX_RESULTS
×
309
                                + "to a valid integer. Returning the default "
310
                                + OpenmrsConstants.GLOBAL_PROPERTY_PERSON_SEARCH_MAX_RESULTS_DEFAULT_VALUE);
311
                }
312
                
313
                return OpenmrsConstants.GLOBAL_PROPERTY_PERSON_SEARCH_MAX_RESULTS_DEFAULT_VALUE;
×
314
        }
315
        
316
        /**
317
         * @see org.openmrs.api.PersonService#getPerson(java.lang.Integer)
318
         * @see org.openmrs.api.db.PersonDAO#getPerson(java.lang.Integer)
319
         */
320
        @Override
321
        public Person getPerson(Integer personId) {
322
                return sessionFactory.getCurrentSession().get(Person.class, personId);
1✔
323
        }
324
        
325
        /**
326
         * @see org.openmrs.api.PersonService#purgePersonAttributeType(org.openmrs.PersonAttributeType)
327
         * @see org.openmrs.api.db.PersonDAO#deletePersonAttributeType(org.openmrs.PersonAttributeType)
328
         */
329
        @Override
330
        public void deletePersonAttributeType(PersonAttributeType type) {
331
                sessionFactory.getCurrentSession().delete(type);
1✔
332
        }
1✔
333
        
334
        /**
335
         * @see org.openmrs.api.PersonService#savePersonAttributeType(org.openmrs.PersonAttributeType)
336
         * @see org.openmrs.api.db.PersonDAO#savePersonAttributeType(org.openmrs.PersonAttributeType)
337
         */
338
        @Override
339
        public PersonAttributeType savePersonAttributeType(PersonAttributeType type) {
340
                sessionFactory.getCurrentSession().saveOrUpdate(type);
1✔
341
                return type;
1✔
342
        }
343
        
344
        /**
345
         * @see org.openmrs.api.PersonService#getPersonAttributeType(java.lang.Integer)
346
         * @see org.openmrs.api.db.PersonDAO#getPersonAttributeType(java.lang.Integer)
347
         */
348
        @Override
349
        public PersonAttributeType getPersonAttributeType(Integer typeId) {
350
                return sessionFactory.getCurrentSession().get(PersonAttributeType.class, typeId);
1✔
351
        }
352
        
353
        /**
354
         * @see org.openmrs.api.PersonService#getPersonAttribute(java.lang.Integer)
355
         * @see org.openmrs.api.db.PersonDAO#getPersonAttribute(java.lang.Integer)
356
         */
357
        @Override
358
        public PersonAttribute getPersonAttribute(Integer id) {
359
                return sessionFactory.getCurrentSession().get(PersonAttribute.class, id);
1✔
360
        }
361
        
362
        /**
363
         * @see org.openmrs.api.PersonService#getAllPersonAttributeTypes(boolean)
364
         * @see org.openmrs.api.db.PersonDAO#getAllPersonAttributeTypes(boolean)
365
         */
366
        @Override
367
        public List<PersonAttributeType> getAllPersonAttributeTypes(boolean includeRetired) throws DAOException {
368
                Session session = sessionFactory.getCurrentSession();
1✔
369
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
370
                CriteriaQuery<PersonAttributeType> cq = cb.createQuery(PersonAttributeType.class);
1✔
371
                Root<PersonAttributeType> root = cq.from(PersonAttributeType.class);
1✔
372

373
                if (!includeRetired) {
1✔
374
                        cq.where(cb.isFalse(root.get("retired")));
1✔
375
                }
376

377
                cq.orderBy(cb.asc(root.get("sortWeight")));
1✔
378

379
                return session.createQuery(cq).getResultList();
1✔
380
        }
381

382
        /**
383
         * @see org.openmrs.api.db.PersonDAO#getPersonAttributeTypes(java.lang.String, java.lang.String,
384
         *      java.lang.Integer, java.lang.Boolean)
385
         */
386
        @Override
387
        // TODO - PersonServiceTest fails here
388
        public List<PersonAttributeType> getPersonAttributeTypes(String exactName, String format, Integer foreignKey, Boolean searchable) throws DAOException {
389
                Session session = sessionFactory.getCurrentSession();
1✔
390
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
391
                CriteriaQuery<PersonAttributeType> cq = cb.createQuery(PersonAttributeType.class);
1✔
392
                Root<PersonAttributeType> root = cq.from(PersonAttributeType.class);
1✔
393

394
                List<Predicate> predicates = new ArrayList<>();
1✔
395
                if (exactName != null) {
1✔
396
                        predicates.add(cb.equal(root.get("name"), exactName));
1✔
397
                }
398

399
                if (format != null) {
1✔
400
                        predicates.add(cb.equal(root.get("format"), format));
1✔
401
                }
402

403
                if (foreignKey != null) {
1✔
404
                        predicates.add(cb.equal(root.get("foreignKey"), foreignKey));
×
405
                }
406

407
                if (searchable != null) {
1✔
408
                        predicates.add(cb.equal(root.get("searchable"), searchable));
1✔
409
                }
410

411
                cq.where(predicates.toArray(new Predicate[]{}));
1✔
412

413
                return session.createQuery(cq).getResultList();
1✔
414
        }
415

416
        /**
417
         * @see org.openmrs.api.PersonService#getRelationship(java.lang.Integer)
418
         * @see org.openmrs.api.db.PersonDAO#getRelationship(java.lang.Integer)
419
         */
420
        @Override
421
        public Relationship getRelationship(Integer relationshipId) throws DAOException {
422

423
                return sessionFactory.getCurrentSession()
1✔
424
                        .get(Relationship.class, relationshipId);
1✔
425
        }
426
        
427
        /**
428
         * @see org.openmrs.api.PersonService#getAllRelationships(boolean)
429
         * @see org.openmrs.api.db.PersonDAO#getAllRelationships(boolean)
430
         */
431
        @Override
432
        public List<Relationship> getAllRelationships(boolean includeVoided) throws DAOException {
433
                Session session = sessionFactory.getCurrentSession();
1✔
434
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
435
                CriteriaQuery<Relationship> cq = cb.createQuery(Relationship.class);
1✔
436
                Root<Relationship> root = cq.from(Relationship.class);
1✔
437

438
                if (!includeVoided) {
1✔
439
                        cq.where(cb.isFalse(root.get("voided")));
1✔
440
                }
441

442
                return session.createQuery(cq).getResultList();
1✔
443
        }
444

445

446
        /**
447
         * @see org.openmrs.api.PersonService#getRelationships(org.openmrs.Person, org.openmrs.Person,
448
         *      org.openmrs.RelationshipType)
449
         * @see org.openmrs.api.db.PersonDAO#getRelationships(org.openmrs.Person, org.openmrs.Person,
450
         *      org.openmrs.RelationshipType)
451
         */
452
        @Override
453
        public List<Relationship> getRelationships(Person fromPerson, Person toPerson, RelationshipType relType) {
454
                Session session = sessionFactory.getCurrentSession();
1✔
455
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
456
                CriteriaQuery<Relationship> cq = cb.createQuery(Relationship.class);
1✔
457
                Root<Relationship> root = cq.from(Relationship.class);
1✔
458

459
                List<Predicate> predicates = new ArrayList<>();
1✔
460
                if (fromPerson != null) {
1✔
461
                        predicates.add(cb.equal(root.get("personA"), fromPerson));
1✔
462
                }
463
                if (toPerson != null) {
1✔
464
                        predicates.add(cb.equal(root.get("personB"), toPerson));
1✔
465
                }
466
                if (relType != null) {
1✔
467
                        predicates.add(cb.equal(root.get("relationshipType"), relType));
1✔
468
                }
469

470
                predicates.add(cb.isFalse(root.get("voided")));
1✔
471

472
                cq.where(predicates.toArray(new Predicate[]{}));
1✔
473

474
                return session.createQuery(cq).getResultList();
1✔
475
        }
476

477
        /**
478
         * @see org.openmrs.api.PersonService#getRelationships(org.openmrs.Person, org.openmrs.Person,
479
         *      org.openmrs.RelationshipType, java.util.Date, java.util.Date)
480
         * @see org.openmrs.api.db.PersonDAO#getRelationships(org.openmrs.Person, org.openmrs.Person,
481
         *      org.openmrs.RelationshipType, java.util.Date, java.util.Date)
482
         */
483
        @Override
484
        public List<Relationship> getRelationships(Person fromPerson, Person toPerson, RelationshipType relType, Date startEffectiveDate, Date endEffectiveDate) {
485
                Session session = sessionFactory.getCurrentSession();
1✔
486
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
487
                CriteriaQuery<Relationship> cq = cb.createQuery(Relationship.class);
1✔
488
                Root<Relationship> root = cq.from(Relationship.class);
1✔
489

490
                List<Predicate> predicates = new ArrayList<>();
1✔
491
                if (fromPerson != null) {
1✔
492
                        predicates.add(cb.equal(root.get("personA"), fromPerson));
1✔
493
                }
494
                if (toPerson != null) {
1✔
495
                        predicates.add(cb.equal(root.get("personB"), toPerson));
1✔
496
                }
497
                if (relType != null) {
1✔
498
                        predicates.add(cb.equal(root.get("relationshipType"), relType));
1✔
499
                }
500
                if (startEffectiveDate != null) {
1✔
501
                        Predicate startDatePredicate = cb.or(
1✔
502
                                cb.and(cb.lessThanOrEqualTo(root.get("startDate"), startEffectiveDate),
1✔
503
                                        cb.greaterThanOrEqualTo(root.get("endDate"), startEffectiveDate)),
1✔
504
                                cb.and(cb.lessThanOrEqualTo(root.get("startDate"), startEffectiveDate),
1✔
505
                                        cb.isNull(root.get("endDate"))),
1✔
506
                                cb.and(cb.isNull(root.get("startDate")),
1✔
507
                                        cb.greaterThanOrEqualTo(root.get("endDate"), startEffectiveDate)),
1✔
508
                                cb.and(cb.isNull(root.get("startDate")),
1✔
509
                                        cb.isNull(root.get("endDate")))
1✔
510
                        );
511
                        predicates.add(startDatePredicate);
1✔
512
                }
513
                if (endEffectiveDate != null) {
1✔
514
                        Predicate endDatePredicate = cb.or(
1✔
515
                                cb.and(cb.lessThanOrEqualTo(root.get("startDate"), endEffectiveDate),
1✔
516
                                        cb.greaterThanOrEqualTo(root.get("endDate"), endEffectiveDate)),
1✔
517
                                cb.and(cb.lessThanOrEqualTo(root.get("startDate"), endEffectiveDate),
1✔
518
                                        cb.isNull(root.get("endDate"))),
1✔
519
                                cb.and(cb.isNull(root.get("startDate")),
1✔
520
                                        cb.greaterThanOrEqualTo(root.get("endDate"), endEffectiveDate)),
1✔
521
                                cb.and(cb.isNull(root.get("startDate")),
1✔
522
                                        cb.isNull(root.get("endDate")))
1✔
523
                        );
524
                        predicates.add(endDatePredicate);
1✔
525
                }
526

527
                predicates.add(cb.isFalse(root.get("voided")));
1✔
528

529
                cq.where(predicates.toArray(new Predicate[]{}));
1✔
530

531
                return session.createQuery(cq).getResultList();
1✔
532
        }
533

534
        /**
535
         * @see org.openmrs.api.PersonService#getRelationshipType(java.lang.Integer)
536
         * @see org.openmrs.api.db.PersonDAO#getRelationshipType(java.lang.Integer)
537
         */
538
        @Override
539
        public RelationshipType getRelationshipType(Integer relationshipTypeId) throws DAOException {
540
                return sessionFactory.getCurrentSession().get(
1✔
541
                    RelationshipType.class, relationshipTypeId);
542
        }
543
        
544
        /**
545
         * @see org.openmrs.api.PersonService#getRelationshipTypes(java.lang.String, java.lang.Boolean)
546
         * @see org.openmrs.api.db.PersonDAO#getRelationshipTypes(java.lang.String, java.lang.Boolean)
547
         */
548
        @Override
549
        public List<RelationshipType> getRelationshipTypes(String relationshipTypeName, Boolean preferred) throws DAOException {
550
                Session session = sessionFactory.getCurrentSession();
1✔
551
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
552
                CriteriaQuery<RelationshipType> cq = cb.createQuery(RelationshipType.class);
1✔
553
                Root<RelationshipType> root = cq.from(RelationshipType.class);
1✔
554

555
                List<Predicate> predicates = new ArrayList<>();
1✔
556
                if (StringUtils.isNotEmpty(relationshipTypeName)) {
1✔
557
                        Expression<String> concatenatedFields = cb.concat(root.get("aIsToB"),
1✔
558
                                cb.concat("/", root.get("bIsToA")));
1✔
559
                        predicates.add(cb.like(concatenatedFields, relationshipTypeName));
1✔
560
                } else {
1✔
561
                        // Add a predicate that is always false
562
                        predicates.add(cb.or());
1✔
563
                }
564

565
                if (preferred != null) {
1✔
566
                        predicates.add(cb.equal(root.get("preferred"), preferred));
1✔
567
                }
568

569
                cq.where(predicates.toArray(new Predicate[]{}));
1✔
570
                return session.createQuery(cq).getResultList();
1✔
571
        }
572

573
        /**
574
         * @see org.openmrs.api.PersonService#saveRelationshipType(org.openmrs.RelationshipType)
575
         * @see org.openmrs.api.db.PersonDAO#saveRelationshipType(org.openmrs.RelationshipType)
576
         */
577
        @Override
578
        public RelationshipType saveRelationshipType(RelationshipType relationshipType) throws DAOException {
579
                sessionFactory.getCurrentSession().saveOrUpdate(relationshipType);
1✔
580
                return relationshipType;
1✔
581
        }
582
        
583
        /**
584
         * @see org.openmrs.api.PersonService#purgeRelationshipType(org.openmrs.RelationshipType)
585
         * @see org.openmrs.api.db.PersonDAO#deleteRelationshipType(org.openmrs.RelationshipType)
586
         */
587
        @Override
588
        public void deleteRelationshipType(RelationshipType relationshipType) throws DAOException {
589
                sessionFactory.getCurrentSession().delete(relationshipType);
1✔
590
        }
1✔
591
        
592
        /**
593
         * @see org.openmrs.api.PersonService#purgePerson(org.openmrs.Person)
594
         * @see org.openmrs.api.db.PersonDAO#deletePerson(org.openmrs.Person)
595
         */
596
        @Override
597
        public void deletePerson(Person person) throws DAOException {
598
                HibernatePersonDAO.deletePersonAndAttributes(sessionFactory, person);
1✔
599
        }
1✔
600
        
601
        /**
602
         * @see org.openmrs.api.PersonService#savePerson(org.openmrs.Person)
603
         * @see org.openmrs.api.db.PersonDAO#savePerson(org.openmrs.Person)
604
         */
605
        @Override
606
        public Person savePerson(Person person) throws DAOException {
607
                sessionFactory.getCurrentSession().saveOrUpdate(person);
1✔
608
                return person;
1✔
609
        }
610
        
611
        /**
612
         * @see org.openmrs.api.PersonService#saveRelationship(org.openmrs.Relationship)
613
         * @see org.openmrs.api.db.PersonDAO#saveRelationship(org.openmrs.Relationship)
614
         */
615
        @Override
616
        public Relationship saveRelationship(Relationship relationship) throws DAOException {
617
                sessionFactory.getCurrentSession().saveOrUpdate(relationship);
1✔
618
                return relationship;
1✔
619
        }
620
        
621
        /**
622
         * @see org.openmrs.api.PersonService#purgeRelationship(org.openmrs.Relationship)
623
         * @see org.openmrs.api.db.PersonDAO#deleteRelationship(org.openmrs.Relationship)
624
         */
625
        @Override
626
        public void deleteRelationship(Relationship relationship) throws DAOException {
627
                sessionFactory.getCurrentSession().delete(relationship);
1✔
628
        }
1✔
629
        
630
        /**
631
         * Used by deletePerson, deletePatient, and deleteUser to remove all properties of a person
632
         * before deleting them.
633
         * 
634
         * @param sessionFactory the session factory from which to pull the current session
635
         * @param person the person to delete
636
         */
637
        public static void deletePersonAndAttributes(SessionFactory sessionFactory, Person person) {
638
                // delete properties and fields so hibernate can't complain
639
                for (PersonAddress address : person.getAddresses()) {
1✔
640
                        if (address.getDateCreated() == null) {
1✔
641
                                sessionFactory.getCurrentSession().evict(address);
×
642
                        } else {
643
                                sessionFactory.getCurrentSession().delete(address);
1✔
644
                        }
645
                }
1✔
646
                person.setAddresses(null);
1✔
647
                
648
                for (PersonAttribute attribute : person.getAttributes()) {
1✔
649
                        if (attribute.getDateCreated() == null) {
1✔
650
                                sessionFactory.getCurrentSession().evict(attribute);
×
651
                        } else {
652
                                sessionFactory.getCurrentSession().delete(attribute);
1✔
653
                        }
654
                }
1✔
655
                person.setAttributes(null);
1✔
656
                
657
                for (PersonName name : person.getNames()) {
1✔
658
                        if (name.getDateCreated() == null) {
1✔
659
                                sessionFactory.getCurrentSession().evict(name);
×
660
                        } else {
661
                                sessionFactory.getCurrentSession().delete(name);
1✔
662
                        }
663
                }
1✔
664
                person.setNames(null);
1✔
665
                
666
                // finally, just tell hibernate to delete our object
667
                sessionFactory.getCurrentSession().delete(person);
1✔
668
        }
1✔
669
        
670
        /**
671
         * @see org.openmrs.api.db.PersonDAO#getPersonAttributeTypeByUuid(java.lang.String)
672
         */
673
        @Override
674
        public PersonAttributeType getPersonAttributeTypeByUuid(String uuid) {
675
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, PersonAttributeType.class, uuid);
1✔
676
        }
677
        
678
        /**
679
         * @see org.openmrs.api.db.PersonDAO#getSavedPersonAttributeTypeName(org.openmrs.PersonAttributeType)
680
         */
681
        @Override
682
        public String getSavedPersonAttributeTypeName(PersonAttributeType personAttributeType) {
683
                SQLQuery sql = sessionFactory.getCurrentSession().createSQLQuery(
1✔
684
                    "select name from person_attribute_type where person_attribute_type_id = :personAttributeTypeId");
685
                sql.setParameter("personAttributeTypeId", personAttributeType.getId());
1✔
686
                return (String) sql.uniqueResult();
1✔
687
        }
688

689
        @Override
690
        public Boolean getSavedPersonAttributeTypeSearchable(PersonAttributeType personAttributeType) {
691
                SQLQuery sql = sessionFactory.getCurrentSession().createSQLQuery(
1✔
692
                        "select searchable from person_attribute_type where person_attribute_type_id = :personAttributeTypeId");
693
                sql.setParameter("personAttributeTypeId", personAttributeType.getId());
1✔
694
                return (Boolean) sql.uniqueResult();
1✔
695
        }
696

697
        /**
698
         * @see org.openmrs.api.db.PersonDAO#getPersonByUuid(java.lang.String)
699
         */
700
        @Override
701
        public Person getPersonByUuid(String uuid) {
702
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, Person.class, uuid);
1✔
703
        }
704
        
705
        @Override
706
        public PersonAddress getPersonAddressByUuid(String uuid) {
707
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, PersonAddress.class, uuid);
1✔
708
        }
709
        
710
        /**
711
         * @see org.openmrs.api.db.PersonDAO#savePersonMergeLog(PersonMergeLog)
712
         */
713
        @Override
714
        public PersonMergeLog savePersonMergeLog(PersonMergeLog personMergeLog) throws DAOException {
715
                sessionFactory.getCurrentSession().saveOrUpdate(personMergeLog);
1✔
716
                return personMergeLog;
1✔
717
        }
718
        
719
        /**
720
         * @see org.openmrs.api.db.PersonDAO#getPersonMergeLog(java.lang.Integer)
721
         */
722
        @Override
723
        public PersonMergeLog getPersonMergeLog(Integer id) throws DAOException {
724
                return sessionFactory.getCurrentSession().get(PersonMergeLog.class, id);
×
725
        }
726
        
727
        /**
728
         * @see org.openmrs.api.db.PersonDAO#getPersonMergeLogByUuid(String)
729
         */
730
        @Override
731
        public PersonMergeLog getPersonMergeLogByUuid(String uuid) throws DAOException {
732
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, PersonMergeLog.class, uuid);
1✔
733
        }
734
        
735
        /**
736
         * @see org.openmrs.api.db.PersonDAO#getWinningPersonMergeLogs(org.openmrs.Person)
737
         */
738
        @Override
739
        @SuppressWarnings("unchecked")
740
        public List<PersonMergeLog> getWinningPersonMergeLogs(Person person) throws DAOException {
741
                return (List<PersonMergeLog>) sessionFactory.getCurrentSession().createQuery(
1✔
742
                    "from PersonMergeLog p where p.winner.id = :winnerId").setParameter("winnerId", person.getId()).list();
1✔
743
        }
744
        
745
        /**
746
         * @see org.openmrs.api.db.PersonDAO#getLosingPersonMergeLogs(org.openmrs.Person)
747
         */
748
        @Override
749
        public PersonMergeLog getLosingPersonMergeLogs(Person person) throws DAOException {
750
                return (PersonMergeLog) sessionFactory.getCurrentSession().createQuery(
1✔
751
                    "from PersonMergeLog p where p.loser.id = :loserId").setParameter("loserId", person.getId()).uniqueResult();
1✔
752
        }
753
        
754
        /**
755
         * @see org.openmrs.api.db.PersonDAO#getAllPersonMergeLogs()
756
         */
757
        @Override
758
        @SuppressWarnings("unchecked")
759
        public List<PersonMergeLog> getAllPersonMergeLogs() throws DAOException {
760
                return (List<PersonMergeLog>) sessionFactory.getCurrentSession().createQuery("from PersonMergeLog p").list();
1✔
761
        }
762
        
763
        @Override
764
        public PersonAttribute getPersonAttributeByUuid(String uuid) {
765
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, PersonAttribute.class, uuid);
1✔
766
        }
767
        
768
        /**
769
         * @see org.openmrs.api.db.PersonDAO#getPersonName(Integer)
770
         */
771
        @Override
772
        public PersonName getPersonName(Integer personNameId) {
773
                return sessionFactory.getCurrentSession().get(PersonName.class, personNameId);
1✔
774
        }
775
        
776
        /**
777
         * @see org.openmrs.api.db.PersonDAO#getPersonNameByUuid(String)
778
         */
779
        @Override
780
        public PersonName getPersonNameByUuid(String uuid) {
781
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, PersonName.class, uuid);
1✔
782
        }
783
        
784
        /**
785
         * @see org.openmrs.api.db.PersonDAO#getRelationshipByUuid(java.lang.String)
786
         */
787
        @Override
788
        public Relationship getRelationshipByUuid(String uuid) {
789
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, Relationship.class, uuid);
1✔
790
        }
791
        
792
        /**
793
         * @see org.openmrs.api.db.PersonDAO#getRelationshipTypeByUuid(java.lang.String)
794
         */
795
        @Override
796
        public RelationshipType getRelationshipTypeByUuid(String uuid) {
797
                return HibernateUtil.getUniqueEntityByUUID(sessionFactory, RelationshipType.class, uuid);
1✔
798
        }
799
        
800
        /**
801
         * @see org.openmrs.api.db.PersonDAO#getAllRelationshipTypes(boolean)
802
         */
803
        @Override
804
        public List<RelationshipType> getAllRelationshipTypes(boolean includeRetired) {
805
                Session session = sessionFactory.getCurrentSession();
1✔
806
                CriteriaBuilder cb = session.getCriteriaBuilder();
1✔
807
                CriteriaQuery<RelationshipType> cq = cb.createQuery(RelationshipType.class);
1✔
808
                Root<RelationshipType> root = cq.from(RelationshipType.class);
1✔
809

810
                if (!includeRetired) {
1✔
811
                        cq.where(cb.isFalse(root.get("retired")));
1✔
812
                }
813

814
                cq.orderBy(cb.asc(root.get("weight")));
1✔
815

816
                return session.createQuery(cq).getResultList();
1✔
817
        }
818

819
        /**
820
         * @see org.openmrs.api.PersonService#savePersonName(org.openmrs.PersonName)
821
         * @see org.openmrs.api.db.PersonDAO#savePersonName(org.openmrs.PersonName)
822
         */
823
        @Override
824
        public PersonName savePersonName(PersonName personName) {
825
                sessionFactory.getCurrentSession().saveOrUpdate(personName);
1✔
826
                return personName;
1✔
827
        }
828
        
829
        /**
830
         * @see org.openmrs.api.PersonService#savePersonAddress(org.openmrs.PersonAddress)
831
         * @see org.openmrs.api.db.PersonDAO#savePersonAddress(org.openmrs.PersonAddress)
832
         */
833
        @Override
834
        public PersonAddress savePersonAddress(PersonAddress personAddress) {
835
                sessionFactory.getCurrentSession().saveOrUpdate(personAddress);
1✔
836
                return personAddress;
1✔
837
        }
838

839
        /**
840
         * Hydrates a page of full-text search hits into their persons in a single order-preserving
841
         * {@code multiLoad} instead of one {@code get} per hit, so a page issues a bounded number of
842
         * entity loads. Loading the whole page in one operation also lets the eagerly-fetched person names,
843
         * addresses, and attributes be read in a bounded number of batched selects across the page rather
844
         * than a separate set of selects for every person. Hits whose person is no longer present in the
845
         * database (a stale search-index entry) are dropped from the results.
846
         *
847
         * @param hits the page of search hits, in the order they should appear in the results
848
         * @param personIdExtractor extracts the person id from a hit
849
         * @param <S> the search hit type
850
         * @return the persons for the page, in hit order, with missing rows removed
851
         */
852
        private <S> List<Person> multiLoadPersons(List<S> hits, Function<S, Integer> personIdExtractor) {
853
                List<Integer> personIds = hits.stream().map(personIdExtractor).collect(Collectors.toList());
1✔
854
                List<Person> persons = sessionFactory.getCurrentSession().byMultipleIds(Person.class).multiLoad(personIds).stream()
1✔
855
                        .filter(Objects::nonNull).collect(Collectors.toList());
1✔
856
                if (persons.size() < personIds.size()) {
1✔
NEW
857
                        log.debug("Dropped {} person search hit(s) with no matching row (stale search index?)",
×
NEW
858
                            personIds.size() - persons.size());
×
859
                }
860
                return persons;
1✔
861
        }
862

863
}
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