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

openmrs / openmrs-core / 14752386992

30 Apr 2025 10:25AM UTC coverage: 64.96% (-0.1%) from 65.095%
14752386992

push

github

web-flow
TRUNK-6316 Upgrade Hibernate Search to 6.2.4 (#5005)

501 of 591 new or added lines in 24 files covered. (84.77%)

21 existing lines in 6 files now uncovered.

23344 of 35936 relevant lines covered (64.96%)

0.65 hits per line

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

91.46
/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.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.util.ArrayList;
13
import java.util.Arrays;
14
import java.util.Collections;
15
import java.util.Comparator;
16
import java.util.Date;
17
import java.util.HashMap;
18
import java.util.LinkedList;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.Set;
22
import java.util.regex.Pattern;
23

24
import org.apache.commons.collections.CollectionUtils;
25
import org.apache.commons.lang3.StringUtils;
26
import org.hibernate.Criteria;
27
import org.hibernate.Query;
28
import org.hibernate.SQLQuery;
29
import org.hibernate.SessionFactory;
30
import org.hibernate.criterion.Order;
31
import org.hibernate.criterion.Restrictions;
32
import org.hibernate.persister.entity.AbstractEntityPersister;
33
import org.hibernate.search.engine.search.predicate.SearchPredicate;
34
import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory;
35
import org.openmrs.Allergies;
36
import org.openmrs.Allergy;
37
import org.openmrs.Location;
38
import org.openmrs.Patient;
39
import org.openmrs.PatientIdentifier;
40
import org.openmrs.PatientIdentifierType;
41
import org.openmrs.PatientIdentifierType.UniquenessBehavior;
42
import org.openmrs.PatientProgram;
43
import org.openmrs.Person;
44
import org.openmrs.PersonAttribute;
45
import org.openmrs.PersonName;
46
import org.openmrs.api.context.Context;
47
import org.openmrs.api.db.DAOException;
48
import org.openmrs.api.db.PatientDAO;
49
import org.openmrs.api.db.hibernate.search.SearchQueryUnique;
50
import org.openmrs.api.db.hibernate.search.session.SearchSessionFactory;
51
import org.openmrs.util.OpenmrsConstants;
52
import org.openmrs.util.OpenmrsUtil;
53
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
55

56
/**
57
 * Hibernate specific database methods for the PatientService
58
 *
59
 * @see org.openmrs.api.context.Context
60
 * @see org.openmrs.api.db.PatientDAO
61
 * @see org.openmrs.api.PatientService
62
 */
63
public class HibernatePatientDAO implements PatientDAO {
1✔
64
        
65
        private static final Logger log = LoggerFactory.getLogger(HibernatePatientDAO.class);
1✔
66
        
67
        /**
68
         * Hibernate session factory
69
         */
70
        private SessionFactory sessionFactory;
71
        
72
        private SearchSessionFactory searchSessionFactory;
73
        
74
        /**
75
         * Set session factory
76
         *
77
         * @param sessionFactory
78
         */
79
        public void setSessionFactory(SessionFactory sessionFactory) {
80
                this.sessionFactory = sessionFactory;
1✔
81
        }
1✔
82

83
        public void setSearchSessionFactory(SearchSessionFactory searchSessionFactory) {
84
                this.searchSessionFactory = searchSessionFactory;
1✔
85
        }
1✔
86

87
        /**
88
     * @param patientId  internal patient identifier
89
     * @return           patient with given internal identifier
90
         * @see org.openmrs.api.PatientService#getPatient(java.lang.Integer)
91
         */
92
        @Override
93
        public Patient getPatient(Integer patientId) {
94
                return (Patient) sessionFactory.getCurrentSession().get(Patient.class, patientId);
1✔
95
        }
96
        
97
        /**
98
     * @param patient  patient to be created or updated
99
     * @return         patient who was created or updated
100
         * @see org.openmrs.api.db.PatientDAO#savePatient(org.openmrs.Patient)
101
         */
102
        @Override
103
        public Patient savePatient(Patient patient) throws DAOException {
104
                if (patient.getPatientId() == null) {
1✔
105
                        // if we're saving a new patient, just do the normal thing
106
                        // and rows in the person and patient table will be created by
107
                        // hibernate
108
                        sessionFactory.getCurrentSession().saveOrUpdate(patient);
1✔
109
                        return patient;
1✔
110
                } else {
111
                        // if we're updating a patient, its possible that a person
112
                        // row exists but a patient row does not. hibernate does not deal
113
                        // with this correctly right now, so we must create a dummy row
114
                        // in the patient table before saving
115
                        
116
                        // Check to make sure we have a row in the patient table already.
117
                        // If we don't have a row, create it so Hibernate doesn't bung
118
                        // things up
119
                        insertPatientStubIfNeeded(patient);
1✔
120
                        
121
                        // Note: A merge might be necessary here because hibernate thinks that Patients
122
                        // and Persons are the same objects.  So it sees a Person object in the
123
                        // cache and claims it is a duplicate of this Patient object.
124
                        sessionFactory.getCurrentSession().saveOrUpdate(patient);
1✔
125
                        
126
                        return patient;
1✔
127
                }
128
        }
129
        
130
        /**
131
         * Inserts a row into the patient table This avoids hibernate's bunging of our
132
         * person/patient/user inheritance
133
         *
134
         * @param patient
135
         */
136
        private void insertPatientStubIfNeeded(Patient patient) {
137
                
138
                boolean stubInsertNeeded = false;
1✔
139
                
140
                if (patient.getPatientId() != null) {
1✔
141
                        // check if there is a row with a matching patient.patient_id
142
                        String sql = "SELECT 1 FROM patient WHERE patient_id = :patientId";
1✔
143
                        Query query = sessionFactory.getCurrentSession().createSQLQuery(sql);
1✔
144
                        query.setInteger("patientId", patient.getPatientId());
1✔
145
                        
146
                        stubInsertNeeded = (query.uniqueResult() == null);
1✔
147
                }
148
                
149
                if (stubInsertNeeded) {
1✔
150
                        //If not yet persisted
151
                        if (patient.getCreator() == null) {
1✔
152
                                patient.setCreator(Context.getAuthenticatedUser());
×
153
                        }
154
                        //If not yet persisted
155
                        if (patient.getDateCreated() == null) {
1✔
156
                                patient.setDateCreated(new Date());
×
157
                        }
158
                        
159
                        String insert = "INSERT INTO patient (patient_id, creator, voided, date_created) VALUES (:patientId, :creator, :voided, :dateCreated)";
1✔
160
                        Query query = sessionFactory.getCurrentSession().createSQLQuery(insert);
1✔
161
                        query.setInteger("patientId", patient.getPatientId());
1✔
162
                        query.setInteger("creator", patient.getCreator().getUserId());
1✔
163
                        query.setBoolean("voided", false);
1✔
164
                        query.setDate("dateCreated", patient.getDateCreated());
1✔
165
                        
166
                        query.executeUpdate();
1✔
167
                        
168
                        //Without evicting person, you will get this error when promoting person to patient
169
                        //org.hibernate.NonUniqueObjectException: a different object with the same identifier
170
                        //value was already associated with the session: [org.openmrs.Patient#]
171
                        //see TRUNK-3728
172
                        Person person = (Person) sessionFactory.getCurrentSession().get(Person.class, patient.getPersonId());
1✔
173
                        sessionFactory.getCurrentSession().evict(person);
1✔
174
                }
175
                
176
        }
1✔
177
        
178
        public List<Patient> getPatients(String query, List<PatientIdentifierType> identifierTypes,
179
                boolean matchIdentifierExactly, Integer start, Integer length) throws DAOException{
180
                
181
                if (StringUtils.isBlank(query) || (length != null && length < 1) || identifierTypes == null || identifierTypes.isEmpty())  {
1✔
182
                        return Collections.emptyList();
×
183
                }
184
                
185
                Integer tmpStart = start;
1✔
186
                if (tmpStart == null || tmpStart < 0) {
1✔
187
                        tmpStart = 0;
×
188
                }
189
                
190
                Integer tmpLength = length;
1✔
191
                if (tmpLength == null) {
1✔
192
                        tmpLength = HibernatePersonDAO.getMaximumSearchResults();
1✔
193
                }
194
                
195
                return findPatients(query, identifierTypes, matchIdentifierExactly, tmpStart, tmpLength);
1✔
196
        }
197
        
198
        /**
199
         * @see org.openmrs.api.db.PatientDAO#getPatients(String, boolean, Integer, Integer)
200
         * <strong>Should</strong> return exact match first
201
         */
202
        @Override
203
        public List<Patient> getPatients(String query, boolean includeVoided, Integer start, Integer length) throws DAOException {
204
                if (StringUtils.isBlank(query) || (length != null && length < 1)) {
1✔
205
                        return Collections.emptyList();
1✔
206
                }
207

208
                Integer tmpStart = start;
1✔
209
                if (tmpStart == null || tmpStart < 0) {
1✔
210
                        tmpStart = 0;
1✔
211
                }
212

213
                Integer tmpLength = length;
1✔
214
                if (tmpLength == null) {
1✔
215
                        tmpLength = HibernatePersonDAO.getMaximumSearchResults();
1✔
216
                }
217

218
                List<Patient> patients = findPatients(query, includeVoided, tmpStart, tmpLength);
1✔
219

220
                return new ArrayList<>(patients);
1✔
221
        }
222
        
223
        /**
224
         * @see org.openmrs.api.db.PatientDAO#getPatients(String, Integer, Integer)
225
         */
226
        @Override
227
        public List<Patient> getPatients(String query, Integer start, Integer length) throws DAOException {
228
                return getPatients(query, false, start, length);
1✔
229
        }
230
        
231
        private void setFirstAndMaxResult(Criteria criteria, Integer start, Integer length) {
232
                if (start != null) {
×
233
                        criteria.setFirstResult(start);
×
234
                }
235
                
236
                int maximumSearchResults = HibernatePersonDAO.getMaximumSearchResults();
×
237
                if (length != null && length < maximumSearchResults) {
×
238
                        criteria.setMaxResults(length);
×
239
                } else {
240
                        log.debug("Limiting the size of the number of matching patients to {}", maximumSearchResults);
×
241
                        criteria.setMaxResults(maximumSearchResults);
×
242
                }
243
        }
×
244
        
245
        /**
246
         * @see org.openmrs.api.db.PatientDAO#getAllPatients(boolean)
247
         */
248
        @SuppressWarnings("unchecked")
249
        @Override
250
        public List<Patient> getAllPatients(boolean includeVoided) throws DAOException {
251
                Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Patient.class);
1✔
252
                
253
                if (!includeVoided) {
1✔
254
                        criteria.add(Restrictions.eq("voided", false));
1✔
255
                }
256
                
257
                return criteria.list();
1✔
258
        }
259
        
260
        /**
261
         * @see org.openmrs.api.PatientService#purgePatientIdentifierType(org.openmrs.PatientIdentifierType)
262
         * @see org.openmrs.api.db.PatientDAO#deletePatientIdentifierType(org.openmrs.PatientIdentifierType)
263
         */
264
        @Override
265
        public void deletePatientIdentifierType(PatientIdentifierType patientIdentifierType) throws DAOException {
266
                sessionFactory.getCurrentSession().delete(patientIdentifierType);
1✔
267
        }
1✔
268
        
269
        /**
270
         * @see org.openmrs.api.PatientService#getPatientIdentifiers(java.lang.String, java.util.List, java.util.List, java.util.List, java.lang.Boolean)
271
         */
272
        @SuppressWarnings("unchecked")
273
        @Override
274
        public List<PatientIdentifier> getPatientIdentifiers(String identifier,
275
                List<PatientIdentifierType> patientIdentifierTypes, List<Location> locations, List<Patient> patients,
276
                Boolean isPreferred) throws DAOException {
277
                Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PatientIdentifier.class);
1✔
278
                
279
                // join with the patient table to prevent patient identifiers from patients
280
                // that already voided getting returned
281
                criteria.createAlias("patient", "patient");
1✔
282

283
                criteria.add(Restrictions.eq("patient.voided", false));
1✔
284
                
285
                criteria.add(Restrictions.eq("voided", false));
1✔
286
                
287
                if (identifier != null) {
1✔
288
                        criteria.add(Restrictions.eq("identifier", identifier));
1✔
289
                }
290
                
291
                if (!patientIdentifierTypes.isEmpty()) {
1✔
292
                        criteria.add(Restrictions.in("identifierType", patientIdentifierTypes));
1✔
293
                }
294
                
295
                if (!locations.isEmpty()) {
1✔
296
                        criteria.add(Restrictions.in("location", locations));
1✔
297
                }
298
                
299
                if (!patients.isEmpty()) {
1✔
300
                        criteria.add(Restrictions.in("patient", patients));
1✔
301
                }
302
                
303
                if (isPreferred != null) {
1✔
304
                        criteria.add(Restrictions.eq("preferred", isPreferred));
1✔
305
                }
306
                
307
                return criteria.list();
1✔
308
        }
