diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6261-upgrade-to-jakarta-mail.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6261-upgrade-to-jakarta-mail.yaml new file mode 100644 index 00000000000..96433fc01cf --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6261-upgrade-to-jakarta-mail.yaml @@ -0,0 +1,5 @@ +--- +type: change +issue: 6261 +title: "Upgrading to Jakarta had caused a problem with the version of java-simple-mail that was in use. This has been updated to be conformant +with the Jakarta Mail APIs. Thanks to Thomas Papke(@thopap) for the contribution!" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6290-mssql-migration-trim-fix.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6290-mssql-migration-trim-fix.yaml new file mode 100644 index 00000000000..435207a3454 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6290-mssql-migration-trim-fix.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 6290 +title: "Previously, a specific migration task was using the `TRIM()` function, which does not exist in MSSQL 2012. This was causing migrations targeting MSSQL 2012 to fail. +This has been corrected and replaced with usage of a combination of LTRIM() and RTRIM(). Thanks to Primož Delopst at Better for the contribution!" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6317-fix-resource-duplication-for-composite-unique-sp-with-date-time-component.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6317-fix-resource-duplication-for-composite-unique-sp-with-date-time-component.yaml new file mode 100644 index 00000000000..f1e379d654c --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_6_0/6317-fix-resource-duplication-for-composite-unique-sp-with-date-time-component.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 6317 +title: "Previously, defining a unique combo Search Parameter with the DateTime component and submitting multiple +resources with the same dateTime element (e.g. Observation.effectiveDateTime) resulted in duplicate resource creation. +This has been fixed." + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index bb50e0dd613..59ecfa233ae 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -1972,7 +1972,7 @@ public class SearchBuilder implements ISearchBuilder { * The loop allows us to create multiple combo index joins if there * are multiple AND expressions for the related parameters. */ - while (validateParamValuesAreValidForComboParam(theRequest, theParams, comboParamNames)) { + while (validateParamValuesAreValidForComboParam(theRequest, theParams, comboParamNames, comboParam)) { applyComboSearchParam(theQueryStack, theParams, theRequest, comboParamNames, comboParam); } } @@ -2068,7 +2068,10 @@ public class SearchBuilder implements ISearchBuilder { * (e.g. ?date=gt2024-02-01), etc. */ private boolean validateParamValuesAreValidForComboParam( - RequestDetails theRequest, @Nonnull SearchParameterMap theParams, List theComboParamNames) { + RequestDetails theRequest, + @Nonnull SearchParameterMap theParams, + List theComboParamNames, + RuntimeSearchParam theComboParam) { boolean paramValuesAreValidForCombo = true; List> paramOrValues = new ArrayList<>(theComboParamNames.size()); @@ -2129,6 +2132,19 @@ public class SearchBuilder implements ISearchBuilder { break; } } + + // Date params are not eligible for using composite unique index + // as index could contain date with different precision (e.g. DAY, SECOND) + if (nextParamDef.getParamType() == RestSearchParameterTypeEnum.DATE + && theComboParam.getComboSearchParamType() == ComboSearchParamType.UNIQUE) { + ourLog.debug( + "Search with params {} is not a candidate for combo searching - " + + "Unique combo search parameter '{}' has DATE type", + theComboParamNames, + nextParamName); + paramValuesAreValidForCombo = false; + break; + } } if (CartesianProductUtil.calculateCartesianProductSize(paramOrValues) > 500) { diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index a35dc11d554..290c166a38c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -566,7 +566,8 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor for (BaseResourceIndexedSearchParam nextParam : paramsListForCompositePart) { IQueryParameterType nextParamAsClientParam = nextParam.toQueryParameterType(); - if (nextParamAsClientParam instanceof DateParam) { + if (theParam.getComboSearchParamType() == ComboSearchParamType.NON_UNIQUE + && nextParamAsClientParam instanceof DateParam) { DateParam date = (DateParam) nextParamAsClientParam; if (date.getPrecision() != TemporalPrecisionEnum.DAY) { continue; diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index ba09eed0ab5..45504f94b2f 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -70,6 +70,11 @@ jakarta.servlet-api provided + + jakarta.mail + jakarta.mail-api + true + diff --git a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java index 90726afb0c8..ae1e985ed22 100644 --- a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java +++ b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java @@ -28,8 +28,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSenderImplTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSenderImplTest.java index fa871fa2595..9c01f3aeb90 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSenderImplTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSenderImplTest.java @@ -17,8 +17,8 @@ import org.junit.jupiter.api.extension.RegisterExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java index 28d8ae61b35..825f6ebb9ae 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java @@ -26,8 +26,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java index 0773223b9f0..de665e28ec0 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/BasePartitioningR4Test.java @@ -135,10 +135,10 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { addCreateDefaultPartition(); addReadDefaultPartition(); // one for search param validation SearchParameter sp = new SearchParameter(); - sp.setId("SearchParameter/patient-birthdate"); - sp.setType(Enumerations.SearchParamType.DATE); - sp.setCode("birthdate"); - sp.setExpression("Patient.birthDate"); + sp.setId("SearchParameter/patient-gender"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.setCode("gender"); + sp.setExpression("Patient.gender"); sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.addBase("Patient"); mySearchParameterDao.update(sp, mySrd); @@ -156,13 +156,13 @@ public abstract class BasePartitioningR4Test extends BaseJpaR4SystemTest { addCreateDefaultPartition(); sp = new SearchParameter(); - sp.setId("SearchParameter/patient-birthdate-unique"); + sp.setId("SearchParameter/patient-gender-family-unique"); sp.setType(Enumerations.SearchParamType.COMPOSITE); sp.setStatus(Enumerations.PublicationStatus.ACTIVE); sp.addBase("Patient"); sp.addComponent() .setExpression("Patient") - .setDefinition("SearchParameter/patient-birthdate"); + .setDefinition("SearchParameter/patient-gender"); sp.addComponent() .setExpression("Patient") .setDefinition("SearchParameter/patient-family"); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamTest.java index baf3e756028..05f4c5a5e24 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ComboUniqueParamTest.java @@ -13,10 +13,10 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.param.DateAndListParam; -import ca.uhn.fhir.rest.param.DateOrListParam; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringOrListParam; +import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; @@ -33,6 +33,7 @@ import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r4.model.HumanName; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Organization; @@ -115,6 +116,46 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test myMessages.clear(); } + private void createUniqueGenderFamilyComboSp() { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/patient-gender"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.setCode("gender"); + sp.setExpression("Patient.gender"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-family"); + sp.setType(Enumerations.SearchParamType.STRING); + sp.setCode("family"); + sp.setExpression("Patient.name.family"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.addBase("Patient"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/patient-gender-family"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Patient"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-gender"); + sp.addComponent() + .setExpression("Patient") + .setDefinition("SearchParameter/patient-family"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(true)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + + myMessages.clear(); + } + private void createUniqueIndexCoverageBeneficiary() { SearchParameter sp = new SearchParameter(); sp.setId("SearchParameter/coverage-beneficiary"); @@ -276,6 +317,45 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test mySearchParamRegistry.forceRefresh(); } + private void createUniqueObservationDateCode() { + SearchParameter sp = new SearchParameter(); + sp.setId("SearchParameter/obs-effective"); + sp.setType(Enumerations.SearchParamType.DATE); + sp.setCode("date"); + sp.setExpression("Observation.effective"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/obs-code"); + sp.setType(Enumerations.SearchParamType.TOKEN); + sp.setCode("code"); + sp.setExpression("Observation.code"); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + mySearchParameterDao.update(sp, mySrd); + + sp = new SearchParameter(); + sp.setId("SearchParameter/observation-date-code"); + sp.setType(Enumerations.SearchParamType.COMPOSITE); + sp.setStatus(PublicationStatus.ACTIVE); + sp.addBase("Observation"); + sp.setExpression("Observation.code"); + sp.addComponent() + .setExpression("Observation") + .setDefinition("SearchParameter/obs-effective"); + sp.addComponent() + .setExpression("Observation") + .setDefinition("SearchParameter/obs-code"); + sp.addExtension() + .setUrl(HapiExtensions.EXT_SP_UNIQUE) + .setValue(new BooleanType(true)); + mySearchParameterDao.update(sp, mySrd); + + mySearchParamRegistry.forceRefresh(); + } + private void createUniqueObservationSubjectDateCode() { SearchParameter sp = new SearchParameter(); sp.setId("SearchParameter/obs-subject"); @@ -471,11 +551,11 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test public void testDoubleMatchingOnAnd_Search_TwoAndOrValues() { myStorageSettings.setUniqueIndexesCheckedBeforeSave(false); - createUniqueBirthdateAndGenderSps(); + createUniqueGenderFamilyComboSp(); Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); + pt1.getName().add(new HumanName().setFamily("Family1")); String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue(); // Two OR values on the same resource - Currently composite SPs don't work for this @@ -484,17 +564,22 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test sp.setLoadSynchronous(true); sp.add(Patient.SP_GENDER, new TokenAndListParam() - .addAnd(new TokenParam("http://hl7.org/fhir/administrative-gender","male"), new TokenParam( "http://hl7.org/fhir/administrative-gender","female")) + .addAnd(new TokenParam("http://hl7.org/fhir/administrative-gender","male"), + new TokenParam( "http://hl7.org/fhir/administrative-gender","female")) ); - sp.add(Patient.SP_BIRTHDATE, - new DateAndListParam() - .addAnd(new DateParam("2011-01-01"), new DateParam( "2011-02-02")) + sp.add(Patient.SP_FAMILY, + new StringOrListParam() + .addOr(new StringParam("Family1")).addOr(new StringParam("Family2")) ); IBundleProvider outcome = myPatientDao.search(sp, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(outcome)).containsExactlyInAnyOrder(id1); String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); - assertEquals("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN ('Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale','Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale','Patient?birthdate=2011-02-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale','Patient?birthdate=2011-02-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )", unformattedSql); + assertEquals("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN (" + + "'Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale'," + + "'Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale'," + + "'Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cfemale'," + + "'Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )", unformattedSql); } @@ -1167,16 +1252,16 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test @Test public void testOrQuery() { myStorageSettings.setAdvancedHSearchIndexing(false); - createUniqueBirthdateAndGenderSps(); + createUniqueGenderFamilyComboSp(); Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); + pt1.getName().add(new HumanName().setFamily("Family1")); IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-02")); + pt2.getName().add(new HumanName().setFamily("Family2")); IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myCaptureQueriesListener.clear(); @@ -1184,16 +1269,21 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(100); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateOrListParam().addOr(new DateParam("2011-01-01")).addOr(new DateParam("2011-01-02"))); + params.add("family", new StringOrListParam() + .addOr(new StringParam("Family1")).addOr(new StringParam("Family2"))); myCaptureQueriesListener.clear(); IBundleProvider results = myPatientDao.search(params, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue(), id2.getValue()); assertThat(myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, false)) - .contains("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN ('Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale','Patient?birthdate=2011-01-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )"); + .contains("SELECT t0.RES_ID FROM HFJ_IDX_CMP_STRING_UNIQ t0 WHERE (t0.IDX_STRING IN " + + "('Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale'," + + "'Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale') )"); logCapturedMessages(); - assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: [Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale, Patient?birthdate=2011-01-02&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale]"); + assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " + + "[Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale, " + + "Patient?family=Family2&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale]"); myMessages.clear(); } @@ -1201,16 +1291,16 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test @Test public void testSearchSynchronousUsingUniqueComposite() { myStorageSettings.setAdvancedHSearchIndexing(false); - createUniqueBirthdateAndGenderSps(); + createUniqueGenderFamilyComboSp(); Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); + pt1.getName().add(new HumanName().setFamily("Family1")); IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless(); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-02")); + pt2.getName().add(new HumanName().setFamily("Family2")); myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myCaptureQueriesListener.clear(); @@ -1218,13 +1308,14 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test SearchParameterMap params = new SearchParameterMap(); params.setLoadSynchronousUpTo(100); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-01")); + params.add("family", new StringParam("Family1")); IBundleProvider results = myPatientDao.search(params, mySrd); myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1.getValue()); logCapturedMessages(); - assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); + assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " + + "Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); myMessages.clear(); } @@ -1232,33 +1323,34 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test @Test public void testSearchUsingUniqueComposite() { - createUniqueBirthdateAndGenderSps(); + createUniqueGenderFamilyComboSp(); Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); + pt1.getName().add(new HumanName().setFamily("Family1")); String id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue(); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-02")); + pt2.getName().add(new HumanName().setFamily("Family2")); myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myMessages.clear(); SearchParameterMap params = new SearchParameterMap(); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-01")); + params.add("family", new StringParam("Family1")); IBundleProvider results = myPatientDao.search(params, mySrd); String searchId = results.getUuid(); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id1); logCapturedMessages(); - assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); + assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " + + "Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); myMessages.clear(); // Other order myMessages.clear(); params = new SearchParameterMap(); - params.add("birthdate", new DateParam("2011-01-01")); + params.add("family", new StringParam("Family1")); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); results = myPatientDao.search(params, mySrd); assertEquals(searchId, results.getUuid()); @@ -1272,16 +1364,17 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test myMessages.clear(); params = new SearchParameterMap(); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-03")); + params.add("family", new StringParam("Family3")); results = myPatientDao.search(params, mySrd); assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty(); logCapturedMessages(); - assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-03&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); + assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " + + "Patient?family=Family3&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); myMessages.clear(); myMessages.clear(); params = new SearchParameterMap(); - params.add("birthdate", new DateParam("2011-01-03")); + params.add("family", new StringParam("Family3")); results = myPatientDao.search(params, mySrd); assertThat(toUnqualifiedVersionlessIdValues(results)).isEmpty(); // STANDARD QUERY @@ -1666,7 +1759,7 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test } @Test - public void testDuplicateUniqueValuesAreRejected() { + public void testDuplicateUniqueValuesWithDateAreRejected() { createUniqueBirthdateAndGenderSps(); Patient pt1 = new Patient(); @@ -1699,13 +1792,75 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test } @Test - public void testReplaceOneWithAnother() { - myStorageSettings.setAdvancedHSearchIndexing(false); + public void testDuplicateUniqueValuesWithDateTimeAreRejected() { + createUniqueObservationDateCode(); + + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + obs.setEffective(new DateTimeType("2017-10-10T00:00:00")); + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + + try { + myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); + fail(); + } catch (ResourceVersionConflictException e) { + assertThat(e.getMessage()) + .contains("new unique index created by SearchParameter/observation-date-code"); + } + } + + @Test + public void testUniqueComboSearchWithDateNotUsingUniqueIndex() { createUniqueBirthdateAndGenderSps(); Patient pt1 = new Patient(); pt1.setGender(Enumerations.AdministrativeGender.MALE); pt1.setBirthDateElement(new DateType("2011-01-01")); + String pId = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue(); + + myCaptureQueriesListener.clear(); + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(100); + params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); + params.add("birthdate", new DateParam("2011-01-01")); + + IBundleProvider results = myPatientDao.search(params, mySrd); + assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(pId); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql).doesNotContain("HFJ_IDX_CMP_STRING_UNIQ"); + } + + @Test + public void testUniqueComboSearchWithDateTimeNotUsingUniqueIndex() { + createUniqueObservationDateCode(); + + Observation obs = new Observation(); + obs.getCode().addCoding().setSystem("foo").setCode("bar"); + obs.setEffective(new DateTimeType("2017-10-10T00:00:00")); + String obsId = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless().getValue(); + + myCaptureQueriesListener.clear(); + SearchParameterMap params = new SearchParameterMap(); + params.setLoadSynchronousUpTo(100); + params.add("code", new TokenParam("foo", "bar")); + params.add("date", new DateParam("2017-10-10T00:00:00")); + + IBundleProvider results = myObservationDao.search(params, mySrd); + assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(obsId); + myCaptureQueriesListener.logFirstSelectQueryForCurrentThread(); + String unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); + assertThat(unformattedSql).doesNotContain("HFJ_IDX_CMP_STRING_UNIQ"); + } + + @Test + public void testReplaceOneWithAnother() { + myStorageSettings.setAdvancedHSearchIndexing(false); + createUniqueGenderFamilyComboSp(); + + Patient pt1 = new Patient(); + pt1.setGender(Enumerations.AdministrativeGender.MALE); + pt1.getName().add(new HumanName().setFamily("Family1")); IIdType id1 = myPatientDao.create(pt1, mySrd).getId().toUnqualified(); assertNotNull(id1); @@ -1714,27 +1869,28 @@ public class FhirResourceDaoR4ComboUniqueParamTest extends BaseComboParamsR4Test pt1 = new Patient(); pt1.setId(id1); pt1.setGender(Enumerations.AdministrativeGender.FEMALE); - pt1.setBirthDateElement(new DateType("2011-01-01")); + pt1.getName().add(new HumanName().setFamily("Family1")); id1 = myPatientDao.update(pt1, mySrd).getId().toUnqualified(); assertNotNull(id1); assertEquals("2", id1.getVersionIdPart()); Patient pt2 = new Patient(); pt2.setGender(Enumerations.AdministrativeGender.MALE); - pt2.setBirthDateElement(new DateType("2011-01-01")); + pt2.getName().add(new HumanName().setFamily("Family1")); IIdType id2 = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless(); myMessages.clear(); SearchParameterMap params = new SearchParameterMap(); params.add("gender", new TokenParam("http://hl7.org/fhir/administrative-gender", "male")); - params.add("birthdate", new DateParam("2011-01-01")); + params.add("family", new StringParam("Family1")); IBundleProvider results = myPatientDao.search(params, mySrd); String searchId = results.getUuid(); assertThat(searchId).isNotBlank(); assertThat(toUnqualifiedVersionlessIdValues(results)).containsExactlyInAnyOrder(id2.getValue()); logCapturedMessages(); - assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: Patient?birthdate=2011-01-01&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); + assertThat(myMessages.toString()).contains("Using UNIQUE index(es) for query for search: " + + "Patient?family=Family1&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale"); myMessages.clear(); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java index 9eddb6767e8..ee0cce41228 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java @@ -413,7 +413,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { p.getMeta().addTag("http://system", "code", "diisplay"); p.addName().setFamily("FAM"); p.addIdentifier().setSystem("system").setValue("value"); - p.setBirthDateElement(new DateType("2020-01-01")); + p.setGender(Enumerations.AdministrativeGender.MALE); p.getManagingOrganization().setReferenceElement(orgId); Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); @@ -502,7 +502,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { p.getMeta().addTag("http://system", "code", "diisplay"); p.addName().setFamily("FAM"); p.addIdentifier().setSystem("system").setValue("value"); - p.setBirthDate(new Date()); + p.setGender(Enumerations.AdministrativeGender.MALE); p.getManagingOrganization().setReferenceElement(orgId); Long patientId = myPatientDao.create(p, mySrd).getId().getIdPartAsLong(); @@ -679,7 +679,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { p.getMeta().addTag("http://system", "code", "display"); p.addName().setFamily("FAM"); p.addIdentifier().setSystem("system").setValue("value"); - p.setBirthDate(new Date()); + p.setGender(Enumerations.AdministrativeGender.MALE); p.getManagingOrganization().setReference(org.getId()); input.addEntry() .setFullUrl(p.getId()) @@ -2541,14 +2541,14 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { public void testSearch_UniqueParam_SearchAllPartitions() { createUniqueComboSp(); - IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM")); + IIdType id = createPatient(withPartition(1), withGender("male"), withFamily("FAM")); addReadAllPartitions(); myCaptureQueriesListener.clear(); SearchParameterMap map = new SearchParameterMap(); map.add(Patient.SP_FAMILY, new StringParam("FAM")); - map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); + map.add(Patient.SP_GENDER, new TokenParam(null, "male")); map.setLoadSynchronous(true); IBundleProvider results = myPatientDao.search(map, mySrd); List ids = toUnqualifiedVersionlessIds(results); @@ -2558,7 +2558,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("Search SQL:\n{}", searchSql); assertThat(searchSql).doesNotContain("PARTITION_ID"); - assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?birthdate=2020-01-01&family=FAM'"); + assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?family=FAM&gender=male'"); } @@ -2566,13 +2566,13 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { public void testSearch_UniqueParam_SearchOnePartition() { createUniqueComboSp(); - IIdType id = createPatient(withPartition(1), withBirthdate("2020-01-01"), withFamily("FAM")); + IIdType id = createPatient(withPartition(1), withGender("male"), withFamily("FAM")); addReadPartition(1); myCaptureQueriesListener.clear(); SearchParameterMap map = new SearchParameterMap(); map.add(Patient.SP_FAMILY, new StringParam("FAM")); - map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); + map.add(Patient.SP_GENDER, new TokenParam(null, "male")); map.setLoadSynchronous(true); IBundleProvider results = myPatientDao.search(map, mySrd); List ids = toUnqualifiedVersionlessIds(results); @@ -2582,13 +2582,13 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("Search SQL:\n{}", searchSql); assertThat(searchSql).containsOnlyOnce( "PARTITION_ID = '1'"); - assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?birthdate=2020-01-01&family=FAM'"); + assertThat(searchSql).containsOnlyOnce("IDX_STRING = 'Patient?family=FAM&gender=male'"); // Same query, different partition addReadPartition(2); myCaptureQueriesListener.clear(); map = new SearchParameterMap(); - map.add(Patient.SP_BIRTHDATE, new DateParam("2020-01-01")); + map.add(Patient.SP_GENDER, new TokenParam(null, "male")); map.setLoadSynchronous(true); results = myPatientDao.search(map, mySrd); ids = toUnqualifiedVersionlessIds(results); @@ -2661,7 +2661,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { public void testSearch_RefParam_TargetPid_SearchOnePartition() { createUniqueComboSp(); - IIdType patientId = createPatient(withPartition(myPartitionId), withBirthdate("2020-01-01")); + IIdType patientId = createPatient(withPartition(myPartitionId), withGender("male")); IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId)); addReadPartition(myPartitionId); @@ -2698,7 +2698,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { public void testSearch_RefParam_TargetPid_SearchDefaultPartition() { createUniqueComboSp(); - IIdType patientId = createPatient(withPartition(null), withBirthdate("2020-01-01")); + IIdType patientId = createPatient(withPartition(null), withGender("male")); IIdType observationId = createObservation(withPartition(null), withSubject(patientId)); addReadDefaultPartition(); @@ -2735,7 +2735,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { public void testSearch_RefParam_TargetForcedId_SearchOnePartition() { createUniqueComboSp(); - IIdType patientId = createPatient(withPartition(myPartitionId), withId("ONE"), withBirthdate("2020-01-01")); + IIdType patientId = createPatient(withPartition(myPartitionId), withId("ONE"), withGender("male")); IIdType observationId = createObservation(withPartition(myPartitionId), withSubject(patientId)); addReadPartition(myPartitionId); @@ -2805,7 +2805,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { public void testSearch_RefParam_TargetForcedId_SearchDefaultPartition() { createUniqueComboSp(); - IIdType patientId = createPatient(withPartition(null), withId("ONE"), withBirthdate("2020-01-01")); + IIdType patientId = createPatient(withPartition(null), withId("ONE"), withGender("male")); IIdType observationId = createObservation(withPartition(null), withSubject(patientId)); addReadDefaultPartition(); diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/HapiEmbeddedDatabasesExtension.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/HapiEmbeddedDatabasesExtension.java index c2ca070788a..8619d3db0a8 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/HapiEmbeddedDatabasesExtension.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/HapiEmbeddedDatabasesExtension.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.embedded; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.fhir.jpa.util.DatabaseSupportUtil; import ca.uhn.fhir.test.utilities.docker.DockerRequiredCondition; import ca.uhn.fhir.util.VersionEnum; import org.junit.jupiter.api.extension.AfterAllCallback; @@ -54,7 +55,7 @@ public class HapiEmbeddedDatabasesExtension implements AfterAllCallback { myEmbeddedDatabases.add(new H2EmbeddedDatabase()); myEmbeddedDatabases.add(new PostgresEmbeddedDatabase()); myEmbeddedDatabases.add(new MsSqlEmbeddedDatabase()); - if (OracleCondition.canUseOracle()) { + if (DatabaseSupportUtil.canUseOracle()) { myEmbeddedDatabases.add(new OracleEmbeddedDatabase()); } else { String message = @@ -136,7 +137,7 @@ public class HapiEmbeddedDatabasesExtension implements AfterAllCallback { arguments.add(Arguments.of(DriverTypeEnum.POSTGRES_9_4)); arguments.add(Arguments.of(DriverTypeEnum.MSSQL_2012)); - if (OracleCondition.canUseOracle()) { + if (DatabaseSupportUtil.canUseOracle()) { arguments.add(Arguments.of(DriverTypeEnum.ORACLE_12C)); } diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/MsSqlEmbeddedDatabase.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/MsSqlEmbeddedDatabase.java index 7ab8ed648ea..cc498305293 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/MsSqlEmbeddedDatabase.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/MsSqlEmbeddedDatabase.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.embedded; import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; +import ca.uhn.fhir.jpa.util.DatabaseSupportUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.MSSQLServerContainer; @@ -43,9 +44,19 @@ public class MsSqlEmbeddedDatabase extends JpaEmbeddedDatabase { private final MSSQLServerContainer myContainer; public MsSqlEmbeddedDatabase() { - DockerImageName msSqlImage = DockerImageName.parse("mcr.microsoft.com/azure-sql-edge:latest") - .asCompatibleSubstituteFor("mcr.microsoft.com/mssql/server"); - myContainer = new MSSQLServerContainer(msSqlImage).acceptLicense(); + + // azure-sql-edge docker image does not support kernel 6.7+ + // as a result, mssql container fails to start most of the time + // mssql/server:2019 image support kernel 6.7+, so use it for amd64 architecture + // See: https://github.com/microsoft/mssql-docker/issues/868 + if (DatabaseSupportUtil.canUseMsSql2019()) { + myContainer = new MSSQLServerContainer("mcr.microsoft.com/mssql/server:2019-latest").acceptLicense(); + } else { + DockerImageName msSqlImage = DockerImageName.parse("mcr.microsoft.com/azure-sql-edge:latest") + .asCompatibleSubstituteFor("mcr.microsoft.com/mssql/server"); + myContainer = new MSSQLServerContainer(msSqlImage).acceptLicense(); + } + myContainer.start(); super.initialize( DriverTypeEnum.MSSQL_2012, diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/OracleCondition.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/OracleCondition.java index ddefa1a127c..6528e8f6bcc 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/OracleCondition.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/embedded/OracleCondition.java @@ -19,8 +19,7 @@ */ package ca.uhn.fhir.jpa.embedded; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.SystemUtils; +import ca.uhn.fhir.jpa.util.DatabaseSupportUtil; import org.junit.jupiter.api.extension.ConditionEvaluationResult; import org.junit.jupiter.api.extension.ExecutionCondition; import org.junit.jupiter.api.extension.ExtensionContext; @@ -33,25 +32,8 @@ public class OracleCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext theExtensionContext) { - return canUseOracle() + return DatabaseSupportUtil.canUseOracle() ? ConditionEvaluationResult.enabled(ENABLED_MSG) : ConditionEvaluationResult.disabled(DISABLED_MSG); } - - public static boolean canUseOracle() { - if (!isMac()) { - return true; - } - return isColimaConfigured(); - } - - private static boolean isMac() { - return SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX; - } - - private static boolean isColimaConfigured() { - return StringUtils.isNotBlank(System.getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE")) - && StringUtils.isNotBlank(System.getenv("DOCKER_HOST")) - && System.getenv("DOCKER_HOST").contains("colima"); - } } diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/util/DatabaseSupportUtil.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/util/DatabaseSupportUtil.java new file mode 100644 index 00000000000..fd1d362a95a --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/util/DatabaseSupportUtil.java @@ -0,0 +1,34 @@ +package ca.uhn.fhir.jpa.util; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.SystemUtils; + +public final class DatabaseSupportUtil { + + private DatabaseSupportUtil() {} + + public static boolean canUseMsSql2019() { + return isSupportAmd64Architecture(); + } + + public static boolean canUseOracle() { + return isSupportAmd64Architecture(); + } + + private static boolean isSupportAmd64Architecture() { + if (!isMac()) { + return true; + } + return isColimaConfigured(); + } + + private static boolean isMac() { + return SystemUtils.IS_OS_MAC || SystemUtils.IS_OS_MAC_OSX; + } + + private static boolean isColimaConfigured() { + return StringUtils.isNotBlank(System.getenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE")) + && StringUtils.isNotBlank(System.getenv("DOCKER_HOST")) + && System.getenv("DOCKER_HOST").contains("colima"); + } +} diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index 1f396dc4fdc..281604dba5a 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -79,25 +79,11 @@ org.simplejavamail simple-java-mail - - - - com.sun.activation - jakarta.activation - - com.icegreen greenmail compile - - - - com.sun.activation - jakarta.activation - - diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/IMailSvc.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/IMailSvc.java index 2a4d3ea6d56..62e67076204 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/IMailSvc.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/IMailSvc.java @@ -21,9 +21,9 @@ package ca.uhn.fhir.rest.server.mail; import jakarta.annotation.Nonnull; import org.simplejavamail.api.email.Email; -import org.simplejavamail.api.mailer.AsyncResponse; import java.util.List; +import java.util.function.Consumer; public interface IMailSvc { void sendMail(@Nonnull List theEmails); @@ -31,7 +31,5 @@ public interface IMailSvc { void sendMail(@Nonnull Email theEmail); void sendMail( - @Nonnull Email theEmail, - @Nonnull Runnable theOnSuccess, - @Nonnull AsyncResponse.ExceptionConsumer theErrorHandler); + @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer theErrorHandler); } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/MailSvc.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/MailSvc.java index 45299edda15..adc7111d4b9 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/MailSvc.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/mail/MailSvc.java @@ -20,12 +20,9 @@ package ca.uhn.fhir.rest.server.mail; import jakarta.annotation.Nonnull; -import org.apache.commons.lang3.Validate; import org.simplejavamail.MailException; import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Recipient; -import org.simplejavamail.api.mailer.AsyncResponse; -import org.simplejavamail.api.mailer.AsyncResponse.ExceptionConsumer; import org.simplejavamail.api.mailer.Mailer; import org.simplejavamail.api.mailer.config.TransportStrategy; import org.simplejavamail.mailer.MailerBuilder; @@ -33,6 +30,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; +import java.util.Objects; +import java.util.function.Consumer; import java.util.stream.Collectors; public class MailSvc implements IMailSvc { @@ -42,14 +41,14 @@ public class MailSvc implements IMailSvc { private final Mailer myMailer; public MailSvc(@Nonnull MailConfig theMailConfig) { - Validate.notNull(theMailConfig); + Objects.requireNonNull(theMailConfig); myMailConfig = theMailConfig; myMailer = makeMailer(myMailConfig); } @Override public void sendMail(@Nonnull List theEmails) { - Validate.notNull(theEmails); + Objects.requireNonNull(theEmails); theEmails.forEach(theEmail -> send(theEmail, new OnSuccess(theEmail), new ErrorHandler(theEmail))); } @@ -60,21 +59,23 @@ public class MailSvc implements IMailSvc { @Override public void sendMail( - @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) { + @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer theErrorHandler) { send(theEmail, theOnSuccess, theErrorHandler); } private void send( - @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull ExceptionConsumer theErrorHandler) { - Validate.notNull(theEmail); - Validate.notNull(theOnSuccess); - Validate.notNull(theErrorHandler); + @Nonnull Email theEmail, @Nonnull Runnable theOnSuccess, @Nonnull Consumer theErrorHandler) { + Objects.requireNonNull(theEmail); + Objects.requireNonNull(theOnSuccess); + Objects.requireNonNull(theErrorHandler); try { - final AsyncResponse asyncResponse = myMailer.sendMail(theEmail, true); - if (asyncResponse != null) { - asyncResponse.onSuccess(theOnSuccess); - asyncResponse.onException(theErrorHandler); - } + myMailer.sendMail(theEmail, true).whenComplete((result, ex) -> { + if (ex != null) { + theErrorHandler.accept(ex); + } else { + theOnSuccess.run(); + } + }); } catch (MailException e) { theErrorHandler.accept(e); } @@ -117,7 +118,7 @@ public class MailSvc implements IMailSvc { } } - private class ErrorHandler implements ExceptionConsumer { + private class ErrorHandler implements Consumer { private final Email myEmail; private ErrorHandler(@Nonnull Email theEmail) { @@ -125,7 +126,7 @@ public class MailSvc implements IMailSvc { } @Override - public void accept(Exception t) { + public void accept(Throwable t) { ourLog.error("Email not sent" + makeMessage(myEmail), t); } } diff --git a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java index 2c2fd6c2d52..a78b4f8c26c 100644 --- a/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java +++ b/hapi-fhir-server/src/test/java/ca/uhn/fhir/rest/server/mail/MailSvcIT.java @@ -4,6 +4,7 @@ import com.icegreen.greenmail.junit5.GreenMailExtension; import com.icegreen.greenmail.util.GreenMailUtil; import com.icegreen.greenmail.util.ServerSetupTest; import jakarta.annotation.Nonnull; +import jakarta.mail.internet.MimeMessage; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -11,7 +12,6 @@ import org.simplejavamail.MailException; import org.simplejavamail.api.email.Email; import org.simplejavamail.email.EmailBuilder; -import javax.mail.internet.MimeMessage; import java.util.Arrays; import java.util.List; @@ -86,13 +86,14 @@ public class MailSvcIT { @Test public void testSendMailWithInvalidToAddressExpectErrorHandler() { // setup - final Email email = withEmail("xyz"); + String invalidEmailAdress = "xyz"; + final Email email = withEmail(invalidEmailAdress); // execute fixture.sendMail(email, () -> fail("Should not execute on Success"), (e) -> { assertTrue(e instanceof MailException); - assertEquals("Invalid TO address: " + email, e.getMessage()); + assertEquals("Invalid TO address: " + invalidEmailAdress, e.getMessage()); }); // validate assertTrue(ourGreenMail.waitForIncomingEmail(1000, 0)); diff --git a/pom.xml b/pom.xml index e6f098ceece..0f562d546bd 100644 --- a/pom.xml +++ b/pom.xml @@ -869,6 +869,7 @@ delopst Primož Delopst + Better Zach Smith @@ -1160,27 +1161,38 @@ org.simplejavamail simple-java-mail - 6.6.1 + 8.11.2 - com.sun.activation - jakarta.activation-api + com.github.bbottema + jetbrains-runtime-annotations - com.sun.activation - jakarta.activation + jakarta.mail + jakarta.mail-api + + + + + jakarta.mail + jakarta.mail-api + 2.1.3 + + + com.icegreen + greenmail + 2.1.0-rc-1 + + + jakarta.mail + jakarta.mail-api - - com.icegreen - greenmail - 1.6.4 - com.icegreen greenmail-junit5 - 1.6.4 + 2.1.0-rc-1 compile