From 304a8038b206dc45f4286d20ec3cb1d9011ab5f6 Mon Sep 17 00:00:00 2001 From: Rahul Pamnani Date: Sun, 14 Jul 2024 15:11:24 +0530 Subject: [PATCH 1/3] Implement Lucene queries in LuceneSearchResultsDAOImpl --- .../daoimpl/LuceneSearchResultsDAOImpl.java | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) diff --git a/src/main/java/org/openelisglobal/sample/daoimpl/LuceneSearchResultsDAOImpl.java b/src/main/java/org/openelisglobal/sample/daoimpl/LuceneSearchResultsDAOImpl.java index 3471a2b5ae..34db90384d 100644 --- a/src/main/java/org/openelisglobal/sample/daoimpl/LuceneSearchResultsDAOImpl.java +++ b/src/main/java/org/openelisglobal/sample/daoimpl/LuceneSearchResultsDAOImpl.java @@ -1,23 +1,103 @@ package org.openelisglobal.sample.daoimpl; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import javax.transaction.Transactional; +import org.apache.commons.validator.GenericValidator; +import org.hibernate.Session; +import org.hibernate.query.Query; +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.session.SearchSession; import org.openelisglobal.common.exception.LIMSRuntimeException; import org.openelisglobal.common.provider.query.PatientSearchResults; +import org.openelisglobal.patient.valueholder.Patient; +import org.openelisglobal.patientidentitytype.util.PatientIdentityTypeMap; import org.openelisglobal.sample.dao.SearchResultsDAO; import org.springframework.stereotype.Component; @Component public class LuceneSearchResultsDAOImpl implements SearchResultsDAO { + @PersistenceContext + EntityManager entityManager; + @Override @Transactional public List getSearchResults(String lastName, String firstName, String STNumber, String subjectNumber, String nationalID, String externalID, String patientID, String guid, String dateOfBirth, String gender) throws LIMSRuntimeException { + SearchSession searchSession = Search.session(entityManager); + + List hits = searchSession.search(Patient.class).select(f -> f.id(String.class)).where(f -> f.bool(b -> { + if (!GenericValidator.isBlankOrNull(patientID)) { + b.must(f.match().field("id").matching(patientID)); + } + if (!GenericValidator.isBlankOrNull(gender)) { + b.must(f.match().field("gender").matching(gender)); + } + if (!GenericValidator.isBlankOrNull(dateOfBirth)) { + b.must(f.match().field("birthDateForDisplay").matching(dateOfBirth)); + } + if (!GenericValidator.isBlankOrNull(firstName) && !GenericValidator.isBlankOrNull(lastName)) { + b.must(f.nested().objectField("person") + .nest(f.bool().must(f.match().field("person.firstName").matching(firstName).fuzzy()) + .must(f.match().field("person.lastName").matching(lastName).fuzzy()))); + } else { + if (!GenericValidator.isBlankOrNull(firstName)) { + b.must(f.match().field("person.firstName").matching(firstName).fuzzy()); + } + if (!GenericValidator.isBlankOrNull(lastName)) { + b.must(f.match().field("person.lastName").matching(lastName).fuzzy()); + } + } + })).fetchAllHits(); + + List longHits = hits.stream().map(Long::parseLong).collect(Collectors.toList()); + // 'IN' predicate requires the list to contain at least one value + longHits.add(-1L); + + String sqlString = buildQueryString(nationalID, externalID, STNumber, subjectNumber, guid); + Query query = entityManager.unwrap(Session.class).createNativeQuery(sqlString); + query.setParameter(ID_TYPE_FOR_ST, Integer.valueOf(PatientIdentityTypeMap.getInstance().getIDForType("ST"))); + query.setParameter(ID_TYPE_FOR_SUBJECT_NUMBER, + Integer.valueOf(PatientIdentityTypeMap.getInstance().getIDForType("SUBJECT"))); + query.setParameter(ID_TYPE_FOR_GUID, + Integer.valueOf(PatientIdentityTypeMap.getInstance().getIDForType("GUID"))); + + if (!GenericValidator.isBlankOrNull(nationalID)) { + query.setParameter(NATIONAL_ID_PARAM, nationalID); + } + if (!GenericValidator.isBlankOrNull(externalID)) { + query.setParameter(EXTERNAL_ID_PARAM, nationalID); + } + if (!GenericValidator.isBlankOrNull(STNumber)) { + query.setParameter(ST_NUMBER_PARAM, STNumber); + } + if (!GenericValidator.isBlankOrNull(subjectNumber)) { + query.setParameter(SUBJECT_NUMBER_PARAM, subjectNumber); + } + if (!GenericValidator.isBlankOrNull(guid)) { + query.setParameter(GUID, guid); + } + query.setParameter("idList", longHits); + + List queryResults = query.list(); + List patientSearchResultsList = new ArrayList<>(); + + for (Object[] tuple : queryResults) { + + PatientSearchResults patientSearchResults = new PatientSearchResults((BigDecimal) tuple[0], + (String) tuple[1], (String) tuple[2], (String) tuple[3], (String) tuple[4], (String) tuple[5], + (String) tuple[6], (String) tuple[7], (String) tuple[8], (String) tuple[9], null); + patientSearchResultsList.add(patientSearchResults); + } + return patientSearchResultsList; } @@ -37,8 +117,135 @@ public List getSearchResultsExact(String lastName, String String subjectNumber, String nationalID, String externalID, String patientID, String guid, String dateOfBirth, String gender) throws LIMSRuntimeException { + SearchSession searchSession = Search.session(entityManager); + + List hits = searchSession.search(Patient.class).select(f -> f.id(String.class)).where(f -> f.bool(b -> { + if (!GenericValidator.isBlankOrNull(patientID)) { + b.must(f.match().field("id").matching(patientID)); + } + if (!GenericValidator.isBlankOrNull(gender)) { + b.must(f.match().field("gender").matching(gender)); + } + if (!GenericValidator.isBlankOrNull(dateOfBirth)) { + b.must(f.match().field("birthDateForDisplay").matching(dateOfBirth)); + } + if (!GenericValidator.isBlankOrNull(firstName) && !GenericValidator.isBlankOrNull(lastName)) { + b.must(f.nested().objectField("person") + .nest(f.bool().must(f.match().field("person.firstName").matching(firstName)) + .must(f.match().field("person.lastName").matching(lastName)))); + } else { + if (!GenericValidator.isBlankOrNull(firstName)) { + b.must(f.match().field("person.firstName").matching(firstName)); + } + if (!GenericValidator.isBlankOrNull(lastName)) { + b.must(f.match().field("person.lastName").matching(lastName)); + } + } + })).fetchAllHits(); + + List longHits = hits.stream().map(Long::parseLong).collect(Collectors.toList()); + // 'IN' predicate requires the list to contain at least one value + longHits.add(-1L); + + String sqlString = buildQueryString(nationalID, externalID, STNumber, subjectNumber, guid); + Query query = entityManager.unwrap(Session.class).createNativeQuery(sqlString); + query.setParameter(ID_TYPE_FOR_ST, Integer.valueOf(PatientIdentityTypeMap.getInstance().getIDForType("ST"))); + query.setParameter(ID_TYPE_FOR_SUBJECT_NUMBER, + Integer.valueOf(PatientIdentityTypeMap.getInstance().getIDForType("SUBJECT"))); + query.setParameter(ID_TYPE_FOR_GUID, + Integer.valueOf(PatientIdentityTypeMap.getInstance().getIDForType("GUID"))); + + if (!GenericValidator.isBlankOrNull(nationalID)) { + query.setParameter(NATIONAL_ID_PARAM, nationalID); + } + if (!GenericValidator.isBlankOrNull(externalID)) { + query.setParameter(EXTERNAL_ID_PARAM, nationalID); + } + if (!GenericValidator.isBlankOrNull(STNumber)) { + query.setParameter(ST_NUMBER_PARAM, STNumber); + } + if (!GenericValidator.isBlankOrNull(subjectNumber)) { + query.setParameter(SUBJECT_NUMBER_PARAM, subjectNumber); + } + if (!GenericValidator.isBlankOrNull(guid)) { + query.setParameter(GUID, guid); + } + query.setParameter("idList", longHits); + + List queryResults = query.list(); + List patientSearchResultsList = new ArrayList<>(); + + for (Object[] tuple : queryResults) { + + PatientSearchResults patientSearchResults = new PatientSearchResults((BigDecimal) tuple[0], + (String) tuple[1], (String) tuple[2], (String) tuple[3], (String) tuple[4], (String) tuple[5], + (String) tuple[6], (String) tuple[7], (String) tuple[8], (String) tuple[9], null); + patientSearchResultsList.add(patientSearchResults); + } + return patientSearchResultsList; } + private String buildQueryString(String nationalID, String externalID, String STNumber, String subjectNumber, + String guid) { + + StringBuilder queryBuilder = new StringBuilder(); + queryBuilder.append("select p.id, pr.first_name, pr.last_name, p.gender, p.entered_birth_date, p.national_id," + + " p.external_id, pi.identity_data as st, piSN.identity_data as subject," + + " piGUID.identity_data as guid "); + queryBuilder.append("from patient p join person pr on p.person_id = pr.id "); + queryBuilder.append("left join patient_identity pi on pi.patient_id = p.id and pi.identity_type_id = :"); + queryBuilder.append(ID_TYPE_FOR_ST).append(" "); + queryBuilder.append("left join patient_identity piSN on piSN.patient_id = p.id and piSN.identity_type_id = :"); + queryBuilder.append(ID_TYPE_FOR_SUBJECT_NUMBER).append(" "); + queryBuilder.append( + "left join patient_identity piGUID on piGUID.patient_id = p.id and piGUID.identity_type_id" + " = :"); + queryBuilder.append(ID_TYPE_FOR_GUID).append(" "); + queryBuilder.append("where "); + + queryBuilder.append("( false or "); + if (!GenericValidator.isBlankOrNull(subjectNumber)) { + queryBuilder.append("piSN.identity_data ilike :"); + queryBuilder.append(SUBJECT_NUMBER_PARAM).append(" or "); + } + + if (!GenericValidator.isBlankOrNull(nationalID)) { + queryBuilder.append("p.national_id ilike :"); + queryBuilder.append(NATIONAL_ID_PARAM).append(" or "); + } + + if (!GenericValidator.isBlankOrNull(externalID)) { + queryBuilder.append("p.external_id ilike :"); + queryBuilder.append(EXTERNAL_ID_PARAM).append(" or "); + } + + if (!GenericValidator.isBlankOrNull(STNumber)) { + queryBuilder.append("pi.identity_data ilike :"); + queryBuilder.append(ST_NUMBER_PARAM).append(" and "); + } + + // Need to close paren before dangling AND/OR. + int lastAndIndex = queryBuilder.lastIndexOf("and"); + int lastOrIndex = queryBuilder.lastIndexOf("or"); + + if (lastAndIndex > lastOrIndex) { + queryBuilder.delete(lastAndIndex, queryBuilder.length()); + queryBuilder.append(") and "); + } else if (lastOrIndex > lastAndIndex) { + queryBuilder.delete(lastOrIndex, queryBuilder.length()); + queryBuilder.append(") or "); + } + + if (!GenericValidator.isBlankOrNull(guid)) { + queryBuilder.append("piGUID.identity_data = :"); + queryBuilder.append(GUID).append(" and "); + } + + // idList contains patient IDs from Lucene search results matching the fields + // id, person.firstName, person.lastName, birthDateForDisplay and gender + queryBuilder.append("p.id in :idList"); + + return queryBuilder.toString(); + } } From 0ded21fe6e68b76d4b19c0468f3d7166c0f856e3 Mon Sep 17 00:00:00 2001 From: Rahul Pamnani Date: Sun, 14 Jul 2024 15:12:55 +0530 Subject: [PATCH 2/3] Remove redundant if-else check in DBSearchResultsDAOImpl --- .../daoimpl/DBSearchResultsDAOImpl.java | 68 ++++++------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/openelisglobal/sample/daoimpl/DBSearchResultsDAOImpl.java b/src/main/java/org/openelisglobal/sample/daoimpl/DBSearchResultsDAOImpl.java index 09f5af6390..433b4b9fef 100644 --- a/src/main/java/org/openelisglobal/sample/daoimpl/DBSearchResultsDAOImpl.java +++ b/src/main/java/org/openelisglobal/sample/daoimpl/DBSearchResultsDAOImpl.java @@ -321,57 +321,29 @@ private String buildQueryString(boolean lastName, boolean firstName, boolean STN queryBuilder.append(ID_TYPE_FOR_GUID); queryBuilder.append(" where "); - if (anyID) { - queryBuilder.append(" ( false or "); - if (subjectNumber) { - queryBuilder.append(" piSN.identity_data ilike :"); - queryBuilder.append(SUBJECT_NUMBER_PARAM); - queryBuilder.append(" or"); - } - - if (nationalID) { - queryBuilder.append(" p.national_id ilike :"); - queryBuilder.append(NATIONAL_ID_PARAM); - queryBuilder.append(" or"); - } - - if (externalID) { - queryBuilder.append(" p.external_id ilike :"); - queryBuilder.append(EXTERNAL_ID_PARAM); - queryBuilder.append(" or"); - } - - if (STNumber) { - queryBuilder.append(" pi.identity_data ilike :"); - queryBuilder.append(ST_NUMBER_PARAM); - queryBuilder.append(" and"); - } - - } else { - queryBuilder.append(" ( false or "); - if (subjectNumber) { - queryBuilder.append(" piSN.identity_data ilike :"); - queryBuilder.append(SUBJECT_NUMBER_PARAM); - queryBuilder.append(" or"); - } + queryBuilder.append(" ( false or "); + if (subjectNumber) { + queryBuilder.append(" piSN.identity_data ilike :"); + queryBuilder.append(SUBJECT_NUMBER_PARAM); + queryBuilder.append(" or"); + } - if (nationalID) { - queryBuilder.append(" p.national_id ilike :"); - queryBuilder.append(NATIONAL_ID_PARAM); - queryBuilder.append(" or"); - } + if (nationalID) { + queryBuilder.append(" p.national_id ilike :"); + queryBuilder.append(NATIONAL_ID_PARAM); + queryBuilder.append(" or"); + } - if (externalID) { - queryBuilder.append(" p.external_id ilike :"); - queryBuilder.append(EXTERNAL_ID_PARAM); - queryBuilder.append(" or"); - } + if (externalID) { + queryBuilder.append(" p.external_id ilike :"); + queryBuilder.append(EXTERNAL_ID_PARAM); + queryBuilder.append(" or"); + } - if (STNumber) { - queryBuilder.append(" pi.identity_data ilike :"); - queryBuilder.append(ST_NUMBER_PARAM); - queryBuilder.append(" and"); - } + if (STNumber) { + queryBuilder.append(" pi.identity_data ilike :"); + queryBuilder.append(ST_NUMBER_PARAM); + queryBuilder.append(" and"); } // Need to close paren before dangling AND/OR. From 05ec991aab8771c564905db7f20205af4c15fed6 Mon Sep 17 00:00:00 2001 From: Rahul Pamnani Date: Sun, 14 Jul 2024 15:14:12 +0530 Subject: [PATCH 3/3] Modify placeholder tests in SearchResultsServiceTest --- .../search/SearchResultsServiceTest.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/openelisglobal/search/SearchResultsServiceTest.java b/src/test/java/org/openelisglobal/search/SearchResultsServiceTest.java index 524ecdf31e..f8b4e9e6e9 100644 --- a/src/test/java/org/openelisglobal/search/SearchResultsServiceTest.java +++ b/src/test/java/org/openelisglobal/search/SearchResultsServiceTest.java @@ -99,12 +99,18 @@ public void getSearchResults_shouldGetSearchResultsFromLuceneIndexes() throws Ex Patient pat = createPatient(firstName, lastname, dob, gender); String patientId = patientService.insert(pat); - List searchResults = luceneSearchResultsServiceImpl.getSearchResults(lastname, firstName, - null, null, null, null, null, null, dob, gender); + String searchFirstName = "Johm"; + String searchLastName = "Doee"; + + List searchResults = luceneSearchResultsServiceImpl.getSearchResults(searchLastName, + searchFirstName, null, null, null, null, null, null, dob, gender); - // The search results are currently expected to be empty because the method has - // not been implemented yet - Assert.assertEquals(0, searchResults.size()); + Assert.assertEquals(1, searchResults.size()); + PatientSearchResults result = searchResults.get(0); + Assert.assertEquals(patientId, result.getPatientID()); + Assert.assertEquals(firstName, result.getFirstName()); + Assert.assertEquals(lastname, result.getLastName()); + Assert.assertEquals(dob, result.getBirthdate()); } @Test @@ -119,9 +125,12 @@ public void getSearchResultsExact_shouldGetExactSearchResultsFromLuceneIndexes() List searchResults = luceneSearchResultsServiceImpl.getSearchResultsExact(lastname, firstName, null, null, null, null, null, null, dob, gender); - // The search results are currently expected to be empty because the method has - // not been implemented yet - Assert.assertEquals(0, searchResults.size()); + Assert.assertEquals(1, searchResults.size()); + PatientSearchResults result = searchResults.get(0); + Assert.assertEquals(patientId, result.getPatientID()); + Assert.assertEquals(firstName, result.getFirstName()); + Assert.assertEquals(lastname, result.getLastName()); + Assert.assertEquals(dob, result.getBirthdate()); } private Patient createPatient(String firstName, String LastName, String birthDate, String gender)