309
        
310
        /**
311
         * @see org.openmrs.api.db.PatientDAO#savePatientIdentifierType(org.openmrs.PatientIdentifierType)
312
         */
313
        @Override
314
        public PatientIdentifierType savePatientIdentifierType(PatientIdentifierType patientIdentifierType) throws DAOException {
315
                sessionFactory.getCurrentSession().saveOrUpdate(patientIdentifierType);
1✔
316
                return patientIdentifierType;
1✔
317
        }
318
        
319
        /**
320
         * @see org.openmrs.api.PatientDAO#deletePatient(org.openmrs.Patient)
321
         */
322
        @Override
323
        public void deletePatient(Patient patient) throws DAOException {
324
                HibernatePersonDAO.deletePersonAndAttributes(sessionFactory, patient);
1✔
325
        }
1✔
326
        
327
        /**
328
         * @see org.openmrs.api.PatientService#getPatientIdentifierType(java.lang.Integer)
329
         */
330
        @Override
331
        public PatientIdentifierType getPatientIdentifierType(Integer patientIdentifierTypeId) throws DAOException {
332
                return (PatientIdentifierType) sessionFactory.getCurrentSession().get(PatientIdentifierType.class,
1✔
333
                    patientIdentifierTypeId);
334
        }
335
        
336
        /**
337
         * <strong>Should</strong> not return null when includeRetired is false
338
         * <strong>Should</strong> not return retired when includeRetired is false
339
         * <strong>Should</strong> not return null when includeRetired is true
340
         * <strong>Should</strong> return all when includeRetired is true
341
         * <strong>Should</strong> return ordered
342
         * @see org.openmrs.api.db.PatientDAO#getAllPatientIdentifierTypes(boolean)
343
         */
