From 58a4ed817f1abf66f01d355534eddf7843bcb4ea Mon Sep 17 00:00:00 2001 From: Rishi Date: Sat, 1 Jun 2019 20:58:13 +0530 Subject: [PATCH] GSoC 2019: Patient search criteria added in openmrs core --- api/src/main/java/org/openmrs/Patient.java | 2 + api/src/main/java/org/openmrs/Person.java | 10 +- .../java/org/openmrs/api/PatientService.java | 44 +++ .../java/org/openmrs/api/db/PatientDAO.java | 96 +++++++ .../api/db/hibernate/HibernatePatientDAO.java | 105 +++++++ .../api/db/hibernate/PatientLuceneQuery.java | 144 ++++++++++ .../db/hibernate/search/DateLuceneQuery.java | 75 +++++ .../openmrs/api/impl/PatientServiceImpl.java | 72 +++++ .../org/openmrs/util/OpenmrsConstants.java | 4 +- .../org/openmrs/module/dtd/config-1.5.dtd | 2 +- .../api/PatientSearchCriteriaServiceTest.java | 126 +++++++++ .../api/db/PatientSearchCriteriaDAOTest.java | 263 ++++++++++++++++++ .../test/BaseContextSensitiveTest.java | 3 +- .../PatientSearchCriteriaDAOTest-patients.xml | 37 +++ ...ientSearchCriteriaServiceTest-patients.xml | 38 +++ 15 files changed, 1016 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/org/openmrs/api/db/hibernate/PatientLuceneQuery.java create mode 100644 api/src/main/java/org/openmrs/api/db/hibernate/search/DateLuceneQuery.java create mode 100644 api/src/test/java/org/openmrs/api/PatientSearchCriteriaServiceTest.java create mode 100644 api/src/test/java/org/openmrs/api/db/PatientSearchCriteriaDAOTest.java create mode 100644 api/src/test/resources/org/openmrs/api/db/hibernate/include/PatientSearchCriteriaDAOTest-patients.xml create mode 100644 api/src/test/resources/org/openmrs/api/include/PatientSearchCriteriaServiceTest-patients.xml diff --git a/api/src/main/java/org/openmrs/Patient.java b/api/src/main/java/org/openmrs/Patient.java index a21b2028b471..6b8584075ccc 100644 --- a/api/src/main/java/org/openmrs/Patient.java +++ b/api/src/main/java/org/openmrs/Patient.java @@ -31,6 +31,7 @@ import org.hibernate.annotations.SortNatural; import org.hibernate.search.annotations.ContainedIn; import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.Indexed; /** * Defines a Patient in the system. A patient is simply an extension of a person and all that that @@ -41,6 +42,7 @@ @Entity @Table(name = "patient") @PrimaryKeyJoinColumn(name = "patient_id") +@Indexed public class Patient extends Person { public static final long serialVersionUID = 93123L; diff --git a/api/src/main/java/org/openmrs/Person.java b/api/src/main/java/org/openmrs/Person.java index e7270ac8ec39..617ae87ad64c 100644 --- a/api/src/main/java/org/openmrs/Person.java +++ b/api/src/main/java/org/openmrs/Person.java @@ -46,9 +46,14 @@ import org.hibernate.annotations.LazyCollection; import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.SortNatural; +import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.ContainedIn; +import org.hibernate.search.annotations.DateBridge; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; +import org.hibernate.search.annotations.Index; +import org.hibernate.search.annotations.Indexed; +import org.hibernate.search.annotations.Resolution; import org.openmrs.util.OpenmrsUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,6 +70,7 @@ @Entity @Table(name = "person") @Inheritance(strategy = InheritanceType.JOINED) +@Indexed public class Person extends BaseChangeableOpenmrsData { public static final long serialVersionUID = 2L; @@ -102,10 +108,12 @@ public class Person extends BaseChangeableOpenmrsData { @ContainedIn private Set attributes = null; - @Field + @Field(name="gender",index=Index.YES, analyze=Analyze.YES) @Column(length = 50) private String gender; + @Field(name="birthdate",index=Index.YES, analyze=Analyze.YES) + @DateBridge(resolution = Resolution.MILLISECOND) @Column(name = "birthdate", length = 10) private Date birthdate; diff --git a/api/src/main/java/org/openmrs/api/PatientService.java b/api/src/main/java/org/openmrs/api/PatientService.java index 3cb5c1737ac4..9ff739eb1bd2 100644 --- a/api/src/main/java/org/openmrs/api/PatientService.java +++ b/api/src/main/java/org/openmrs/api/PatientService.java @@ -176,6 +176,50 @@ public interface PatientService extends OpenmrsService { public List getPatients(String name, String identifier, List identifierTypes, boolean matchIdentifierExactly) throws APIException; + /** + * Return the list of patient which has required name or identifier or gender or birthdate or + * age. + * + * @param name (optional) this is a slight break from the norm, patients with a partial match on + * this name will be returned + * @param identifier (optional) only patients with a matching identifier are returned. This + * however applies only if name argument is null. Otherwise, its + * ignored. + * @param identifierTypes (optional) the PatientIdentifierTypes to restrict to + * @param matchIdentifierExactly (required) if true, then the given identifier must + * equal the id in the database. if false, then the identifier is 'searched' for by + * using a regular expression + * @param gender(optional) : if user want to search patient by gender or filter the search + * result by gender. value of gender parameter is either "M" or "F". + * @param to(optional) : user wants to search patients having age between some range, at that + * time this paramater represent the upper boundary of range. + * @param from(optional):user wants to search patients having age between some range, at that + * time this paramater represent the lower boundary of range. + * @param birthdate(optional) : User can search patient by birthdate. + * @return patients that matched the given criteria (and are not voided) + * @throws APIException + * @should fetch all patients that partially match given name + * @should fetch all patients that partially match given identifier if name + * argument is null + * @should fetch all patients that partially match given identifier when match identifier + * exactly equals false and if name argument is null + * @should fetch all patients that exactly match given identifier when match identifier exactly + * equals true and if name argument is null + * @should fetch all patients that match given identifier types + * @should not return duplicates + * @should not return voided patients + * @should return empty list when no match is found + * @should search familyName2 with name + * @should support simple regex + * @should support pattern using last digit as check digit + * @should return empty list if name and identifier is empty + * @should support all the search criteria + */ + @Authorized({ PrivilegeConstants.GET_PATIENTS }) + public List getPatients(String name, String identifier, List identifierTypes, + boolean matchIdentifierExactly, String gender, Integer from, Integer to, Date birthdate) throws APIException; + + /** * Void patient record (functionally delete patient from system). Voids Person and retires * Users. diff --git a/api/src/main/java/org/openmrs/api/db/PatientDAO.java b/api/src/main/java/org/openmrs/api/db/PatientDAO.java index e2e13b4a4618..b0c84e0096e7 100644 --- a/api/src/main/java/org/openmrs/api/db/PatientDAO.java +++ b/api/src/main/java/org/openmrs/api/db/PatientDAO.java @@ -9,6 +9,7 @@ */ package org.openmrs.api.db; +import java.util.Date; import java.util.List; import org.openmrs.Allergies; @@ -297,4 +298,99 @@ public List getPatientIdentifierTypes(String name, String */ public Allergy saveAllergy(Allergy allergy); + + //Patient Search Criteria: + /** + * @param query : name or identifier of patients + * @param gender : gender of patients + * @param length : maximum number of patients should return + * @return : list of patients who follow the query regex and having required gender + * @throws DAOException + */ + public List getPatientsByNameOrIdAndGender(String query, String gender, Integer start, Integer length, Boolean includeVoided) + throws DAOException; + + /** + * @return : list of patient having required gender + * @throws DAOException + */ + public List getPatientsByGender(String gender, Integer start, Integer length, Boolean includeVoided) + throws DAOException; + + /** + * @param from: lower boundary of range of age + * @param to : upper boundary of range of age + * @return list of patients, who's age is in between the required range + * @throws DAOException + */ + public List getPatientsByRangeOfAge(Date from, Date to, Integer start, Integer length, Boolean includeVoided) + throws DAOException; + + /** + * @param birthdate : birthdate of patient + * @return list of patient/s , who's birthdate is similar to required birthdate + * @throws DAOException + */ + public List getPatientsByBirthdate(Date birthdate, Integer start, Integer length, Boolean includeVoided) + throws DAOException; + + /** + * @param query : name or identifier of patient/s. + * @param gender : gender of patient + * @param from : lower boundary of range of age + * @param to : upper boundary of range of age + * @return list of patients who follow the query regex and having required gender and having age + * in required range + * @throws DAOException + */ + public List getPatientsByNameOrIdAndGenderAndRangeOfAge(String query, String gender, Date from, Date to, Integer start, Integer length, + Boolean includeVoided) throws DAOException; + + /** + * @param query : name or identifier of patient/s + * @param gender : gender of patient + * @param birthdate : birthdate of patient + * @return list of patients who follow the query regex and having required gender and having + * required birthdate + * @throws DAOException + */ + public List getPatientsByNameOrIdAndGenderAndBirthdate(String query, String gender, Date birthdate, Integer start, Integer length, + Boolean includeVoided) throws DAOException; + + /** + * @param from: lower boundary of range of age + * @param to : upper boundary of range of age + * @return list of patients who follow the query regex and having age in required range + * @throws DAOException + */ + public List getPatientsByNameOrIdAndRangeOfAge(String query, Date from, Date to, Integer start, Integer length, Boolean includeVoided) + throws DAOException; + + /** + * @param birthdate: birthdate of patient + * @return list of patients who follow the query regex and having birthdate as required. + * @throws DAOException + */ + public List getPatientsByNameOrIdAndBirthdate(String query, Date birthdate, Integer start, Integer length, Boolean includeVoided) + throws DAOException; + + /** + * @param gender : gender of patients + * @param birthdate: birthdate of patient + * @return list of patients which have required birthdate and gender + * @throws DAOException + */ + public List getPatientsByGenderAndBirthdate(String gender, Date birthdate, Integer start, Integer length, + Boolean includeVoided) throws DAOException; + + /** + * @param gender : gender of patients + * @param from: lower boundary of range of age + * @param to : upper boundary of range of age + * @return list of patients which have required gender and age in required range. + * @throws DAOException + */ + public List getPatientsByGenderAndAge(String gender, Date from, Date to, Integer start, Integer length, + Boolean includeVoided) throws DAOException; + } diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java b/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java index ab5087b10dbb..9979b3d7d57e 100644 --- a/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java +++ b/api/src/main/java/org/openmrs/api/db/hibernate/HibernatePatientDAO.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -813,6 +814,110 @@ public List findPatients(String query, boolean includeVoided, Integer s return patients; } + + //Patient search criteria functions: + + @Override + public List getPatientsByGender(String gender, Integer start, Integer length, Boolean includeVoided) + throws DAOException { + + PatientLuceneQuery patientLuceneQuery = new PatientLuceneQuery(sessionFactory); + LuceneQuery genderQuery = patientLuceneQuery.getPatinetWithGender(gender, includeVoided); + return getPatientsWithLuceneQuery(genderQuery, start, length); + } + + @Override + public List getPatientsByRangeOfAge(Date from, Date to, Integer start, Integer length, Boolean includeVoided) + throws DAOException { + + PatientLuceneQuery personLuceneQuery = new PatientLuceneQuery(sessionFactory); + LuceneQuery ageQuery = personLuceneQuery.getPatinetWithAgeRange(from, to, includeVoided); + return getPatientsWithLuceneQuery(ageQuery, start, length); + } + + @Override + public List getPatientsByBirthdate(Date birthdate, Integer start, Integer length, Boolean includeVoided) + throws DAOException { + + PatientLuceneQuery personLuceneQuery = new PatientLuceneQuery(sessionFactory); + LuceneQuery birthdateQuery = personLuceneQuery.getPatinetWithBirthdate(birthdate, includeVoided); + return getPatientsWithLuceneQuery(birthdateQuery, start, length); + } + + @Override + public List getPatientsByNameOrIdAndGender(String query, String gender, Integer start, Integer length, Boolean includeVoided) + throws DAOException { + return findPatients(query, includeVoided, start, length).stream().filter(patient -> patient.getGender().equals(gender)).collect(Collectors.toList()); + } + + @Override + public List getPatientsByNameOrIdAndRangeOfAge(String query, Date from, Date to, Integer start, Integer length, + Boolean includeVoided) throws DAOException { + + PatientLuceneQuery patientLuceneQuery=new PatientLuceneQuery(sessionFactory); + LuceneQuery ageQuery=patientLuceneQuery.getPatinetWithAgeRange(from, to, includeVoided); + List temp=getPatientsWithLuceneQuery(ageQuery,start,length); + return findPatients(query, includeVoided, start, length).stream().filter(patient -> temp.contains(patient)).collect(Collectors.toList()); + } + + @Override + public List getPatientsByNameOrIdAndBirthdate(String query,Date birthdate, Integer start, Integer length, + Boolean includeVoided) throws DAOException { + + PatientLuceneQuery patientLuceneQuery=new PatientLuceneQuery(sessionFactory); + LuceneQuery birthdateQuery=patientLuceneQuery.getPatinetWithBirthdate(birthdate, includeVoided); + List temp=getPatientsWithLuceneQuery(birthdateQuery,start,length); + return findPatients(query, includeVoided, start, length).stream().filter(patient -> temp.contains(patient)).collect(Collectors.toList()); + } + + @Override + public List getPatientsByNameOrIdAndGenderAndRangeOfAge(String query, String gender, Date from, Date to, Integer start, Integer length, + Boolean includeVoided) throws DAOException { + return getPatientsByNameOrIdAndRangeOfAge(query, from, to, start, length, includeVoided).stream().filter(patient -> patient.getGender().equals(gender)).collect(Collectors.toList()); + } + + @Override + public List getPatientsByNameOrIdAndGenderAndBirthdate(String query, String gender, Date birthdate, Integer start, Integer length, + Boolean includeVoided) throws DAOException { + return getPatientsByNameOrIdAndBirthdate(query, birthdate, start, length, includeVoided).stream().filter(patient -> patient.getGender().equals(gender)).collect(Collectors.toList()); + } + + @Override + public List getPatientsByGenderAndBirthdate(String gender, Date birthdate, Integer start, Integer length, + Boolean includeVoided) throws DAOException { + return getPatientsByBirthdate(birthdate, start, length, includeVoided).stream().filter(patient -> patient.getGender().equals(gender)).collect(Collectors.toList()); + } + + @Override + public List getPatientsByGenderAndAge(String gender, Date from, Date to, Integer start, Integer length, + Boolean includeVoided) throws DAOException { + return getPatientsByRangeOfAge(from, to, start, length, includeVoided).stream().filter(patient -> patient.getGender().equals(gender)).collect(Collectors.toList()); + } + + private List getPatientsWithLuceneQuery(LuceneQuery query, Integer start, Integer length){ + Integer tmpStart = start; + if (tmpStart == null) { + tmpStart = 0; + } + Integer maxLength = HibernatePersonDAO.getMaximumSearchResults(); + Integer tmpLength = length; + if (tmpLength == null || tmpLength > maxLength) { + tmpLength = maxLength; + } + + List patients = new LinkedList<>(); + + long querySize = query.resultSize(); + if (querySize > tmpStart) { + ListPart tempPatients = query.listPartProjection(tmpStart, tmpLength, "personId"); + tempPatients.getList().forEach(patient -> patients.add(getPatient((Integer) patient[0]))); + } + + return patients; + + + } + private LuceneQuery getPatientIdentifierLuceneQuery(String query, List identifierTypes, boolean matchExactly) { LuceneQuery patientIdentifierLuceneQuery = getPatientIdentifierLuceneQuery(query, matchExactly); for(PatientIdentifierType identifierType : identifierTypes) { diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/PatientLuceneQuery.java b/api/src/main/java/org/openmrs/api/db/hibernate/PatientLuceneQuery.java new file mode 100644 index 000000000000..9cef3ee097d0 --- /dev/null +++ b/api/src/main/java/org/openmrs/api/db/hibernate/PatientLuceneQuery.java @@ -0,0 +1,144 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.api.db.hibernate; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.hibernate.SessionFactory; +import org.openmrs.Person; +import org.openmrs.api.db.hibernate.search.DateLuceneQuery; +import org.openmrs.api.db.hibernate.search.LuceneQuery; + +/* + + Provides the lucene query for the patient search criteria + + */ +public class PatientLuceneQuery { + + SessionFactory sessionFactory; + + public PatientLuceneQuery(SessionFactory sessionFactory) { + this.sessionFactory = sessionFactory; + } + + //returns the lucene query for searching patients with gender + public LuceneQuery getPatinetWithGender(String query) { + return findPatientWithGender(query, true, null); + } + + public LuceneQuery getPatinetWithGender(String query, boolean includeVoided) { + return findPatientWithGender(query, includeVoided, null); + } + + public LuceneQuery getPatinetWithGender(String query, LuceneQuery skipSame) { + return findPatientWithGender(query, true, skipSame); + } + + public LuceneQuery getPatinetWithGender(String query, boolean includeVoided, LuceneQuery skipSame) { + return findPatientWithGender(query, includeVoided, skipSame); + } + + private LuceneQuery findPatientWithGender(String query,boolean includeVoided,LuceneQuery skipSame){ + List fields=new ArrayList<>(); + fields.add("gender"); + + LuceneQuery luceneQuery = LuceneQuery + .newQuery(Person.class, sessionFactory.getCurrentSession(), query, fields); + if (!includeVoided) { + luceneQuery.include("voided", false); + } + + if (skipSame != null) { + luceneQuery.skipSame("personId", skipSame); + } else { + luceneQuery.skipSame("personId"); + } + luceneQuery.include("isPatient", true); + + return luceneQuery; + + } + + //returns the lucene query for searching patients with birth date + public LuceneQuery getPatinetWithBirthdate(Date query) { + return findPatientWithBirthdate(query, true, null); + } + + public LuceneQuery getPatinetWithBirthdate(Date query, boolean includeVoided) { + return findPatientWithBirthdate(query, includeVoided, null); + } + + public LuceneQuery getPatinetWithBirthdate(Date query, LuceneQuery skipSame) { + return findPatientWithBirthdate(query, true, skipSame); + } + + public LuceneQuery getPatinetWithBirthdate(Date query, boolean includeVoided, LuceneQuery skipSame) { + return findPatientWithBirthdate(query, includeVoided, skipSame); + } + + private LuceneQuery findPatientWithBirthdate(Date query, boolean includeVoided, LuceneQuery skipSame) { + String field = "birthdate"; + + DateLuceneQuery luceneQuery = DateLuceneQuery.newQuery(Person.class, sessionFactory.getCurrentSession(), + query, field); + if (!includeVoided) { + luceneQuery.include("voided", false); + } + + if (skipSame != null) { + luceneQuery.skipSame("personId", skipSame); + } else { + luceneQuery.skipSame("personId"); + } + luceneQuery.include("isPatient", true); + return luceneQuery; + + } + + //returns the lucene query for searching patients with range of age + public LuceneQuery getPatinetWithAgeRange(Date from, Date to) { + return findPatientWithAgeRange(from, to, true, null); + } + + public LuceneQuery getPatinetWithAgeRange(Date from, Date to, boolean includeVoided) { + return findPatientWithAgeRange(from, to, includeVoided, null); + } + + public LuceneQuery getPatinetWithAgeRange(Date from, Date to, LuceneQuery skipSame) { + return findPatientWithAgeRange(from, to, true, skipSame); + } + + public LuceneQuery getPatinetWithAgeRange(Date from, Date to, boolean includeVoided, LuceneQuery skipSame) { + return findPatientWithAgeRange(from, to, includeVoided, skipSame); + } + + private LuceneQuery findPatientWithAgeRange(Date from, Date to, boolean includeVoided, LuceneQuery skipSame) { + String field = "birthdate"; + + DateLuceneQuery luceneQuery = DateLuceneQuery.newQuery(Person.class, sessionFactory.getCurrentSession(), + from, to, field); + if (!includeVoided) { + luceneQuery.include("voided", false); + } + + if (skipSame != null) { + luceneQuery.skipSame("personId", skipSame); + } else { + luceneQuery.skipSame("personId"); + } + luceneQuery.include("isPatient", true); + return luceneQuery; + + } + +} diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/search/DateLuceneQuery.java b/api/src/main/java/org/openmrs/api/db/hibernate/search/DateLuceneQuery.java new file mode 100644 index 000000000000..6bf6b1bec658 --- /dev/null +++ b/api/src/main/java/org/openmrs/api/db/hibernate/search/DateLuceneQuery.java @@ -0,0 +1,75 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.api.db.hibernate.search; + +import java.util.Date; + +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.hibernate.Session; +import org.hibernate.search.query.dsl.QueryBuilder; +import org.openmrs.api.db.hibernate.search.LuceneQuery; + +/* + * Lucene query for date fomate + */ +public class DateLuceneQuery extends LuceneQuery { + + public static DateLuceneQuery newQuery(final Class type, final Session session, final Date query, + final String field) { + return new DateLuceneQuery( + type, session) { + + @Override + protected Query prepareQuery() { + if (query == null) { + return new MatchAllDocsQuery(); + } + + QueryBuilder queryBuilder = getFullTextSession().getSearchFactory().buildQueryBuilder().forEntity(type) + .get(); + return queryBuilder.range().onField(field).from(query.getTime()).to(query.getTime() + 86400000) + .createQuery(); + + } + }; + } + + public static DateLuceneQuery newQuery(final Class type, final Session session, final Date from, + final Date to, final String field) { + return new DateLuceneQuery( + type, session) { + + @Override + protected Query prepareQuery() { + if (from == null || to == null) { + return new MatchAllDocsQuery(); + } + + QueryBuilder queryBuilder = getFullTextSession().getSearchFactory().buildQueryBuilder().forEntity(type) + .get(); + return queryBuilder.range().onField(field).from(to.getTime()).to(from.getTime()).createQuery(); + + } + }; + } + + public DateLuceneQuery(Class type, Session session) { + super(type, session); + // TODO Auto-generated constructor stub + } + + @Override + protected Query prepareQuery() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/api/src/main/java/org/openmrs/api/impl/PatientServiceImpl.java b/api/src/main/java/org/openmrs/api/impl/PatientServiceImpl.java index bceaf1ecfed3..68bc34def226 100644 --- a/api/src/main/java/org/openmrs/api/impl/PatientServiceImpl.java +++ b/api/src/main/java/org/openmrs/api/impl/PatientServiceImpl.java @@ -62,9 +62,11 @@ import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.GregorianCalendar; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -216,6 +218,76 @@ public Patient getPatient(Integer patientId) throws APIException { return dao.getPatient(patientId); } + /** + * @see org.openmrs.api.PatientService#getPatients(String, String, List, boolean, String, Integer, Integer, Date) + */ + + @Override + public List getPatients(String name, String identifier, List identifierTypes, + boolean matchIdentifierExactly, String gender, Integer from, Integer to, Date birthdate) throws APIException { + if (identifierTypes == null) { + Date fromDate = null; + Date toDate = null; + + if (to != null && from != null) { + Calendar today = Calendar.getInstance(); + fromDate = new GregorianCalendar(today.get(Calendar.YEAR) - from, 0, 1).getTime(); + toDate = new GregorianCalendar(today.get(Calendar.YEAR) - to, 11, 31).getTime(); + } + + if (gender == null && to == null && from == null && birthdate == null) { + return dao.getPatients(name != null ? name : identifier, 0, null); + } + + else if (gender != null && to == null && from == null && birthdate == null) { + if (name == null && identifier == null) { + return dao.getPatientsByGender(gender, 0, null, false); + } + return dao.getPatientsByNameOrIdAndGender(name != null ? name : identifier, gender, 0, null, false); + } + + else if (name == null && identifier == null && gender == null && to != null && from != null && birthdate == null) { + return dao.getPatientsByRangeOfAge(fromDate, toDate, 0, null, false); + } + + else if (name == null && identifier == null && gender == null && to == null && from == null && birthdate != null) { + return dao.getPatientsByBirthdate(birthdate, 0, null, false); + } + + else { + return getPatientsBySeachCriteria(name,identifier,identifierTypes,gender,fromDate,toDate,birthdate); + } + + } else { + return dao.getPatients(name != null ? name : identifier, identifierTypes, matchIdentifierExactly, 0, null); + } + + } + + private List getPatientsBySeachCriteria(String name, String identifier, List identifierTypes, + String gender, Date from, Date to, Date birthdate) { + if (name == null && identifier == null) { + if (birthdate == null) { + return dao.getPatientsByGenderAndAge(gender, from, to, 0, null, false); + } else { + return dao.getPatientsByGenderAndBirthdate(gender, birthdate, 0, null, false); + } + } + if (gender == null) { + if (birthdate == null) { + return dao.getPatientsByNameOrIdAndRangeOfAge(name != null ? name : identifier, from, to, 0, null, false); + } else { + return dao.getPatientsByNameOrIdAndBirthdate(name != null ? name : identifier, birthdate, 0, null, false); + } + } + + if (birthdate == null) { + return dao.getPatientsByNameOrIdAndGenderAndRangeOfAge(name != null ? name : identifier, gender, from, to, 0, null, false); + } else { + return dao.getPatientsByNameOrIdAndGenderAndBirthdate(name != null ? name : identifier, gender, birthdate, 0, null, false); + } + } + @Override @Transactional(readOnly = true) public Patient getPatientOrPromotePerson(Integer patientOrPersonId) { diff --git a/api/src/main/java/org/openmrs/util/OpenmrsConstants.java b/api/src/main/java/org/openmrs/util/OpenmrsConstants.java index b2b919dc4729..ca78e69e69a5 100644 --- a/api/src/main/java/org/openmrs/util/OpenmrsConstants.java +++ b/api/src/main/java/org/openmrs/util/OpenmrsConstants.java @@ -599,9 +599,9 @@ public static final Collection AUTO_ROLES() { /** * Indicates the version of the search index. The index will be rebuilt, if the version changes. * - * @since 1.11 + * @since 2.3 */ - public static final Integer SEARCH_INDEX_VERSION = 7; + public static final Integer SEARCH_INDEX_VERSION = 8; /** * @since 1.12 diff --git a/api/src/main/resources/org/openmrs/module/dtd/config-1.5.dtd b/api/src/main/resources/org/openmrs/module/dtd/config-1.5.dtd index 6fafe00f93eb..ff3777b2efc2 100644 --- a/api/src/main/resources/org/openmrs/module/dtd/config-1.5.dtd +++ b/api/src/main/resources/org/openmrs/module/dtd/config-1.5.dtd @@ -27,7 +27,7 @@ (filter*), (filter-mapping*), (messages*), - (mappingFiles?) + (mappingFiles?), (packagesWithMappedClasses?) )> diff --git a/api/src/test/java/org/openmrs/api/PatientSearchCriteriaServiceTest.java b/api/src/test/java/org/openmrs/api/PatientSearchCriteriaServiceTest.java new file mode 100644 index 000000000000..7044b419c567 --- /dev/null +++ b/api/src/test/java/org/openmrs/api/PatientSearchCriteriaServiceTest.java @@ -0,0 +1,126 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.api; + +import java.sql.SQLException; +import java.util.GregorianCalendar; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openmrs.Patient; +import org.openmrs.api.context.Context; +import org.openmrs.test.BaseContextSensitiveTest; +import org.openmrs.test.SkipBaseSetup; + +public class PatientSearchCriteriaServiceTest extends BaseContextSensitiveTest{ + +private final static String PATIENTS_XML = "org/openmrs/api/include/PatientSearchCriteriaServiceTest-patients.xml"; + + + private PatientService patientService=null; + + @Before + public void runBeforeEachTest() { + patientService = Context.getPatientService(); + try { + initializeInMemoryDatabase(); + } + catch (SQLException e) { + e.printStackTrace(); + } + executeDataSet(PATIENTS_XML); + updateSearchIndex(); + authenticate(); + } + + @Test + @SkipBaseSetup + public void getPatients_shouldReturnListOfPatientsWithRequriedGender() { + List patients = patientService.getPatients(null, null, null, true, "F", null, null, null); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("F", patients.get(0).getGender()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = patientService.getPatients(null, null, null, false, null, null, null, birthdate.getTime()); + Assert.assertEquals(2, patients.size()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedAgeRange() { + List patients = patientService.getPatients(null, null, null, false, null, 3, 10, null); + Assert.assertEquals(3, patients.size()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedGenderAndBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = patientService.getPatients(null, null, null, false, "M", null, null, birthdate.getTime()); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("M", patients.get(0).getGender()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedGenderAndAgeRange() { + List patients = patientService.getPatients(null, null, null, false, "F", 3, 10, null); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("F", patients.get(0).getGender()); + + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedNameAndGender() { + List patients = patientService.getPatients("Frank", null, null, true, "M", null, null, null); + Assert.assertEquals(3, patients.size()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedPatientIdentifierAndGender() { + List patients = patientService.getPatients(null, "82-82-82", null, true, null, null, null, null); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals(82, patients.get(0).getId().longValue()); + Assert.assertEquals("M", patients.get(0).getGender()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedNameAndBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = patientService + .getPatients("Adam", null, null, false, null, null, null, birthdate.getTime()); + Assert.assertEquals(1, patients.size()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedNameAndAgeRange() { + List patients = patientService.getPatients("Frank", null, null, false, null, 3, 10, null); + Assert.assertEquals(3, patients.size()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedNameAndGenderAndBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = patientService + .getPatients("Frank", null, null, false, "F", null, null, birthdate.getTime()); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Frank", patients.get(0).getMiddleName()); + } + + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedNameAndGenderAndAgeRange() { + List patients = patientService.getPatients("Frank", null, null, false, "F", 3, 10, null); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Frank", patients.get(0).getMiddleName()); + } + +} diff --git a/api/src/test/java/org/openmrs/api/db/PatientSearchCriteriaDAOTest.java b/api/src/test/java/org/openmrs/api/db/PatientSearchCriteriaDAOTest.java new file mode 100644 index 000000000000..1a1343a486a3 --- /dev/null +++ b/api/src/test/java/org/openmrs/api/db/PatientSearchCriteriaDAOTest.java @@ -0,0 +1,263 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under + * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. + * + * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS + * graphic logo is a trademark of OpenMRS Inc. + */ +package org.openmrs.api.db; + +import java.sql.SQLException; +import java.util.GregorianCalendar; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openmrs.Patient; +import org.openmrs.test.BaseContextSensitiveTest; +import org.openmrs.test.SkipBaseSetup; +import org.springframework.beans.factory.annotation.Autowired; + +public class PatientSearchCriteriaDAOTest extends BaseContextSensitiveTest { + +private final static String PATIENTS_XML = "org/openmrs/api/db/hibernate/include/PatientSearchCriteriaDAOTest-patients.xml"; + + @Autowired + private PatientDAO dao; + + @Before + public void runBeforeEachTest() { + try { + initializeInMemoryDatabase(); + } + catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + executeDataSet(PATIENTS_XML); + + updateSearchIndex(); + authenticate(); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, Integer, Integer, Boolean) + */ + @Test + @SkipBaseSetup + public void getPatients_shouldReturnListOfPatientsWithRequriedGender() { + List patients = dao.getPatientsByGender("M", 0, 11, false); + Assert.assertEquals(3, patients.size()); + Assert.assertEquals("M", patients.get(0).getGender()); + Assert.assertEquals("M", patients.get(1).getGender()); + Assert.assertEquals("M", patients.get(2).getGender()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(java.util.Date, Integer, Integer, Boolean) + */ + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByBirthdate(birthdate.getTime(), 0, 11, false); + Assert.assertEquals(2, patients.size()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(java.util.Date, java.util.Date, Integer, Integer, + * Boolean) + */ + @Test + public void getPatient_shouldReturnListOfPatientsWithRequriedAge() { + GregorianCalendar to = new GregorianCalendar(2013, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByRangeOfAge(from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(3, patients.size()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedGivenNameAndGender() { + List patients = dao.getPatientsByNameOrIdAndGender("Bethany", "F", 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Bethany", patients.get(0).getGivenName()); + Assert.assertEquals("F", patients.get(0).getGender()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, java.util.Date, Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedGivenNameAndBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByNameOrIdAndBirthdate("Bethany", birthdate.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Bethany", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, java.util.Date, java.util.Date, Integer, + * Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedGivenNameAndAgeRange() { + GregorianCalendar to = new GregorianCalendar(2013, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByNameOrIdAndRangeOfAge("Bethany", from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Bethany", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, java.util.Date, Integer, Integer, + * Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedGivenNameAndBirthdaterAndGender() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByNameOrIdAndGenderAndBirthdate("Adam", "M", birthdate.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Adam", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, java.util.Date, java.util.Date, + * Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedGivenNameAndAgeRangeAndGender() { + GregorianCalendar to = new GregorianCalendar(2013, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByNameOrIdAndGenderAndRangeOfAge("Adam", "M", from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Adam", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedMiddleNameAndGender() { + List patients = dao.getPatientsByNameOrIdAndGender("Benedict", "M", 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Benedict", patients.get(0).getMiddleName()); + Assert.assertEquals("M", patients.get(0).getGender()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, java.util.Date, Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedMiddleNameAndBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByNameOrIdAndBirthdate("Benedict", birthdate.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Benedict", patients.get(0).getMiddleName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, java.util.Date, java.util.Date, Integer, + * Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedMiddleNameAndAgeRange() { + GregorianCalendar to = new GregorianCalendar(2014, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByNameOrIdAndRangeOfAge("Benedict", from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Benedict", patients.get(0).getMiddleName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, java.util.Date, Integer, Integer, + * Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedMiddleNameAndBirthdaterAndGender() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByNameOrIdAndGenderAndBirthdate("Frank", "F", birthdate.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Frank", patients.get(0).getMiddleName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, java.util.Date, java.util.Date, + * Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedMiddleNameAndAgeRangeAndGender() { + GregorianCalendar to = new GregorianCalendar(2013, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByNameOrIdAndGenderAndRangeOfAge("Frank", "F", from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Frank", patients.get(0).getMiddleName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedfamilyNameAndGender() { + List patients = dao.getPatientsByNameOrIdAndGender("Franklin", "M", 0, 11, false); + Assert.assertEquals(2, patients.size()); + Assert.assertEquals("Franklin", patients.get(0).getFamilyName()); + Assert.assertEquals("Franklin", patients.get(1).getFamilyName()); + Assert.assertEquals("M", patients.get(0).getGender()); + Assert.assertEquals("M", patients.get(1).getGender()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, java.util.Date, Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedfamilyNameAndBirthdate() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByNameOrIdAndBirthdate("Franklin", birthdate.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Adam", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, java.util.Date, java.util.Date, Integer, + * Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedfamilyNameAndAgeRange() { + GregorianCalendar to = new GregorianCalendar(2013, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByNameOrIdAndRangeOfAge("Franklin", from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Adam", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, java.util.Date, Integer, Integer, + * Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedfamilyNameAndBirthdaterAndGender() { + GregorianCalendar birthdate = new GregorianCalendar(2014, 7, 28); + List patients = dao.getPatientsByNameOrIdAndGenderAndBirthdate("Franklin", "M", birthdate.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Adam", patients.get(0).getGivenName()); + } + + /** + * @see PatientSearchCriteriaDAO#getPatients(String, String, java.util.Date, java.util.Date, + * Integer, Integer, Boolean) + */ + @Test + public void getPatients_shouldReturnListOfPatientsWithRequriedfamilyNameAndAgeRangeAndGender() { + GregorianCalendar to = new GregorianCalendar(2013, 7, 28); + GregorianCalendar from = new GregorianCalendar(2015, 7, 28); + List patients = dao.getPatientsByNameOrIdAndGenderAndRangeOfAge("Franklin", "M", from.getTime(), to.getTime(), 0, 11, false); + Assert.assertEquals(1, patients.size()); + Assert.assertEquals("Adam", patients.get(0).getGivenName()); + } + +} diff --git a/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java b/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java index 1381b148fcc4..0f2322cb8128 100644 --- a/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java +++ b/api/src/test/java/org/openmrs/test/BaseContextSensitiveTest.java @@ -74,6 +74,7 @@ import org.openmrs.ConceptName; import org.openmrs.Drug; import org.openmrs.PatientIdentifier; +import org.openmrs.Person; import org.openmrs.PersonAttribute; import org.openmrs.PersonName; import org.openmrs.User; @@ -945,7 +946,7 @@ public void baseSetupWithStandardDataAndAuthentication() throws SQLException { public Class[] getIndexedTypes() { return new Class[] { ConceptName.class, Drug.class, PersonName.class, PersonAttribute.class, - PatientIdentifier.class}; + PatientIdentifier.class,Person.class}; } /** diff --git a/api/src/test/resources/org/openmrs/api/db/hibernate/include/PatientSearchCriteriaDAOTest-patients.xml b/api/src/test/resources/org/openmrs/api/db/hibernate/include/PatientSearchCriteriaDAOTest-patients.xml new file mode 100644 index 000000000000..0ecec8f07868 --- /dev/null +++ b/api/src/test/resources/org/openmrs/api/db/hibernate/include/PatientSearchCriteriaDAOTest-patients.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/api/src/test/resources/org/openmrs/api/include/PatientSearchCriteriaServiceTest-patients.xml b/api/src/test/resources/org/openmrs/api/include/PatientSearchCriteriaServiceTest-patients.xml new file mode 100644 index 000000000000..ebf0bea50495 --- /dev/null +++ b/api/src/test/resources/org/openmrs/api/include/PatientSearchCriteriaServiceTest-patients.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +