diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/HibernateAdministrationDAO.java b/api/src/main/java/org/openmrs/api/db/hibernate/HibernateAdministrationDAO.java index 8c1d269761ab..5571b6349be8 100644 --- a/api/src/main/java/org/openmrs/api/db/hibernate/HibernateAdministrationDAO.java +++ b/api/src/main/java/org/openmrs/api/db/hibernate/HibernateAdministrationDAO.java @@ -9,21 +9,22 @@ */ package org.openmrs.api.db.hibernate; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; import java.sql.Statement; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import org.hibernate.Criteria; import org.hibernate.FlushMode; import org.hibernate.MappingException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.boot.Metadata; -import org.hibernate.criterion.MatchMode; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Restrictions; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.jdbc.Work; import org.hibernate.mapping.Column; @@ -93,56 +94,89 @@ public String getGlobalProperty(String propertyName) throws DAOException { return gp.getPropertyValue(); } - + /** * @see org.openmrs.api.db.AdministrationDAO#getGlobalPropertyObject(java.lang.String) */ @Override public GlobalProperty getGlobalPropertyObject(String propertyName) { + Session session = sessionFactory.getCurrentSession(); + if (isDatabaseStringComparisonCaseSensitive()) { - Criteria criteria = sessionFactory.getCurrentSession().createCriteria(GlobalProperty.class); - return (GlobalProperty) criteria.add(Restrictions.eq(PROPERTY, propertyName).ignoreCase()) - .uniqueResult(); + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(GlobalProperty.class); + Root root = query.from(GlobalProperty.class); + + Predicate condition = (propertyName != null) + ? cb.equal(cb.lower(root.get(PROPERTY)), propertyName.toLowerCase()) + : cb.isNull(root.get(PROPERTY)); + + query.where(condition); + + return session.createQuery(query).uniqueResult(); } else { - return (GlobalProperty) sessionFactory.getCurrentSession().get(GlobalProperty.class, propertyName); + return session.get(GlobalProperty.class, propertyName); } } - + @Override public GlobalProperty getGlobalPropertyByUuid(String uuid) throws DAOException { - - return (GlobalProperty) sessionFactory.getCurrentSession() - .createQuery("from GlobalProperty t where t.uuid = :uuid").setString("uuid", uuid).uniqueResult(); + return HibernateUtil.getUniqueEntityByUUID(sessionFactory, GlobalProperty.class, uuid); } - + /** * @see org.openmrs.api.db.AdministrationDAO#getAllGlobalProperties() */ @Override - @SuppressWarnings("unchecked") public List getAllGlobalProperties() throws DAOException { - Criteria criteria = sessionFactory.getCurrentSession().createCriteria(GlobalProperty.class); - return criteria.addOrder(Order.asc(PROPERTY)).list(); + Session session = sessionFactory.getCurrentSession(); + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(GlobalProperty.class); + Root root = query.from(GlobalProperty.class); + + query.orderBy(cb.asc(root.get(PROPERTY))); + + return session.createQuery(query).getResultList(); } /** * @see org.openmrs.api.db.AdministrationDAO#getGlobalPropertiesByPrefix(java.lang.String) */ @Override - @SuppressWarnings("unchecked") public List getGlobalPropertiesByPrefix(String prefix) { - return sessionFactory.getCurrentSession().createCriteria(GlobalProperty.class) - .add(Restrictions.ilike(PROPERTY, prefix, MatchMode.START)).list(); + if (prefix == null) { + log.warn("Attempted to get global properties with a null prefix"); + return Collections.emptyList(); + } + + Session session = sessionFactory.getCurrentSession(); + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(GlobalProperty.class); + Root root = query.from(GlobalProperty.class); + + query.where(cb.like(cb.lower(root.get(PROPERTY)), MatchMode.START.toCaseInsensitivePattern(prefix))); + + return session.createQuery(query).getResultList(); } /** * @see org.openmrs.api.db.AdministrationDAO#getGlobalPropertiesBySuffix(java.lang.String) */ @Override - @SuppressWarnings("unchecked") public List getGlobalPropertiesBySuffix(String suffix) { - return sessionFactory.getCurrentSession().createCriteria(GlobalProperty.class) - .add(Restrictions.ilike(PROPERTY, suffix, MatchMode.END)).list(); + if (suffix == null) { + log.warn("Attempted to get global properties with a null suffix"); + return Collections.emptyList(); + } + + Session session = sessionFactory.getCurrentSession(); + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(GlobalProperty.class); + Root root = query.from(GlobalProperty.class); + + query.where(cb.like(cb.lower(root.get(PROPERTY)), MatchMode.END.toCaseInsensitivePattern(suffix))); + + return session.createQuery(query).getResultList(); } /** diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/HibernateUtil.java b/api/src/main/java/org/openmrs/api/db/hibernate/HibernateUtil.java index a94eaa606dea..cde9463453af 100644 --- a/api/src/main/java/org/openmrs/api/db/hibernate/HibernateUtil.java +++ b/api/src/main/java/org/openmrs/api/db/hibernate/HibernateUtil.java @@ -9,6 +9,9 @@ */ package org.openmrs.api.db.hibernate; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; import java.sql.Connection; import java.sql.SQLException; import java.util.Map; @@ -16,6 +19,7 @@ import org.apache.commons.lang3.StringUtils; import org.hibernate.Criteria; import org.hibernate.Hibernate; +import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Conjunction; import org.hibernate.criterion.DetachedCriteria; @@ -28,6 +32,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.proxy.HibernateProxy; import org.openmrs.Location; +import org.openmrs.api.db.DAOException; import org.openmrs.attribute.AttributeType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -188,4 +193,14 @@ public static T getRealObjectFromProxy(T persistentObject) { return persistentObject; } + + public static T getUniqueEntityByUUID(SessionFactory sessionFactory, Class entityClass, String uuid) throws DAOException { + Session session = sessionFactory.getCurrentSession(); + CriteriaBuilder cb = session.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(entityClass); + Root root = query.from(entityClass); + + query.where(cb.equal(root.get("uuid"), uuid)); + return session.createQuery(query).uniqueResult(); + } } diff --git a/api/src/main/java/org/openmrs/api/db/hibernate/MatchMode.java b/api/src/main/java/org/openmrs/api/db/hibernate/MatchMode.java new file mode 100644 index 000000000000..df00f18b3173 --- /dev/null +++ b/api/src/main/java/org/openmrs/api/db/hibernate/MatchMode.java @@ -0,0 +1,43 @@ +/** + * 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; + +public enum MatchMode { + START, + END, + ANYWHERE, + EXACT; + + public String toCaseSensitivePattern(String str) { + return toPatternInternal(str, false); + } + + public String toCaseInsensitivePattern(String str) { + return toPatternInternal(str, true); + } + + private String toPatternInternal(String str, boolean caseInsensitive) { + if (str == null) { + return null; + } + String processedStr = caseInsensitive ? str.toLowerCase() : str; + switch (this) { + case START: + return processedStr + "%"; + case END: + return "%" + processedStr; + case ANYWHERE: + return "%" + processedStr + "%"; + case EXACT: + default: + return processedStr; + } + } +} diff --git a/api/src/test/java/org/openmrs/OpenmrsTestsTest.java b/api/src/test/java/org/openmrs/OpenmrsTestsTest.java index 05432bef44f7..bd208ec5b75c 100644 --- a/api/src/test/java/org/openmrs/OpenmrsTestsTest.java +++ b/api/src/test/java/org/openmrs/OpenmrsTestsTest.java @@ -29,6 +29,7 @@ import org.junit.Ignore; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; import org.openmrs.annotation.OpenmrsProfileExcludeFilterWithModulesJUnit4Test; import org.openmrs.annotation.StartModuleAnnotationJUnit4Test; import org.openmrs.annotation.StartModuleAnnotationReuseJUnit4Test; @@ -98,7 +99,7 @@ public void shouldHaveTestAnnotationWhenStartingWithShould() { // make sure every should___ method has an @Test annotation if (methodName.startsWith("should") || methodName.contains("_should")) { - assertTrue(method.getAnnotation(Test.class) != null || method.getAnnotation(org.junit.Test.class) != null, currentClass.getName() + "#" + methodName + " does not have the @Test annotation on it even though the method name starts with 'should'"); + assertTrue(method.getAnnotation(Test.class) != null || method.getAnnotation(org.junit.Test.class) != null || method.getAnnotation(ParameterizedTest.class) != null, currentClass.getName() + "#" + methodName + " does not have the @Test annotation on it even though the method name starts with 'should'"); } } } diff --git a/api/src/test/java/org/openmrs/api/AdministrationServiceTest.java b/api/src/test/java/org/openmrs/api/AdministrationServiceTest.java index eaf19ae5de1a..774dc94c133a 100644 --- a/api/src/test/java/org/openmrs/api/AdministrationServiceTest.java +++ b/api/src/test/java/org/openmrs/api/AdministrationServiceTest.java @@ -291,6 +291,57 @@ public void getGlobalPropertiesByPrefix_shouldReturnAllRelevantGlobalPropertiesI assertTrue(property.getPropertyValue().startsWith("correct-value")); } } + + @Test + public void getGlobalPropertiesByInvalidPrefix_shouldReturnEmptyList() { + executeDataSet("org/openmrs/api/include/AdministrationServiceTest-globalproperties.xml"); + + String invalidPrefix = "non.existing.prefix."; + List properties = adminService.getGlobalPropertiesByPrefix(invalidPrefix); + + assertTrue(properties.isEmpty()); + } + + @Test + public void getGlobalPropertiesByPrefix_shouldReturnEmptyWhenPrefixIsNull() { + executeDataSet("org/openmrs/api/include/AdministrationServiceTest-globalproperties.xml"); + List properties = adminService.getGlobalPropertiesByPrefix(null); + + assertNotNull(properties); + assertTrue(properties.isEmpty()); + } + + @Test + public void getGlobalPropertiesBySuffix_shouldReturnAllRelevantGlobalPropertiesInTheDatabase() { + executeDataSet("org/openmrs/api/include/AdministrationServiceTest-globalproperties.xml"); + + List properties = adminService.getGlobalPropertiesBySuffix(".abcd"); + + assertNotNull(properties); + assertTrue(properties.size() > 0); + for (GlobalProperty property : properties) { + assertTrue(property.getProperty().endsWith(".abcd")); + } + } + + @Test + public void getGlobalPropertiesByInvalidSuffix_shouldReturnEmptyList() { + executeDataSet("org/openmrs/api/include/AdministrationServiceTest-globalproperties.xml"); + + String invalidSuffix = "non.existing.suffix."; + List properties = adminService.getGlobalPropertiesBySuffix(invalidSuffix); + + assertTrue(properties.isEmpty()); + } + + @Test + public void getGlobalPropertiesBySuffix_shouldReturnEmptyWhenSuffixIsNull() { + executeDataSet("org/openmrs/api/include/AdministrationServiceTest-globalproperties.xml"); + List properties = adminService.getGlobalPropertiesBySuffix(null); + + assertNotNull(properties); + assertTrue(properties.isEmpty()); + } @Test public void getAllowedLocales_shouldNotFailIfNotGlobalPropertyForLocalesAllowedDefinedYet() { @@ -349,7 +400,24 @@ public void getAllGlobalProperties_shouldReturnAllGlobalPropertiesInTheDatabase( executeDataSet(ADMIN_INITIAL_DATA_XML); assertEquals(allGlobalPropertiesSize + 9, adminService.getAllGlobalProperties().size()); } - + + @Test + public void getAllGlobalProperties_shouldReturnPropertiesInAscendingOrder() { + executeDataSet(ADMIN_INITIAL_DATA_XML); + List properties = adminService.getAllGlobalProperties(); + + assertFalse(properties.isEmpty(), "The list of global properties should not be empty"); + + // Verify the properties are in ascending order + for (int i = 0; i < properties.size() - 1; i++) { + String currentProperty = properties.get(i).getProperty(); + String nextProperty = properties.get(i + 1).getProperty(); + + assertTrue(currentProperty.compareTo(nextProperty) <= 0, + "The global properties should be in ascending order by the property name"); + } + } + @Test public void getAllowedLocales_shouldReturnAtLeastOneLocaleIfNoLocalesDefinedInDatabaseYet() { assertTrue(adminService.getAllowedLocales().size() > 0); diff --git a/api/src/test/java/org/openmrs/api/db/hibernate/MatchModeTest.java b/api/src/test/java/org/openmrs/api/db/hibernate/MatchModeTest.java new file mode 100644 index 000000000000..de696191f1d3 --- /dev/null +++ b/api/src/test/java/org/openmrs/api/db/hibernate/MatchModeTest.java @@ -0,0 +1,44 @@ +/** + * 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 static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class MatchModeTest { + + static Stream data() { + return Arrays.stream(new Object[][]{ + {MatchMode.START, "TEST", true, "test%"}, + {MatchMode.END, "TeST", true, "%test"}, + {MatchMode.ANYWHERE, "test", true, "%test%"}, + {MatchMode.EXACT, "TEst", true, "test"}, + {MatchMode.START, "TEST", false, "TEST%"}, + {MatchMode.END, "TeST", false, "%TeST"}, + {MatchMode.ANYWHERE, "test", false, "%test%"}, + {MatchMode.EXACT, "TEst", false, "TEst"}, + }); + } + + @ParameterizedTest + @MethodSource("data") + public void shouldMatchPatternCorrectly(MatchMode matchMode, String input, boolean caseInsensitive, String expectedPattern) { + if (caseInsensitive) { + assertEquals(expectedPattern, matchMode.toCaseInsensitivePattern(input)); + } else { + assertEquals(expectedPattern, matchMode.toCaseSensitivePattern(input)); + } + } +}