344
        @SuppressWarnings("unchecked")
345
        @Override
346
        public List<PatientIdentifierType> getAllPatientIdentifierTypes(boolean includeRetired) throws DAOException {
347
                Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PatientIdentifierType.class);
1✔
348
                
349
                if (!includeRetired) {
1✔
350
                        criteria.add(Restrictions.eq("retired", false));
1✔
351
                } else {
352
                        //retired last
353
                        criteria.addOrder(Order.asc("retired"));
1✔
354
                }
355
                
356
                //required first
357
                criteria.addOrder(Order.desc("required"));
1✔
358
                criteria.addOrder(Order.asc("name"));
1✔
359
                criteria.addOrder(Order.asc("patientIdentifierTypeId"));
1✔
360
                
361
                return criteria.list();
1✔
362
        }
363
        
364
        /**
365
         * @see org.openmrs.api.db.PatientDAO#getPatientIdentifierTypes(java.lang.String,
366
         *      java.lang.String, java.lang.Boolean, java.lang.Boolean)
367
         *
368
         * <strong>Should</strong> return non retired patient identifier types with given name
369
         * <strong>Should</strong> return non retired patient identifier types with given format
370
         * <strong>Should</strong> return non retired patient identifier types that are not required
371
         * <strong>Should</strong> return non retired patient identifier types that are required
372
         * <strong>Should</strong> return non retired patient identifier types that has checkDigit
373
         * <strong>Should</strong> return non retired patient identifier types that has not CheckDigit
374
         * <strong>Should</strong> return only non retired patient identifier types
375
         * <strong>Should</strong> return non retired patient identifier types ordered by required first
376
         * <strong>Should</strong> return non retired patient identifier types ordered by required and name
377
         * <strong>Should</strong> return non retired patient identifier types ordered by required name and type id
378
         *
379
         */
380
        @SuppressWarnings("unchecked")
381
        @Override
382
        public List<PatientIdentifierType> getPatientIdentifierTypes(String name, String format, Boolean required,
383
                Boolean hasCheckDigit) throws DAOException {
384
                
385
                Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PatientIdentifierType.class);
1✔
386
                
387
                if (name != null) {
1✔
388
                        criteria.add(Restrictions.eq("name", name));
1✔
389
                }
390
                
391
                if (format != null) {
1✔
392
                        criteria.add(Restrictions.eq("format", format));
1✔
393
                }
394
                
395
                if (required != null) {
1✔
396
                        criteria.add(Restrictions.eq("required", required));
1✔
397
                }
398
                
399
                if (hasCheckDigit != null) {
1✔
400
                        criteria.add(Restrictions.eq("checkDigit", hasCheckDigit));
×
401
                }
402
                
403
                criteria.add(Restrictions.eq("retired", false));
1✔
404
                
405
                //required first
406
                criteria.addOrder(Order.desc("required"));
1✔
407
                criteria.addOrder(Order.asc("name"));
1✔
408
                criteria.addOrder(Order.asc("patientIdentifierTypeId"));
1✔
409
                
410
                return criteria.list();
1✔
411
        }
412
        
413
        /**
414
     * @param attributes attributes on a Person or Patient object. similar to: [gender, givenName,
415
     *                   middleName, familyName]
416
     * @return           list of patients that match other patients
417
         * @see org.openmrs.api.db.PatientDAO#getDuplicatePatientsByAttributes(java.util.List)
418
         */
419
        @SuppressWarnings("unchecked")
420
        @Override
421
        public List<Patient> getDuplicatePatientsByAttributes(List<String> attributes) {
422
                List<Patient> patients = new ArrayList<>();
1✔
423
                List<Integer> patientIds = new ArrayList<>();
1✔
424

425
                if (!attributes.isEmpty()) {
1✔
426

427
                        String sqlString = getDuplicatePatientsSQLString(attributes);
1✔
428
                        if(sqlString != null) {
1✔
429

430
                                SQLQuery sqlquery = sessionFactory.getCurrentSession().createSQLQuery(sqlString);
1✔
431
                                patientIds = sqlquery.list();
1✔
432
                                if (!patientIds.isEmpty()) {
1✔
433
                                        Query query = sessionFactory.getCurrentSession().createQuery(
1✔
434
                                                        "from Patient p1 where p1.patientId in (:ids)");
435
                                        query.setParameterList("ids", patientIds);
1✔
436
                                        patients = query.list();
1✔
437
                                }
438
                        }
439
                
440
                }
441

442
                sortDuplicatePatients(patients, patientIds);
1✔
443
                return patients;
1✔
444
        }
445

446
        private String getDuplicatePatientsSQLString(List<String> attributes) {
447
                StringBuilder outerSelect = new StringBuilder("select distinct t1.patient_id from patient t1 ");
1✔
448
                final String t5 = " = t5.";
1✔
449
                Set<String> patientFieldNames = OpenmrsUtil.getDeclaredFields(Patient.class);
1✔
450
                Set<String> personFieldNames = OpenmrsUtil.getDeclaredFields(Person.class);
1✔
451
                Set<String> personNameFieldNames = OpenmrsUtil.getDeclaredFields(PersonName.class);
1✔
452
                Set<String> identifierFieldNames = OpenmrsUtil.getDeclaredFields(PatientIdentifier.class);
1✔
453

454
                List<String> whereConditions = new ArrayList<>();
1✔
455

456

457
                List<String> innerFields = new ArrayList<>();
1✔
458
                StringBuilder innerSelect = new StringBuilder(" from patient p1 ");
1✔
459

460
                for (String attribute : attributes) {
1✔
461
                        if (attribute != null) {
1✔
462
                                attribute = attribute.trim();
1✔
463
                        }
464
                        if (patientFieldNames.contains(attribute)) {
1✔
465

466
                                AbstractEntityPersister aep = (AbstractEntityPersister) sessionFactory.getClassMetadata(Patient.class);
×
467
                                String[] properties = aep.getPropertyColumnNames(attribute);
×
468
                                if (properties.length >= 1) {
×
469
                                        attribute = properties[0];
×
470
                                }
471
                                whereConditions.add(" t1." + attribute + t5 + attribute);
×
472
                                innerFields.add("p1." + attribute);
×
473
                        } else if (personFieldNames.contains(attribute)) {
1✔
474
                                // check if outerSelect contains 'person' word, surrounded by spaces.
475
                                // otherwise it will wrongly match for example: 'person_name' etc.
476
                                if (!Arrays.asList(outerSelect.toString().split("\\s+")).contains("person")) {
1✔
477
                                        outerSelect.append("inner join person t2 on t1.patient_id = t2.person_id ");
1✔
478
                                        innerSelect.append("inner join person person1 on p1.patient_id = person1.person_id ");
1✔
479
                                }
480

481
                                AbstractEntityPersister aep = (AbstractEntityPersister) sessionFactory.getClassMetadata(Person.class);
1✔
482
                                if (aep != null) {
1✔
483
                                        String[] properties = aep.getPropertyColumnNames(attribute);
1✔
484
                                        if (properties != null && properties.length >= 1) {
1✔
485
                                                attribute = properties[0];
1✔
486
                                        }
487
                                }
488

489
                                whereConditions.add(" t2." + attribute + t5 + attribute);
1✔
490
                                innerFields.add("person1." + attribute);
1✔
491
                        } else if (personNameFieldNames.contains(attribute)) {
1✔
492
                                if (!outerSelect.toString().contains("person_name")) {
1✔
493
                                        outerSelect.append("inner join person_name t3 on t1.patient_id = t3.person_id ");
1✔
494
                                        innerSelect.append("inner join person_name pn1 on p1.patient_id = pn1.person_id ");
1✔
495
                                }
496

497
                                //Since we are firing a native query get the actual table column name from the field name of the entity
498
                                AbstractEntityPersister aep = (AbstractEntityPersister) sessionFactory
1✔
499
                                                .getClassMetadata(PersonName.class);
1✔
500
                                if (aep != null) {
1✔
501
                                        String[] properties = aep.getPropertyColumnNames(attribute);
1✔
502

503
                                        if (properties != null && properties.length >= 1) {
1✔
504
                                                attribute = properties[0];
1✔
505
                                        }
506
                                }
507

508
                                whereConditions.add(" t3." + attribute + t5 + attribute);
1✔
509
                                innerFields.add("pn1." + attribute);
1✔
510
                        } else if (identifierFieldNames.contains(attribute)) {
1✔
511
                                if (!outerSelect.toString().contains("patient_identifier")) {
1✔
512
                                        outerSelect.append("inner join patient_identifier t4 on t1.patient_id = t4.patient_id ");
1✔
513
                                        innerSelect.append("inner join patient_identifier pi1 on p1.patient_id = pi1.patient_id ");
1✔
514
                                }
515

516
                                AbstractEntityPersister aep = (AbstractEntityPersister) sessionFactory
1✔
517
                                                .getClassMetadata(PatientIdentifier.class);
1✔
518
                                if (aep != null) {
1✔
519

520
                                        String[] properties = aep.getPropertyColumnNames(attribute);
1✔
521
                                        if (properties != null && properties.length >= 1) {
1✔
522
                                                attribute = properties[0];
1✔
523
                                        }
524
                                }
525

526
                                whereConditions.add(" t4." + attribute + t5 + attribute);
1✔
527
                                innerFields.add("pi1." + attribute);
1✔
528
                        } else {
1✔
529
                                log.warn("Unidentified attribute: " + attribute);
1✔
530
                        }
531
                }
1✔
532
                if(CollectionUtils.isNotEmpty(innerFields) || CollectionUtils.isNotEmpty(whereConditions)) {
1✔
533
                        String innerFieldsJoined = StringUtils.join(innerFields, ", ");
1✔
534
                        String whereFieldsJoined = StringUtils.join(whereConditions, " and ");
1✔
535
                        String innerWhereCondition = "";
1✔
536
                        if (!attributes.contains("includeVoided")) {
1✔
537
                                innerWhereCondition = " where p1.voided = false ";
1✔
538
                        }
539
                        String innerQuery = "(Select " + innerFieldsJoined + innerSelect + innerWhereCondition + " group by "
1✔
540
                                        + innerFieldsJoined + " having count(*) > 1" + " order by " + innerFieldsJoined + ") t5";
541
                        return outerSelect + ", " + innerQuery + " where " + whereFieldsJoined + ";";
1✔
542
                }
543
                return null;
1✔
544
        }
545

546
        private void sortDuplicatePatients(List<Patient> patients, List<Integer> patientIds) {
547

548
                Map<Integer, Integer> patientIdOrder = new HashMap<>();
1✔
549
                int startPos = 0;
1✔
550
                for (Integer id : patientIds) {
1✔
551
                        patientIdOrder.put(id, startPos++);
1✔
552
                }
1✔
553
                class PatientIdComparator implements Comparator<Patient> {
554

555
                        private Map<Integer, Integer> sortOrder;
556

557
                        public PatientIdComparator(Map<Integer, Integer> sortOrder) {
1✔
558
                                this.sortOrder = sortOrder;
1✔
559
                        }
1✔
560

561
                        @Override
562
                        public int compare(Patient patient1, Patient patient2) {
563
                                Integer patPos1 = sortOrder.get(patient1.getPatientId());
1✔
564
                                if (patPos1 == null) {
1✔
565
                                        throw new IllegalArgumentException("Bad patient encountered: " + patient1.getPatientId());
×
566
                                }
567
                                Integer patPos2 = sortOrder.get(patient2.getPatientId());
1✔
568
                                if (patPos2 == null) {
1✔
569
                                        throw new IllegalArgumentException("Bad patient encountered: " + patient2.getPatientId());
×
570
                                }
571
                                return patPos1.compareTo(patPos2);
1✔
572
                        }
573
                }
574
                patients.sort(new PatientIdComparator(patientIdOrder));
1✔
575
        }
1✔
576
        
577
        /**
578
         * @see org.openmrs.api.db.PatientDAO#getPatientByUuid(java.lang.String)
579
         */
580
        @Override
581
        public Patient getPatientByUuid(String uuid) {
582
                Patient p;
583
                
584
                p = (Patient) sessionFactory.getCurrentSession().createQuery("from Patient p where p.uuid = :uuid").setString(
1✔
585
                    "uuid", uuid).uniqueResult();
1✔
586
                
587
                return p;
1✔
588
        }
589
        
590
        @Override
591
        public PatientIdentifier getPatientIdentifierByUuid(String uuid) {
592
                return (PatientIdentifier) sessionFactory.getCurrentSession().createQuery(
1✔
593
                    "from PatientIdentifier p where p.uuid = :uuid").setString("uuid", uuid).uniqueResult();
1✔
594
        }
595
        
596
        /**
597
         * @see org.openmrs.api.db.PatientDAO#getPatientIdentifierTypeByUuid(java.lang.String)
598
         */
599
        @Override
600
        public PatientIdentifierType getPatientIdentifierTypeByUuid(String uuid) {
601
                return (PatientIdentifierType) sessionFactory.getCurrentSession().createQuery(
1✔
602
                    "from PatientIdentifierType pit where pit.uuid = :uuid").setString("uuid", uuid).uniqueResult();
1✔
603
        }
604
        
605
        /**
606
         * This method uses a SQL query and does not load anything into the hibernate session. It exists
607
         * because of ticket #1375.
608
         *
609
         * @see org.openmrs.api.db.PatientDAO#isIdentifierInUseByAnotherPatient(org.openmrs.PatientIdentifier)
610
         */
611
        @Override
612
        public boolean isIdentifierInUseByAnotherPatient(PatientIdentifier patientIdentifier) {
613
                boolean checkPatient = patientIdentifier.getPatient() != null
1✔
614
                        && patientIdentifier.getPatient().getPatientId() != null;
1✔
615
                boolean checkLocation = patientIdentifier.getLocation() != null
1✔
616
                        && patientIdentifier.getIdentifierType().getUniquenessBehavior() == UniquenessBehavior.LOCATION;
1✔
617
                
618
                // switched this to an hql query so the hibernate cache can be considered as well as the database
619
                String hql = "select count(*) from PatientIdentifier pi, Patient p where pi.patient.patientId = p.patientId "
1✔
620
                        + "and p.voided = false and pi.voided = false and pi.identifier = :identifier and pi.identifierType = :idType";
621
                
622
                if (checkPatient) {
1✔
623
                        hql += " and p.patientId != :ptId";
1✔
624
                }
625
                if (checkLocation) {
1✔
626
                        hql += " and pi.location = :locationId";
1✔
627
                }
628
                
629
                Query query = sessionFactory.getCurrentSession().createQuery(hql);
1✔
630
                query.setString("identifier", patientIdentifier.getIdentifier());
1✔
631
                query.setInteger("idType", patientIdentifier.getIdentifierType().getPatientIdentifierTypeId());
1✔
632
                if (checkPatient) {
1✔
633
                        query.setInteger("ptId", patientIdentifier.getPatient().getPatientId());
1✔
634
                }
635
                if (checkLocation) {
1✔
636
                        query.setInteger("locationId", patientIdentifier.getLocation().getLocationId());
1✔
637
                }
638
                return !"0".equals(query.uniqueResult().toString());
1✔
639
        }
640
        
641
        /**
642
     * @param patientIdentifierId  the patientIdentifier id
643
     * @return                     the patientIdentifier matching the Id
644
         * @see org.openmrs.api.db.PatientDAO#getPatientIdentifier(java.lang.Integer)
645
         */
646
        @Override
647
        public PatientIdentifier getPatientIdentifier(Integer patientIdentifierId) throws DAOException {
648
                
649
                return (PatientIdentifier) sessionFactory.getCurrentSession().get(PatientIdentifier.class, patientIdentifierId);
1✔
650
                
651
        }
652
        
653
        /**
654
     * @param patientIdentifier patientIndentifier to be created or updated
655
     * @return                  patientIndentifier that was created or updated
656
         * @see org.openmrs.api.db.PatientDAO#savePatientIdentifier(org.openmrs.PatientIdentifier)
657
         */
658
        @Override
659
        public PatientIdentifier savePatientIdentifier(PatientIdentifier patientIdentifier) {
660
                
661
                sessionFactory.getCurrentSession().saveOrUpdate(patientIdentifier);
1✔
662
                return patientIdentifier;
1✔
663
                
664
        }
665
        
666
        /**
667
         * @see org.openmrs.api.PatientService#purgePatientIdentifier(org.openmrs.PatientIdentifier)
668
         * @see org.openmrs.api.db.PatientDAO#deletePatientIdentifier(org.openmrs.PatientIdentifier)
669
         */
670
        @Override
671
        public void deletePatientIdentifier(PatientIdentifier patientIdentifier) throws DAOException {
672
                
673
                sessionFactory.getCurrentSession().delete(patientIdentifier);
1✔
674
                
675
        }
1✔
676
        
677
        /**
678
         * @param query  the string to search on
679
         * @return       the number of patients matching the given search phrase
680
         * @see org.openmrs.api.db.PatientDAO#getCountOfPatients(String)
681
         */
682
        @Override
683
        public Long getCountOfPatients(String query) {
684
                return getCountOfPatients(query, false);
1✔
685
        }
686
        
687
        /**
688
         * @param query          the string to search on
689
         * @param includeVoided  true/false whether or not to included voided patients
690
         * @return               the number of patients matching the given search phrase
691
         * @see org.openmrs.api.db.PatientDAO#getCountOfPatients(String, boolean)
692
         */
693
        @Override
694
        public Long getCountOfPatients(String query, boolean includeVoided) {
695
                if (StringUtils.isBlank(query)) {
1✔
696
                        return 0L;
1✔
697
                }
698

699
                PersonQuery personQuery = new PersonQuery();
1✔
700

701
                return SearchQueryUnique.searchCount(searchSessionFactory,
1✔
702
                        SearchQueryUnique.newQuery(PatientIdentifier.class, f ->
1✔
703
                                        newPatientIdentifierSearchPredicate(f, query, includeVoided, false),
1✔
704
                                 "patient.personId", PatientIdentifier::getPatient).join(
1✔
705
                                         SearchQueryUnique.newQuery(PersonName.class, f ->
1✔
706
                                                personQuery.getPatientNameQuery(f, query, includeVoided),
1✔
707
                                         "person.personId", pN -> getPatient(pN.getPerson().getId())).join(
1✔
708
                                                 SearchQueryUnique.newQuery(PersonAttribute.class,
1✔
709
                                                f -> personQuery.getPatientAttributeQuery(f, query, includeVoided),
1✔
NEW
710
                                                 "person.personId", pA -> getPatient(pA.getPerson().getId())
×
711
                                        ))));
712
        }
713

714
    private List<Patient> findPatients(String query, boolean includeVoided) {
715
                return findPatients(query, includeVoided, null, null);
×
716
        }
717
        
718
        private List<Patient> findPatients(String query, List<PatientIdentifierType> identifierTypes, boolean matchExactly, 
719
                                                                           Integer start, Integer length) {
720
                Integer tmpStart = start;
1✔
721
                
722
                if (tmpStart == null) {
1✔
723
                        tmpStart = 0;
×
724
                }
725
                Integer maxLength = HibernatePersonDAO.getMaximumSearchResults();
1✔
726
                Integer tmpLength = length;
1✔
727
                if (tmpLength == null || tmpLength > maxLength) {
1✔
728
                        tmpLength = maxLength;
×
729
                }
730
                List<Patient> patients = new LinkedList<>();
1✔
731
                
732
                String minChars = Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_MIN_SEARCH_CHARACTERS);
1✔
733
                
734
                if (!StringUtils.isNumeric(minChars)) {
1✔
735
                        minChars = "" + OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_MIN_SEARCH_CHARACTERS;
1✔
736
                }
737
                if (query.length() < Integer.parseInt(minChars)) {
1✔
738
                        return patients;
×
739
                }
740
                
741
                return SearchQueryUnique.search(searchSessionFactory, SearchQueryUnique.newQuery(PatientIdentifier.class, 
1✔
742
                        f -> f.bool().with(b -> {
1✔
743
                        b.must(getPatientIdentifierSearchPredicate(f, query, matchExactly));
1✔
744
                        List<Integer> identifierTypeIds = new ArrayList<Integer>();
1✔
745
                        for(PatientIdentifierType identifierType : identifierTypes) {
1✔
746
                                identifierTypeIds.add(identifierType.getId());
1✔
747
                        }
1✔
748
                        b.filter(f.terms().field("identifierType.patientIdentifierTypeId").matchingAny(identifierTypeIds));
1✔
749
                        b.filter(f.match().field("patient.isPatient").matching(true));
1✔
750
                }).toPredicate(), "patient.personId", PatientIdentifier::getPatient), tmpStart, tmpLength);
1✔
751
        }
752
        
753
        public List<Patient> findPatients(String query, boolean includeVoided, Integer start, Integer length) {
754
                Integer tmpStart = start;
1✔
755
                if (tmpStart == null) {
1✔
756
                        tmpStart = 0;
×
757
                }
758
                Integer maxLength = HibernatePersonDAO.getMaximumSearchResults();
1✔
759
                Integer tmpLength = length;
1✔
760
                if (tmpLength == null || tmpLength > maxLength) {
1✔
761
                        tmpLength = maxLength;
1✔
762
                }
763

764
                List<Patient> patients = new LinkedList<>();
1✔
765

766
                String minChars = Context.getAdministrationService().getGlobalProperty(
1✔
767
                        OpenmrsConstants.GLOBAL_PROPERTY_MIN_SEARCH_CHARACTERS);
768

769
                if (!StringUtils.isNumeric(minChars)) {
1✔
770
                        minChars = "" + OpenmrsConstants.GLOBAL_PROPERTY_DEFAULT_MIN_SEARCH_CHARACTERS;
1✔
771
                }
772
                if (query.length() < Integer.parseInt(minChars)) {
1✔
773
                        return patients;
1✔
774
                }
775

776
                PersonQuery personQuery = new PersonQuery();
1✔
777

778
                patients = SearchQueryUnique.search(searchSessionFactory,
1✔
779
                        SearchQueryUnique.newQuery(PatientIdentifier.class, f -> 
1✔
780
                                        newPatientIdentifierSearchPredicate(f, query, includeVoided, false), 
1✔
781
                                 "patient.personId", PatientIdentifier::getPatient).join(
1✔
782
                                         SearchQueryUnique.newQuery(PersonName.class, f -> 
1✔
783
                                        personQuery.getPatientNameQuery(f, query, includeVoided),
1✔
784
                                         "person.personId", pN -> getPatient(pN.getPerson().getId())).join(
1✔
785
                                                 SearchQueryUnique.newQuery(PersonAttribute.class,
1✔
786
                                                f -> personQuery.getPatientAttributeQuery(f, query, includeVoided), 
1✔
787
                                                "person.personId", pA -> getPatient(pA.getPerson().getId())
1✔
788
                                                ))), start, length);
789

790
                return patients;
1✔
791
        }
792
        
793
        private SearchPredicate getPatientIdentifierSearchPredicate(SearchPredicateFactory f, String paramQuery, boolean matchExactly) {
794
                List<String> tokens = tokenizeIdentifierQuery(removeIdentifierPadding(paramQuery));
1✔
795
                final String query = StringUtils.join(tokens, " | ");
1✔
796
                //TODO: hibernate search identifierType?
797
                //fields.add("identifierType");
798
                return f.bool().with(b -> {
1✔
799
                        b.minimumShouldMatchNumber(1);
1✔
800
                        b.should(f.simpleQueryString().field("identifierPhrase").matching(query).boost(8f));
1✔
801
                        String matchMode = Context.getAdministrationService()
1✔
802
                                .getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_PATIENT_IDENTIFIER_SEARCH_MATCH_MODE);
1✔
803
                        if (matchExactly) {
1✔
804
                                b.should(f.simpleQueryString().field("identifierExact").matching(query).boost(4f));
1✔
805
                        }
806
                        else if (OpenmrsConstants.GLOBAL_PROPERTY_PATIENT_SEARCH_MATCH_START.equals(matchMode)) {
1✔
807
                                b.should(f.simpleQueryString().field("identifierStart").matching(query).boost(2f));
1✔
808
                        }
809
                        else  {
810
                                b.should(f.simpleQueryString().field("identifierAnywhere").matching(query));
1✔
811
                        }
812
                }).toPredicate();
1✔
813
        
814
        }
815

816
        private SearchPredicate newPatientIdentifierSearchPredicate(SearchPredicateFactory predicateFactory, String query, boolean includeVoided, boolean matchExactly) {
817
                return predicateFactory.bool().with(b -> {
1✔
818
                        b.must(getPatientIdentifierSearchPredicate(predicateFactory, query, matchExactly));
1✔
819

820
                        if (!includeVoided) {
1✔
821
                                b.filter(predicateFactory.match().field("voided").matching(false));
1✔
822
                                b.filter(predicateFactory.match().field("patient.voided").matching(false));
1✔
823
                        }
824
                        b.filter(predicateFactory.match().field("patient.isPatient").matching(true));
1✔
825
                }).toPredicate();
1✔
826
        }
827

828
        private String removeIdentifierPadding(String query) {
829
                String regex = Context.getAdministrationService().getGlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_PATIENT_IDENTIFIER_REGEX, "");
1✔
830
                if (Pattern.matches("^\\^.{1}\\*.*$", regex)) {
1✔
831
                        String padding = regex.substring(regex.indexOf("^") + 1, regex.indexOf("*"));
1✔
832
                        Pattern pattern = Pattern.compile("^" + padding + "+");
1✔
833
                        query = pattern.matcher(query).replaceFirst("");
1✔
834
                }
835
                return query;
1✔
836
        }
837

838
        /**
839
         * Copied over from PatientSearchCriteria...
840
         *
841
         * I have no idea how it is supposed to work, but tests pass...
842
         *
843
         * @param query
844
         * @return
845
         * @see PatientSearchCriteria
846
         */
847
        private List<String> tokenizeIdentifierQuery(String query) {
848
                List<String> searchPatterns = new ArrayList<>();
1✔
849

850
                String patternSearch = Context.getAdministrationService().getGlobalProperty(
1✔
851
                                OpenmrsConstants.GLOBAL_PROPERTY_PATIENT_IDENTIFIER_SEARCH_PATTERN, "");
852

853
                if (StringUtils.isBlank(patternSearch)) {
1✔
854
                        searchPatterns.add(query);
1✔
855
                } else {
856
                        // split the pattern before replacing in case the user searched on a comma
857
                        // replace the @SEARCH@, etc in all elements
858
                        for (String pattern : patternSearch.split(",")) {
1✔
859
                                searchPatterns.add(replaceSearchString(pattern, query));
1✔
860
                        }
861
                }
862
                return searchPatterns;
1✔
863
        }
864

865
        /**
866
         * Copied over from PatientSearchCriteria...
867
         *
868
         * I have no idea how it is supposed to work, but tests pass...
869
         *
870
         * Puts @SEARCH@, @SEARCH-1@, and @CHECKDIGIT@ into the search string
871
         *
872
         * @param regex the admin-defined search string containing the @..@'s to be replaced
873
         * @param identifierSearched the user entered search string
874
         * @return substituted search strings.
875
         *
876
         * @see PatientSearchCriteria#replaceSearchString(String, String)
877
         */
878
        private String replaceSearchString(String regex, String identifierSearched) {
879
                String returnString = regex.replaceAll("@SEARCH@", identifierSearched);
1✔
880
                if (identifierSearched.length() > 1) {
1✔
881
                        // for 2 or more character searches, we allow regex to use last character as check digit
882
                        returnString = returnString.replaceAll("@SEARCH-1@", identifierSearched.substring(0,
1✔
883
                                        identifierSearched.length() - 1));
1✔
884
                        returnString = returnString.replaceAll("@CHECKDIGIT@", identifierSearched
1✔
885
                                        .substring(identifierSearched.length() - 1));
1✔
886
                } else {
887
                        returnString = returnString.replaceAll("@SEARCH-1@", "");
×
888
                        returnString = returnString.replaceAll("@CHECKDIGIT@", "");
×
889
                }
890
                return returnString;
1✔
891
        }
892

893
    /**
894
         * @see org.openmrs..api.db.PatientDAO#getAllergies(org.openmrs.Patient)
895
         */
896
        //@Override
897
        @Override
898
        public List<Allergy> getAllergies(Patient patient) {
899
                Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Allergy.class);
1✔
900
                criteria.add(Restrictions.eq("patient", patient));
1✔
901
                criteria.add(Restrictions.eq("voided", false));
1✔
902
                return criteria.list();
1✔
903
        }
904
        
905
        /**
906
         * @see org.openmrs.api.db.PatientDAO#getAllergyStatus(org.openmrs.Patient)
907
         */
908
        //@Override
909
        @Override
910
        public String getAllergyStatus(Patient patient) {
911

912
                return (String) sessionFactory.getCurrentSession().createSQLQuery(
1✔
913
                            "select allergy_status from patient where patient_id = :patientId").setInteger("patientId", patient.getPatientId()).uniqueResult();
1✔
914
        }
915
        
916
        /**
917
         * @see org.openmrs.api.db.PatientDAO#saveAllergies(org.openmrs.Patient,
918
         *      org.openmrsallergyapi.Allergies)
919
         */
920
        @Override
921
        public Allergies saveAllergies(Patient patient, Allergies allergies) {
922

923
                sessionFactory.getCurrentSession().createSQLQuery(
1✔
924
                            "update patient set allergy_status = :allergyStatus where patient_id = :patientId")
925
                            .setInteger("patientId", patient.getPatientId())
1✔
926
                            .setString("allergyStatus", allergies.getAllergyStatus())
1✔
927
                            .executeUpdate();
1✔
928
                
929
                for (Allergy allergy : allergies) {
1✔
930
                        sessionFactory.getCurrentSession().save(allergy);
1✔
931
                }
1✔
932
                        
933
                return allergies;
1✔
934
        }
935
        
936
        /**
937
         * @see org.openmrs.PatientDAO#getAllergy(Integer)
938
         */
939
        @Override
940
        public Allergy getAllergy(Integer allergyId) {
941
                return (Allergy) sessionFactory.getCurrentSession().createQuery("from Allergy a where a.allergyId = :allergyId")
×
942
                                .setInteger("allergyId", allergyId).uniqueResult();
×
943
        }
944
        
945
        /**
946
         * @see org.openmrs.PatientDAO#getAllergyByUuid(String)
947
         */
948
        @Override
949
        public Allergy getAllergyByUuid(String uuid) {
950
                return (Allergy) sessionFactory.getCurrentSession().createQuery("from Allergy a where a.uuid = :uuid")
1✔
951
                                .setString("uuid", uuid).uniqueResult();
1✔
952
        }
953

954
        /**
955
     * @see org.openmrs.api.db.PatientDAO#saveAllergy(org.openmrs.Allergy)
956
     */
957
    @Override
958
    public Allergy saveAllergy(Allergy allergy) {
959
            sessionFactory.getCurrentSession().save(allergy);
1✔
960
            return allergy;
1✔
961
    }
962

963

964
    /**
965
     * @see org.openmrs.api.db.PatientDAO#getPatientIdentifierByProgram(org.openmrs.PatientProgram)
966
     */
967
    public List<PatientIdentifier> getPatientIdentifierByProgram(PatientProgram patientProgram) {
968

969
        Criteria criteria = sessionFactory.getCurrentSession().createCriteria(PatientIdentifier.class);
1✔
970
        criteria.add(Restrictions.eq("patientProgram", patientProgram));
1✔
971
        return criteria.list();
1✔
972
    }
973
}
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