Compare commits

...

10 Commits

Author SHA1 Message Date
Luke deGruchy 3af223f277 Add unique error codes to Exceptions. 2024-09-27 15:28:04 -04:00
Luke deGruchy f8df77ced5 Merge remote-tracking branch 'origin/master' into ld-20240919-evaluate-measure-timezone 2024-09-27 14:11:45 -04:00
Luke deGruchy 9ce0bfe9e0 Rename class, test, and variables. Fix tests. Create a general CR Spring config class to be used by both DSTU3 and R4. 2024-09-27 13:21:15 -04:00
Luke deGruchy 991f3f6cee Add changelog. Ensure evaluated measure period search takes either both start and end as null or both neither. javadoc. Refactoring. More tests. 2024-09-27 11:32:58 -04:00
volodymyr-korzh fb7571185a
Composite unique search parameter with dateTime component does not reject resource duplication (#6318)
* Composite Unique Search Parameter with dateTime component does not reject resource duplication - failing tests

* Composite Unique Search Parameter with dateTime component does not reject resource duplication - implementation

* Composite Unique Search Parameter with dateTime component does not reject resource duplication - changelog and test fixes
2024-09-26 09:18:35 -06:00
Tadgh 919e2d2405
Attribution for javamail and API bump (#6319) 2024-09-26 14:56:49 +00:00
Thomas Papke 3f6d1eb29b
#5768 Upgrade to latest simple-java-mail (#6261) 2024-09-26 02:07:27 +00:00
Tadgh 377e44b6ca
attribution and pom change (#6309) 2024-09-25 20:38:22 +00:00
Martha Mitran 20d3e6bb25
Fix missing qualifier search for reference search parameters (#6306)
* Fix missing qualifier search for reference search parameter. Reuse tests and run them for both indexing enabled and disabled.

* Fix edge case where the search parameter has multiple paths. Add a test. Fix some of the compile warnings in a test class.

* Fix test setup.
2024-09-24 23:46:38 +00:00
Primož Delopst 4aea94ccb6
Fix forced ID migration for MSSQL (#6290)
Co-authored-by: Primož Delopst <primoz.delopst@better.care>
2024-09-24 14:43:17 +00:00
32 changed files with 1219 additions and 949 deletions

View File

@ -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!"

View File

@ -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!"

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 6305
title: "Previously, when having StorageSettings#getIndexMissingFields() == IndexEnabledEnum.DISABLED (default value)
and attempting to search with the missing qualifier against a resource type with multiple search parameters of type reference,
the returned results would be incorrect. This has been fixed."

View File

@ -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."

View File

@ -0,0 +1,6 @@
---
type: add
issue: 6267
title: "$evaluate-measure will now consider the 'Timezone' request header when computing period start and end with
the requested date/time offset instead of using the server timezone. If no 'Timezone' header is provided,
$evaluate-measure will default to UTC."

View File

@ -1972,7 +1972,7 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
* 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<JpaPid> {
* (e.g. <code>?date=gt2024-02-01</code>), etc.
*/
private boolean validateParamValuesAreValidForComboParam(
RequestDetails theRequest, @Nonnull SearchParameterMap theParams, List<String> theComboParamNames) {
RequestDetails theRequest,
@Nonnull SearchParameterMap theParams,
List<String> theComboParamNames,
RuntimeSearchParam theComboParam) {
boolean paramValuesAreValidForCombo = true;
List<List<IQueryParameterType>> paramOrValues = new ArrayList<>(theComboParamNames.size());
@ -2129,6 +2132,19 @@ public class SearchBuilder implements ISearchBuilder<JpaPid> {
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) {

View File

@ -800,14 +800,19 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
subquery.addCustomColumns(1);
subquery.addFromTable(getTable());
String resourceType = theParams.getResourceTablePredicateBuilder().getResourceType();
RuntimeSearchParam paramDefinition =
mySearchParamRegistry.getRuntimeSearchParam(resourceType, theParams.getParamName());
List<String> pathList = paramDefinition.getPathsSplitForResourceType(resourceType);
Condition subQueryCondition = ComboCondition.and(
BinaryCondition.equalTo(
getResourceIdColumn(),
theParams.getResourceTablePredicateBuilder().getResourceIdColumn()),
BinaryCondition.equalTo(
getResourceTypeColumn(),
generatePlaceholder(
theParams.getResourceTablePredicateBuilder().getResourceType())));
BinaryCondition.equalTo(getResourceTypeColumn(), generatePlaceholder(resourceType)),
ComboCondition.or(pathList.stream()
.map(path -> BinaryCondition.equalTo(getColumnSourcePath(), generatePlaceholder(path)))
.toArray(BinaryCondition[]::new)));
subquery.addCondition(subQueryCondition);

View File

@ -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;

View File

@ -70,6 +70,11 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<optional>true</optional>
</dependency>
<!-- test dependencies -->
<dependency>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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");

View File

@ -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();
}

View File

@ -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<IIdType> 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<IIdType> 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();

View File

@ -217,7 +217,7 @@ import static org.mockito.Mockito.when;
public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4Test.class);
private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw;
private CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor();
private final CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor();
@Autowired
private ISearchDao mySearchEntityDao;
@ -413,15 +413,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient pt1 = new Patient();
pt1.addName().setFamily("Elizabeth");
String pt1id = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue();
String pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue();
Patient pt2 = new Patient();
pt2.addName().setFamily("fghijk");
String pt2id = myPatientDao.create(pt2).getId().toUnqualifiedVersionless().getValue();
String pt2id = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless().getValue();
Patient pt3 = new Patient();
pt3.addName().setFamily("zzzzz");
myPatientDao.create(pt3).getId().toUnqualifiedVersionless().getValue();
myPatientDao.create(pt3, mySrd).getId().toUnqualifiedVersionless().getValue();
Bundle output = myClient
@ -450,7 +450,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient pt1 = new Patient();
pt1.addName().setFamily("Smith%");
String pt1id = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue();
String pt1id = myPatientDao.create(pt1, mySrd).getId().toUnqualifiedVersionless().getValue();
Bundle output = myClient
.search()
@ -463,7 +463,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient pt2 = new Patient();
pt2.addName().setFamily("Sm%ith");
String pt2id = myPatientDao.create(pt2).getId().toUnqualifiedVersionless().getValue();
String pt2id = myPatientDao.create(pt2, mySrd).getId().toUnqualifiedVersionless().getValue();
output = myClient
.search()
@ -740,7 +740,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient p = new Patient();
p.addName().setFamily("FAM").addGiven("GIV");
IIdType id = myPatientDao.create(p).getId();
IIdType id = myPatientDao.create(p, mySrd).getId();
myClient.read().resource("Patient").withId(id.toUnqualifiedVersionless()).execute();
@ -763,7 +763,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient p = new Patient();
p.addName().setFamily("FAM").addGiven("GIV");
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
myClient
.delete()
@ -1025,57 +1025,58 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
public void testCreateAndReadBackResourceWithContainedReferenceToContainer() {
myFhirContext.setParserErrorHandler(new StrictErrorHandler());
String input = "{\n" +
" \"resourceType\": \"Organization\",\n" +
" \"id\": \"1\",\n" +
" \"meta\": {\n" +
" \"tag\": [\n" +
" {\n" +
" \"system\": \"https://blah.org/deployment\",\n" +
" \"code\": \"e69414dd-b5c2-462d-bcfd-9d04d6b16596\",\n" +
" \"display\": \"DEPLOYMENT\"\n" +
" },\n" +
" {\n" +
" \"system\": \"https://blah.org/region\",\n" +
" \"code\": \"b47d7a5b-b159-4bed-a8f8-3258e6603adb\",\n" +
" \"display\": \"REGION\"\n" +
" },\n" +
" {\n" +
" \"system\": \"https://blah.org/provider\",\n" +
" \"code\": \"28c30004-0333-40cf-9e7f-3f9e080930bd\",\n" +
" \"display\": \"PROVIDER\"\n" +
" }\n" +
" ]\n" +
" },\n" +
" \"contained\": [\n" +
" {\n" +
" \"resourceType\": \"Location\",\n" +
" \"id\": \"2\",\n" +
" \"position\": {\n" +
" \"longitude\": 51.443238301454289,\n" +
" \"latitude\": 7.34196905697293\n" +
" },\n" +
" \"managingOrganization\": {\n" +
" \"reference\": \"#\"\n" +
" }\n" +
" }\n" +
" ],\n" +
" \"type\": [\n" +
" {\n" +
" \"coding\": [\n" +
" {\n" +
" \"system\": \"https://blah.org/fmc/OrganizationType\",\n" +
" \"code\": \"CLINIC\",\n" +
" \"display\": \"Clinic\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ],\n" +
" \"name\": \"testOrg\"\n" +
"}";
String input = """
{
"resourceType": "Organization",
"id": "1",
"meta": {
"tag": [
{
"system": "https://blah.org/deployment",
"code": "e69414dd-b5c2-462d-bcfd-9d04d6b16596",
"display": "DEPLOYMENT"
},
{
"system": "https://blah.org/region",
"code": "b47d7a5b-b159-4bed-a8f8-3258e6603adb",
"display": "REGION"
},
{
"system": "https://blah.org/provider",
"code": "28c30004-0333-40cf-9e7f-3f9e080930bd",
"display": "PROVIDER"
}
]
},
"contained": [
{
"resourceType": "Location",
"id": "2",
"position": {
"longitude": 51.443238301454289,
"latitude": 7.34196905697293
},
"managingOrganization": {
"reference": "#"
}
}
],
"type": [
{
"coding": [
{
"system": "https://blah.org/fmc/OrganizationType",
"code": "CLINIC",
"display": "Clinic"
}
]
}
],
"name": "testOrg"
}""";
Organization org = myFhirContext.newJsonParser().parseResource(Organization.class, input);
IIdType id = myOrganizationDao.create(org).getId();
IIdType id = myOrganizationDao.create(org, mySrd).getId();
org = myOrganizationDao.read(id);
String output = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(org);
@ -1095,9 +1096,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
List<IBaseResource> outcome = myClient.transaction().withResources(resources).prettyPrint().encodedXml().execute();
runInTransaction(() -> {
assertEquals(100, myResourceTableDao.count());
});
runInTransaction(() -> assertEquals(100, myResourceTableDao.count()));
Bundle found = myClient
.search()
@ -1306,7 +1305,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testCreateQuestionnaireResponseWithValidation() throws IOException {
public void testCreateQuestionnaireResponseWithValidation() {
CodeSystem cs = new CodeSystem();
cs.setUrl("http://cs");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
@ -1906,8 +1905,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
* Try it with a raw socket call. The Apache client won't let us use the unescaped "|" in the URL but we want to make sure that works too..
*/
Socket sock = new Socket();
sock.setSoTimeout(3000);
try {
try (sock) {
sock.setSoTimeout(3000);
sock.connect(new InetSocketAddress("localhost", myPort));
sock.getOutputStream().write(("DELETE /fhir/context/Patient?identifier=http://ghh.org/patient|" + methodName + " HTTP/1.1\n").getBytes(StandardCharsets.UTF_8));
sock.getOutputStream().write("Host: localhost\n".getBytes(StandardCharsets.UTF_8));
@ -1915,7 +1914,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
BufferedReader socketInput = new BufferedReader(new InputStreamReader(sock.getInputStream()));
// String response = "";
StringBuilder b = new StringBuilder();
char[] buf = new char[1000];
while (socketInput.read(buf) != -1) {
@ -1925,9 +1923,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
ourLog.debug("Resp: {}", resp);
} catch (SocketTimeoutException e) {
e.printStackTrace();
} finally {
sock.close();
ourLog.debug(e.getMessage(), e);
}
Thread.sleep(1000);
@ -2398,7 +2394,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(idValues).as(idValues.toString()).hasSize(10);
idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/_history?_at=gt" + InstantDt.withCurrentTime().getYear());
assertThat(idValues).hasSize(0);
assertThat(idValues).isEmpty();
}
@ -2427,7 +2423,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myMemoryCacheService.invalidateCaches(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID);
}
Bundle history = myClient.history().onInstance(id.getValue()).andReturnBundle(Bundle.class).execute();
Bundle history = myClient.history().onInstance(id.getValue()).returnBundle(Bundle.class).execute();
assertEquals(1, history.getEntry().size());
BundleEntryComponent historyEntry0 = history.getEntry().get(0);
// validate entry.fullUrl
@ -2476,7 +2472,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myMemoryCacheService.invalidateCaches(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID);
}
Bundle history = myClient.history().onInstance(id.getValue()).andReturnBundle(Bundle.class).execute();
Bundle history = myClient.history().onInstance(id.getValue()).returnBundle(Bundle.class).execute();
assertEquals(1, history.getEntry().size());
BundleEntryComponent historyEntry0 = history.getEntry().get(0);
// validate entry.fullUrl
@ -2508,7 +2504,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
ourLog.info("Res ID: {}", id);
Bundle history = myClient.history().onInstance(id.getValue()).andReturnBundle(Bundle.class).prettyPrint().summaryMode(SummaryEnum.DATA).execute();
Bundle history = myClient.history().onInstance(id.getValue()).returnBundle(Bundle.class).prettyPrint().summaryMode(SummaryEnum.DATA).execute();
assertThat(history.getEntry()).hasSize(3);
assertEquals(id.withVersion("3").getValue(), history.getEntry().get(0).getResource().getId());
assertThat(((Patient) history.getEntry().get(0).getResource()).getName()).hasSize(1);
@ -2746,7 +2742,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
int total = 20;
Organization org = new Organization();
org.setName("ORG");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
Coding tagCode = new Coding();
tagCode.setCode("test");
@ -2757,7 +2753,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
.addTag(tagCode);
t.setStatus(Task.TaskStatus.REQUESTED);
t.getOwner().setReference(orgId.getValue());
myTaskDao.create(t);
myTaskDao.create(t, mySrd);
}
HashSet<String> ids = new HashSet<>();
@ -2835,12 +2831,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
if (orgCount > 0) {
Organization org = new Organization();
org.setName("ORG");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
orgCount--;
t.getOwner().setReference(orgId.getValue());
}
myTaskDao.create(t);
myTaskDao.create(t, mySrd);
}
}
@ -2909,12 +2905,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
if (orgCount > 0) {
Organization org = new Organization();
org.setName("ORG");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
orgCount--;
t.getOwner().setReference(orgId.getValue());
}
myTaskDao.create(t);
myTaskDao.create(t, mySrd);
}
}
@ -2961,13 +2957,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
public void testIncludeCountDoesntIncludeIncludes() {
Organization org = new Organization();
org.setName("ORG");
IIdType orgId = myOrganizationDao.create(org).getId().toUnqualifiedVersionless();
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
for (int i = 0; i < 10; i++) {
Patient pt = new Patient();
pt.getManagingOrganization().setReference(orgId.getValue());
pt.addName().setFamily("FAM" + i);
myPatientDao.create(pt);
myPatientDao.create(pt, mySrd);
}
Bundle bundle = myClient
@ -3168,7 +3164,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient newPt = myClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("2", newPt.getIdElement().getVersionIdPart());
assertEquals(false, newPt.getActive());
assertFalse(newPt.getActive());
}
@Test
@ -3196,7 +3192,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient newPt = myClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("1", newPt.getIdElement().getVersionIdPart());
assertEquals(true, newPt.getActive());
assertTrue(newPt.getActive());
}
@Test
@ -3226,7 +3222,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient newPt = myClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("2", newPt.getIdElement().getVersionIdPart());
assertEquals(false, newPt.getActive());
assertFalse(newPt.getActive());
}
@Test
@ -3255,7 +3251,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Patient newPt = myClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute();
assertEquals("2", newPt.getIdElement().getVersionIdPart());
assertEquals(false, newPt.getActive());
assertFalse(newPt.getActive());
}
@Test
@ -3323,12 +3319,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
{
Bundle returned = myClient.search().forResource(Patient.class).encodedXml().returnBundle(Bundle.class).execute();
assertThat(returned.getEntry().size()).isGreaterThan(1);
assertThat(returned.getEntry()).hasSizeGreaterThan(1);
assertEquals(BundleType.SEARCHSET, returned.getType());
}
{
Bundle returned = myClient.search().forResource(Patient.class).encodedJson().returnBundle(Bundle.class).execute();
assertThat(returned.getEntry().size()).isGreaterThan(1);
assertThat(returned.getEntry()).hasSizeGreaterThan(1);
}
}
@ -3350,7 +3346,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
});
Bundle bundle = myClient.history().onServer().andReturnBundle(Bundle.class).execute();
Bundle bundle = myClient.history().onServer().returnBundle(Bundle.class).execute();
assertEquals(1, bundle.getTotal());
assertThat(bundle.getEntry()).hasSize(1);
assertEquals(id2.getIdPart(), bundle.getEntry().get(0).getResource().getIdElement().getIdPart());
@ -3507,15 +3503,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(text).doesNotContain("\"B\"");
assertThat(text).doesNotContain("\"B1\"");
}
// HttpGet read = new HttpGet(ourServerBase + "/Observation?patient=P5000000302&_sort:desc=code&code:in=http://fkcfhir.org/fhir/vs/ccdacapddialysisorder");
// try (CloseableHttpResponse response = ourHttpClient.execute(read)) {
// String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
// ourLog.info(text);
// assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode());
// assertThat(text).doesNotContain("\"text\",\"type\"");
// }
}
@Test
@ -3873,9 +3860,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
p.addName().setFamily(methodName + "1");
IIdType pid1 = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
Thread.sleep(10);
long time1 = System.currentTimeMillis();
Thread.sleep(10);
Patient p2 = new Patient();
p2.addName().setFamily(methodName + "2");
@ -4064,9 +4049,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
public void testSearchLastUpdatedParamRp() throws InterruptedException {
String methodName = "testSearchLastUpdatedParamRp";
int sleep = 100;
Thread.sleep(sleep);
DateTimeType beforeAny = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI);
IIdType id1a;
{
@ -4083,9 +4065,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
id1b = myClient.create().resource(patient).execute().getId().toUnqualifiedVersionless();
}
Thread.sleep(1100);
DateTimeType beforeR2 = new DateTimeType(new Date(), TemporalPrecisionEnum.MILLI);
Thread.sleep(1100);
IIdType id2;
{
@ -4249,13 +4229,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, resp);
matches = bundle.getEntry().size();
assertThat(matches).isGreaterThan(0);
assertThat(matches).isPositive();
}
@Test
public void testSearchReturnsSearchDate() throws Exception {
Date before = new Date();
Thread.sleep(1);
//@formatter:off
Bundle found = myClient
@ -4266,7 +4245,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
.execute();
//@formatter:on
Thread.sleep(1);
Date after = new Date();
InstantType updated = found.getMeta().getLastUpdatedElement();
@ -4300,7 +4278,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4313,7 +4291,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4326,7 +4304,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4339,24 +4317,24 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
// > 1m
String uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt1|http://unitsofmeasure.org|m");
ourLog.info("uri = " + uri);
ourLog.info("uri = {}", uri);
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids).hasSize(3);
//>= 100cm
uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt100|http://unitsofmeasure.org|cm");
ourLog.info("uri = " + uri);
ourLog.info("uri = {}", uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids).hasSize(3);
//>= 10dm
uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt10|http://unitsofmeasure.org|dm");
ourLog.info("uri = " + uri);
ourLog.info("uri = {}", uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids).hasSize(3);
}
@ -4381,7 +4359,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4392,7 +4370,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4403,7 +4381,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4414,7 +4392,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
String uri;
@ -4451,7 +4429,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4462,7 +4440,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -4474,7 +4452,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
myCaptureQueriesListener.clear();
@ -4490,8 +4468,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
//-- check use normalized quantity table to search
String searchSql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, true);
assertThat(searchSql).doesNotContain("HFJ_SPIDX_QUANTITY t0");
assertThat(searchSql).contains("HFJ_SPIDX_QUANTITY_NRML");
assertThat(searchSql).doesNotContain("HFJ_SPIDX_QUANTITY t0").contains("HFJ_SPIDX_QUANTITY_NRML");
}
@Test
@ -5044,7 +5021,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -5056,7 +5033,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -5068,7 +5045,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -5080,7 +5057,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid4 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
String uri = myServerBase + "/Observation?_sort=code-value-quantity";
@ -5092,7 +5069,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
found = myFhirContext.newXmlParser().parseResource(Bundle.class, output);
}
ourLog.debug("Bundle: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(found));
ourLog.debug("Bundle: {}\n", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(found));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(found.getEntry()).hasSize(4);
@ -5129,7 +5106,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -5145,7 +5122,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid2 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -5161,7 +5138,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
oid3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -5176,7 +5153,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
comp.setValue(new Quantity().setValue(250));
oid4 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
String uri = myServerBase + "/Observation?_sort=combo-code-value-quantity";
@ -5188,7 +5165,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
found = myFhirContext.newXmlParser().parseResource(Bundle.class, output);
}
ourLog.debug("Bundle: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(found));
ourLog.debug("Bundle: {}\n", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(found));
List<IIdType> list = toUnqualifiedVersionlessIds(found);
assertThat(found.getEntry()).hasSize(4);
@ -5264,9 +5241,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
List<IIdType> list = toUnqualifiedVersionlessIds(found);
ourLog.info(methodName + " found: " + list.toString() + " - Wanted " + orgMissing + " but not " + orgNotMissing);
assertThat(list).doesNotContain(orgNotMissing);
assertThat(list).doesNotContain(deletedIdMissingTrue);
assertThat(list).contains(orgMissing);
assertThat(list).doesNotContain(orgNotMissing).doesNotContain(deletedIdMissingTrue).contains(orgMissing);
}
@Test
@ -5927,7 +5902,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
}
Date before = new Date();
Thread.sleep(100);
pt = new Patient();
pt.setId(id.getIdPart());
@ -6450,7 +6424,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(125.12)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
IIdType opid1 = myObservationDao.create(obs, mySrd).getId();
@ -6463,7 +6437,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
obs.setValue(new Quantity().setValueElement(new DecimalType(24.12)).setUnit("CM").setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm"));
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
myObservationDao.update(obs, mySrd);
}
@ -6479,7 +6453,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -6492,7 +6466,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
{
@ -6505,25 +6479,25 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myObservationDao.create(obs, mySrd);
ourLog.debug("Observation: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
ourLog.debug("Observation: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
// > 1m
String uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt1|http://unitsofmeasure.org|m");
ourLog.info("uri = " + uri);
ourLog.info("uri = {}", uri);
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids).hasSize(2);
//>= 100cm
uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt100|http://unitsofmeasure.org|cm");
ourLog.info("uri = " + uri);
ourLog.info("uri = {}", uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids).hasSize(2);
//>= 10dm
uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt10|http://unitsofmeasure.org|dm");
ourLog.info("uri = " + uri);
ourLog.info("uri = {}", uri);
ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertThat(ids).hasSize(2);
}
@ -6540,7 +6514,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
patient.setBirthDateElement(new DateType("2073"));
pid0 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
ourLog.debug("Patient: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
ourLog.debug("Patient: {}\n", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
ourLog.info("pid0 " + pid0);
}
@ -6553,7 +6527,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, resp);
ourLog.debug("Patient: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
ourLog.debug("Patient: {}\n", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
}
uri = myServerBase + "/Patient?_total=accurate&birthdate=gt2072-01-01";
@ -6564,7 +6538,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, resp);
ourLog.debug("Patient: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
ourLog.debug("Patient: {}\n", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
}
}
@ -6995,9 +6969,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
&& theInput.IsEnforceRefOnType
&& theInput.IsEnforceRefOnWrite).isFalse();
} catch (InvalidRequestException ex) {
assertThat(ex.getMessage().contains(
"Invalid resource reference"
)).as(ex.getMessage()).isTrue();
assertThat(ex.getMessage()).as(ex.getMessage()).contains("Invalid resource reference");
} finally {
myStorageSettings.setEnforceReferentialIntegrityOnWrite(isEnforceRefOnWrite);
myStorageSettings.setEnforceReferenceTargetTypes(isEnforceRefTargetTypes);
@ -7331,9 +7303,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
patient.setBirthDate(cal.getTime());
}
return patient;
}, (isMissing) -> {
return doSearch(Patient.class, Patient.BIRTHDATE.isMissing(isMissing));
});
}, (isMissing) -> doSearch(Patient.class, Patient.BIRTHDATE.isMissing(isMissing)));
}
@ParameterizedTest
@ -7346,9 +7316,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
patient.setGender(AdministrativeGender.FEMALE);
}
return patient;
}, isMissing -> {
return doSearch(Patient.class, Patient.GENDER.isMissing(isMissing));
});
}, isMissing -> doSearch(Patient.class, Patient.GENDER.isMissing(isMissing)));
}
@ParameterizedTest
@ -7364,9 +7332,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
patient.setGeneralPractitioner(Collections.singletonList(new Reference(practitionerId)));
}
return patient;
}, isMissing -> {
return doSearch(Patient.class, Patient.GENERAL_PRACTITIONER.isMissing(isMissing));
});
}, isMissing -> doSearch(Patient.class, Patient.GENERAL_PRACTITIONER.isMissing(isMissing)));
}
@ParameterizedTest
@ -7409,9 +7375,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
sp.setUrl("http://example.com");
}
return sp;
}, isMissing -> {
return doSearch(SearchParameter.class, SearchParameter.URL.isMissing(isMissing));
});
}, isMissing -> doSearch(SearchParameter.class, SearchParameter.URL.isMissing(isMissing)));
}
@ParameterizedTest
@ -7424,9 +7388,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
obs.setValue(new Quantity(3));
}
return obs;
}, isMissing -> {
return doSearch(Observation.class, Observation.VALUE_QUANTITY.isMissing(isMissing));
});
}, isMissing -> doSearch(Observation.class, Observation.VALUE_QUANTITY.isMissing(isMissing)));
}
@ParameterizedTest
@ -7457,7 +7419,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
Y doTask(X theInput);
}
private static class MissingSearchTestParameters {
public static class MissingSearchTestParameters {
/**
* The setting for IndexMissingFields
*/

View File

@ -79,25 +79,11 @@
<dependency>
<groupId>org.simplejavamail</groupId>
<artifactId>simple-java-mail</artifactId>
<!-- Excluded in favor of jakarta.activation:jakarta.activation-api -->
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<scope>compile</scope>
<!-- Excluded in favor of jakarta.activation:jakarta.activation-api -->
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

View File

@ -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<Email> 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<Throwable> theErrorHandler);
}

View File

@ -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<Email> 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<Throwable> 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<Throwable> 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<Throwable> {
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);
}
}

View File

@ -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));

View File

@ -89,7 +89,7 @@ public class ForceIdMigrationFixTask extends BaseTask {
" set fhir_id = coalesce( "
+
// case 5.
" trim(fhir_id), "
trimFhirId()
+
// case 3
" (select f.forced_id from hfj_forced_id f where f.resource_pid = res_id), "
@ -109,6 +109,22 @@ public class ForceIdMigrationFixTask extends BaseTask {
}
}
private String trimFhirId() {
switch (getDriverType()) {
case MSSQL_2012:
return " LTRIM(RTRIM(fhir_id)), ";
case H2_EMBEDDED:
case DERBY_EMBEDDED:
case MARIADB_10_1:
case MYSQL_5_7:
case POSTGRES_9_4:
case ORACLE_12C:
case COCKROACHDB_21_1:
default:
return " trim(fhir_id), ";
}
}
private String getWhereClauseByDBType() {
switch (getDriverType()) {
case MSSQL_2012:

View File

@ -0,0 +1,35 @@
/*-
* #%L
* HAPI FHIR - Clinical Reasoning
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.cr.config;
import ca.uhn.fhir.cr.r4.measure.MeasureReportPeriodRequestValidatorAndConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.ZoneOffset;
@Configuration
public class CrBaseConfig {
@Bean
MeasureReportPeriodRequestValidatorAndConverter measureReportPeriodService() {
return new MeasureReportPeriodRequestValidatorAndConverter(ZoneOffset.UTC);
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.cr.config.dstu3;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.cr.common.IRepositoryFactory;
import ca.uhn.fhir.cr.config.CrBaseConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.cr.config.RepositoryConfig;
@ -39,7 +40,7 @@ import java.util.Arrays;
import java.util.Map;
@Configuration
@Import({RepositoryConfig.class})
@Import({RepositoryConfig.class, CrBaseConfig.class})
public class CrDstu3Config {
@Bean

View File

@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.cr.common.IRepositoryFactory;
import ca.uhn.fhir.cr.common.RepositoryFactoryForRepositoryInterface;
import ca.uhn.fhir.cr.config.CrBaseConfig;
import ca.uhn.fhir.cr.config.ProviderLoader;
import ca.uhn.fhir.cr.config.ProviderSelector;
import ca.uhn.fhir.cr.config.RepositoryConfig;
@ -39,7 +40,7 @@ import ca.uhn.fhir.cr.r4.measure.CareGapsOperationProvider;
import ca.uhn.fhir.cr.r4.measure.CollectDataOperationProvider;
import ca.uhn.fhir.cr.r4.measure.DataRequirementsOperationProvider;
import ca.uhn.fhir.cr.r4.measure.MeasureOperationsProvider;
import ca.uhn.fhir.cr.r4.measure.MeasureReportPeriodRequestProcessingService;
import ca.uhn.fhir.cr.r4.measure.MeasureReportPeriodRequestValidatorAndConverter;
import ca.uhn.fhir.cr.r4.measure.SubmitDataProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
@ -58,13 +59,12 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.Executor;
@Configuration
@Import({RepositoryConfig.class})
@Import({RepositoryConfig.class, CrBaseConfig.class})
public class CrR4Config {
@Bean
@ -149,9 +149,9 @@ public class CrR4Config {
@Bean
MeasureOperationsProvider r4MeasureOperationsProvider(
IMeasureServiceFactory theR4MeasureServiceFactory,
MeasureReportPeriodRequestProcessingService theMeasureReportPeriodRequestProcessingService) {
MeasureReportPeriodRequestValidatorAndConverter theMeasureReportPeriodRequestValidatorAndConverter) {
return new MeasureOperationsProvider(
theR4MeasureServiceFactory, theMeasureReportPeriodRequestProcessingService);
theR4MeasureServiceFactory, theMeasureReportPeriodRequestValidatorAndConverter);
}
@Bean
@ -173,9 +173,4 @@ public class CrR4Config {
return new ProviderLoader(theRestfulServer, theApplicationContext, selector);
}
@Bean
MeasureReportPeriodRequestProcessingService measureReportPeriodService() {
return new MeasureReportPeriodRequestProcessingService(ZoneOffset.UTC);
}
}

View File

@ -37,13 +37,13 @@ import org.opencds.cqf.fhir.utility.monad.Eithers;
public class MeasureOperationsProvider {
private final IMeasureServiceFactory myR4MeasureServiceFactory;
private final MeasureReportPeriodRequestProcessingService myMeasureReportPeriodRequestProcessingService;
private final MeasureReportPeriodRequestValidatorAndConverter myMeasureReportPeriodRequestProcessingService;
public MeasureOperationsProvider(
IMeasureServiceFactory theR4MeasureServiceFactory,
MeasureReportPeriodRequestProcessingService theMeasureReportPeriodRequestProcessingService) {
MeasureReportPeriodRequestValidatorAndConverter theMeasureReportPeriodRequestValidatorAndConverter) {
myR4MeasureServiceFactory = theR4MeasureServiceFactory;
myMeasureReportPeriodRequestProcessingService = theMeasureReportPeriodRequestProcessingService;
myMeasureReportPeriodRequestProcessingService = theMeasureReportPeriodRequestValidatorAndConverter;
}
/**

View File

@ -19,13 +19,17 @@
*/
package ca.uhn.fhir.cr.r4.measure;
import com.google.common.base.Preconditions;
import jakarta.annotation.Nullable;
import java.util.Objects;
import java.util.StringJoiner;
// LUKETODO: javadoc
// TODO: LD: make this a record when hapi-fhir supports JDK 17
// TODO: LD: consider making this a record when hapi-fhir supports JDK 17
/**
* Simple tuple containing post-conversion String versions of period start and end.
* Either both must be null or neither.
*/
public class MeasurePeriodForEvaluation {
@Nullable
private final String myPeriodStart;
@ -34,6 +38,10 @@ public class MeasurePeriodForEvaluation {
private final String myPeriodEnd;
public MeasurePeriodForEvaluation(@Nullable String thePeriodStart, @Nullable String thePeriodEnd) {
// Either both are null or neither
Preconditions.checkArgument(
(thePeriodStart != null && thePeriodEnd != null) || (thePeriodStart == null && thePeriodEnd == null));
myPeriodStart = thePeriodStart;
myPeriodEnd = thePeriodEnd;
}
@ -49,14 +57,14 @@ public class MeasurePeriodForEvaluation {
}
@Override
public boolean equals(Object theO) {
if (this == theO) {
public boolean equals(Object theOther) {
if (this == theOther) {
return true;
}
if (theO == null || getClass() != theO.getClass()) {
if (theOther == null || getClass() != theOther.getClass()) {
return false;
}
MeasurePeriodForEvaluation that = (MeasurePeriodForEvaluation) theO;
MeasurePeriodForEvaluation that = (MeasurePeriodForEvaluation) theOther;
return Objects.equals(myPeriodStart, that.myPeriodStart) && Objects.equals(myPeriodEnd, that.myPeriodEnd);
}

View File

@ -1,145 +0,0 @@
/*-
* #%L
* HAPI FHIR - Clinical Reasoning
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.cr.r4.measure;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.DateUtils;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.Optional;
// LUKETODO: changelog
// LUKETODO: javadoc
public class MeasureReportPeriodRequestProcessingService {
private static final Logger ourLog = LoggerFactory.getLogger(MeasureReportPeriodRequestProcessingService.class);
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_INPUT = DateTimeFormatter.ofPattern("yyyy");
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_INPUT = DateTimeFormatter.ofPattern("yyyy-MM");
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_DD_INPUT = DateTimeFormatter.ISO_DATE;
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_INPUT =
DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// LUKETODO: what's the winning format here?
// java.time.format.DateTimeParseException: Text '2023-01-01T00:00:00.0-0700'
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_Z_OUTPUT =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SXXX");
// DateTimeFormatter.ISO_OFFSET_DATE_TIME;
// DateTimeFormatter.ISO_ZONED_DATE_TIME;
private static final Map<Integer, DateTimeFormatter> VALID_DATE_TIME_FORMATTERS_BY_FORMAT_LENGTH = Map.of(
4, DATE_TIME_FORMATTER_YYYY_INPUT,
7, DATE_TIME_FORMATTER_YYYY_MM_INPUT,
10, DATE_TIME_FORMATTER_YYYY_MM_DD_INPUT,
19, DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_INPUT);
private final ZoneId myFallbackTimezone;
public MeasureReportPeriodRequestProcessingService(ZoneId theFallbackTimezone) {
myFallbackTimezone = theFallbackTimezone;
}
public MeasurePeriodForEvaluation validateAndProcessTimezone(
RequestDetails theRequestDetails, String thePeriodStart, String thePeriodEnd) {
final ZoneId clientTimezone = getClientTimezoneOrInvalidRequest(theRequestDetails);
return validateInputDates(thePeriodStart, thePeriodEnd, clientTimezone);
}
private MeasurePeriodForEvaluation validateInputDates(
String thePeriodStart, String thePeriodEnd, ZoneId theZoneId) {
if (Strings.isBlank(thePeriodStart) || Strings.isBlank(thePeriodEnd)) {
return new MeasurePeriodForEvaluation(null, null);
}
if (thePeriodStart.length() != thePeriodEnd.length()) {
throw new InvalidRequestException(String.format(
"Period start: %s and end: %s are not the same date/time formats", thePeriodStart, thePeriodEnd));
}
final DateTimeFormatter dateTimeFormatterStart =
VALID_DATE_TIME_FORMATTERS_BY_FORMAT_LENGTH.get(thePeriodStart.length());
if (dateTimeFormatterStart == null) {
throw new InvalidRequestException(String.format(
"Unsupported Date/Time format for period start: %s or end: %s", thePeriodStart, thePeriodEnd));
}
final Optional<LocalDateTime> optLocalDateTimeStart = DateUtils.parseDateTimeStringIfValid(
thePeriodStart, dateTimeFormatterStart)
.flatMap(DateUtils::extractLocalDateTimeForRangeStartOrEmpty);
final Optional<LocalDateTime> optLocalDateTimeEnd = DateUtils.parseDateTimeStringIfValid(
thePeriodEnd, dateTimeFormatterStart)
.flatMap(DateUtils::extractLocalDateTimeForRangeEndOrEmpty);
if (optLocalDateTimeStart.isEmpty() || optLocalDateTimeEnd.isEmpty()) {
throw new InvalidRequestException("Either start or end period have an unsupported format");
}
final LocalDateTime localDateTimeStart = optLocalDateTimeStart.get();
final LocalDateTime localDateTimeEnd = optLocalDateTimeEnd.get();
if (localDateTimeStart.isEqual(localDateTimeEnd)) {
throw new InvalidRequestException(
String.format("Start date: %s is the same as end date: %s", thePeriodStart, thePeriodEnd));
}
if (localDateTimeStart.isAfter(localDateTimeEnd)) {
throw new InvalidRequestException(String.format(
"Invalid Interval - the ending boundary: %s must be greater than or equal to the starting boundary: %s",
thePeriodEnd, thePeriodStart));
}
final String periodStartFormatted = formatWithTimezone(localDateTimeStart, theZoneId);
final String periodEndFormatted = formatWithTimezone(localDateTimeEnd, theZoneId);
ourLog.info("6560: NEW START: {} formatted: {}, NEW END: {}, formatted: {}", localDateTimeStart, periodStartFormatted, localDateTimeEnd, periodEndFormatted);
return new MeasurePeriodForEvaluation(
periodStartFormatted, periodEndFormatted);
}
private String formatWithTimezone(LocalDateTime theLocalDateTime, ZoneId theZoneId) {
return theLocalDateTime.atZone(theZoneId).format(DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_Z_OUTPUT);
}
private ZoneId getClientTimezoneOrInvalidRequest(RequestDetails theRequestDetails) {
final String clientTimezoneString = theRequestDetails.getHeader(Constants.HEADER_CLIENT_TIMEZONE);
if (Strings.isNotBlank(clientTimezoneString)) {
try {
return ZoneId.of(clientTimezoneString);
} catch (Exception exception) {
throw new InvalidRequestException("Invalid value for Timezone header: " + clientTimezoneString);
}
}
return myFallbackTimezone;
}
}

View File

@ -0,0 +1,186 @@
/*-
* #%L
* HAPI FHIR - Clinical Reasoning
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.cr.r4.measure;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.DateUtils;
import jakarta.annotation.Nonnull;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
/**
* Used immediately after receiving a REST call by $evaluate-measure and any potential variants to validate and convert
* period start and end inputs to timezones with offsets. The offset is determined from the request header a value for "Timezone".
* <p/>
* This class takes a fallback timezone that's used in case the request header does not contain a value for "Timezone".
* <p/>
* The output date/time format is the following to be compatible with clinical-reasoning: yyyy-MM-dd'T'HH:mm:ss.SXXX
* ex: 2023-01-01T00:00:00.0-07:00
* <p/>
* Currently, these are the date/time formats supported:
* <ol>
* <li>yyyy</li>
* <li>yyyy-MM</li>
* <li>yyyy-MM-dd</li>
* <li>yyyy-MM-ddTHH:mm:ss</li>
* </ol>
*/
public class MeasureReportPeriodRequestValidatorAndConverter {
private static final Logger ourLog = LoggerFactory.getLogger(MeasureReportPeriodRequestValidatorAndConverter.class);
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_INPUT = DateTimeFormatter.ofPattern("yyyy");
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_INPUT = DateTimeFormatter.ofPattern("yyyy-MM");
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_DD_INPUT = DateTimeFormatter.ISO_DATE;
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_INPUT =
DateTimeFormatter.ISO_LOCAL_DATE_TIME;
// This specific format is needed because otherwise clinical-reasoning will error out when parsing the output
// ex: 2023-01-01T00:00:00.0-07:00
private static final DateTimeFormatter DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_Z_OUTPUT =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SXXX");
private static final Map<Integer, DateTimeFormatter> VALID_DATE_TIME_FORMATTERS_BY_FORMAT_LENGTH = Map.of(
4, DATE_TIME_FORMATTER_YYYY_INPUT,
7, DATE_TIME_FORMATTER_YYYY_MM_INPUT,
10, DATE_TIME_FORMATTER_YYYY_MM_DD_INPUT,
19, DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_INPUT);
private final ZoneId myFallbackTimezone;
public MeasureReportPeriodRequestValidatorAndConverter(ZoneId theFallbackTimezone) {
myFallbackTimezone = theFallbackTimezone;
}
public MeasurePeriodForEvaluation validateAndProcessTimezone(
RequestDetails theRequestDetails, String thePeriodStart, String thePeriodEnd) {
final ZoneId clientTimezone = getClientTimezoneOrInvalidRequest(theRequestDetails);
return validateInputDates(thePeriodStart, thePeriodEnd, clientTimezone);
}
private MeasurePeriodForEvaluation validateInputDates(
String thePeriodStart, String thePeriodEnd, ZoneId theZoneId) {
if ((Strings.isBlank(thePeriodStart) && !Strings.isBlank(thePeriodEnd))
|| (!Strings.isBlank(thePeriodStart) && Strings.isBlank(thePeriodEnd))) {
throw new InvalidRequestException(String.format(
"%sEither both period start: [%s] and end: [%s] must be empty or non empty",
Msg.code(2554), thePeriodStart, thePeriodEnd));
}
if (Strings.isBlank(thePeriodStart) && Strings.isBlank(thePeriodEnd)) {
return new MeasurePeriodForEvaluation(null, null);
}
if (thePeriodStart.length() != thePeriodEnd.length()) {
throw new InvalidRequestException(String.format(
"%sPeriod start: %s and end: %s are not the same date/time formats",
Msg.code(2555), thePeriodStart, thePeriodEnd));
}
final DateTimeFormatter dateTimeFormatterStart = validateAndGetDateTimeFormat(thePeriodStart, thePeriodEnd);
final LocalDateTime localDateTimeStart = validateAndGetLocalDateTime(
thePeriodStart, dateTimeFormatterStart, DateUtils::extractLocalDateTimeForRangeStartOrEmpty, true);
final LocalDateTime localDateTimeEnd = validateAndGetLocalDateTime(
thePeriodEnd, dateTimeFormatterStart, DateUtils::extractLocalDateTimeForRangeEndOrEmpty, false);
validateParsedPeriodStartAndEnd(thePeriodStart, thePeriodEnd, localDateTimeStart, localDateTimeEnd);
final String periodStartFormatted = formatWithTimezone(localDateTimeStart, theZoneId);
final String periodEndFormatted = formatWithTimezone(localDateTimeEnd, theZoneId);
return new MeasurePeriodForEvaluation(periodStartFormatted, periodEndFormatted);
}
private static void validateParsedPeriodStartAndEnd(
String theThePeriodStart,
String theThePeriodEnd,
LocalDateTime theLocalDateTimeStart,
LocalDateTime theLocalDateTimeEnd) {
// This should probably never happen
if (theLocalDateTimeStart.isEqual(theLocalDateTimeEnd)) {
throw new InvalidRequestException(String.format(
"%sStart date: %s is the same as end date: %s",
Msg.code(2556), theThePeriodStart, theThePeriodEnd));
}
if (theLocalDateTimeStart.isAfter(theLocalDateTimeEnd)) {
throw new InvalidRequestException(String.format(
"%sInvalid Interval - the ending boundary: %s must be greater than or equal to the starting boundary: %s",
Msg.code(2557), theThePeriodEnd, theThePeriodStart));
}
}
private LocalDateTime validateAndGetLocalDateTime(
String thePeriod,
DateTimeFormatter theDateTimeFormatter,
Function<TemporalAccessor, Optional<LocalDateTime>> theTemporalAccessorToLocalDateTimeConverter,
boolean isStart) {
return DateUtils.parseDateTimeStringIfValid(thePeriod, theDateTimeFormatter)
.flatMap(theTemporalAccessorToLocalDateTimeConverter)
.orElseThrow(() -> new InvalidRequestException(String.format(
"%s Period %s: %s has an unsupported format",
Msg.code(2558), isStart ? "start" : "end", thePeriod)));
}
@Nonnull
private static DateTimeFormatter validateAndGetDateTimeFormat(String theThePeriodStart, String theThePeriodEnd) {
final DateTimeFormatter dateTimeFormatterStart =
VALID_DATE_TIME_FORMATTERS_BY_FORMAT_LENGTH.get(theThePeriodStart.length());
if (dateTimeFormatterStart == null) {
throw new InvalidRequestException(String.format(
"%s Unsupported Date/Time format for period start: %s or end: %s",
Msg.code(2559), theThePeriodStart, theThePeriodEnd));
}
return dateTimeFormatterStart;
}
private String formatWithTimezone(LocalDateTime theLocalDateTime, ZoneId theZoneId) {
return theLocalDateTime.atZone(theZoneId).format(DATE_TIME_FORMATTER_YYYY_MM_DD_HH_MM_SS_Z_OUTPUT);
}
private ZoneId getClientTimezoneOrInvalidRequest(RequestDetails theRequestDetails) {
final String clientTimezoneString = theRequestDetails.getHeader(Constants.HEADER_CLIENT_TIMEZONE);
if (Strings.isNotBlank(clientTimezoneString)) {
try {
return ZoneId.of(clientTimezoneString);
} catch (Exception exception) {
throw new InvalidRequestException(
String.format("%sInvalid value for Timezone header: %s", Msg.code(2560), clientTimezoneString));
}
}
return myFallbackTimezone;
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.cr.r4;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import jakarta.annotation.Nullable;
import org.hl7.fhir.r4.model.DateType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.MeasureReport;
@ -12,6 +13,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.NoSuchElementException;
import java.util.Optional;
@ -69,17 +72,17 @@ class MeasureOperationProviderTest extends BaseCrR4TestServer {
void testHedis2022() {
runWithPatient("BCSEHEDISMY2022", "Patient/Patient-5", 0, 0, 0, 0, false,
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.999]");
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.000]");
runWithPatient("BCSEHEDISMY2022", "Patient/Patient-7", 1, 1, 0, 0, true,
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.999]");
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.000]");
runWithPatient("BCSEHEDISMY2022", "Patient/Patient-9", 0, 0, 0, 0, true,
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.999]");
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.000]");
runWithPatient("BCSEHEDISMY2022", "Patient/Patient-21", 1, 0, 1, 0, true,
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.999]");
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.000]");
runWithPatient("BCSEHEDISMY2022", "Patient/Patient-23", 1, 1, 0, 0, true,
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.999]");
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.000]");
runWithPatient("BCSEHEDISMY2022", "Patient/Patient-65", 1, 1, 0, 1, true,
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.999]");
"Interval[2020-10-01T00:00:00.000, 2022-12-31T23:59:59.000]");
}
void testClientNonPatientBasedMeasureEvaluate() {

View File

@ -5,40 +5,22 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import jakarta.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class MeasureReportPeriodRequestProcessingServiceTest {
class MeasureReportPeriodRequestValidatorAndConverterTest {
private final MeasureReportPeriodRequestProcessingService myTestSubject = new MeasureReportPeriodRequestProcessingService(ZoneOffset.UTC);
private final MeasureReportPeriodRequestValidatorAndConverter myTestSubject = new MeasureReportPeriodRequestValidatorAndConverter(ZoneOffset.UTC);
@Test
void dammit() {
final String expectedResult = "2020-01-01T00:00:00.0-05:00";
final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SXXX");
final ZonedDateTime input = ZonedDateTime.of(LocalDateTime.of(2020, Month.JANUARY, 1, 0, 0, 0), ZoneId.of("America/Toronto"));
final String actualResult = input.format(dateTimeFormatter);
assertThat(actualResult).isEqualTo(expectedResult);
}
// LUKETODO: what happens if only one is null?
@ParameterizedTest
@CsvSource( nullValues = {"null"},
value={
@ -120,6 +102,8 @@ class MeasureReportPeriodRequestProcessingServiceTest {
private static Stream<Arguments> errorParams() {
return Stream.of(
Arguments.of(null, "2024", "2024-01", new InvalidRequestException("Period start: 2024 and end: 2024-01 are not the same date/time formats")),
Arguments.of(null, null, "2024-01", new InvalidRequestException("Either both period start: [null] and end: [2024-01] must be empty or non empty")),
Arguments.of(null, "2024-01", null, new InvalidRequestException("Either both period start: [2024-01] and end: [null] must be empty or non empty")),
Arguments.of(null, "2024-01-01T12", "2024-01-01T12", new InvalidRequestException("Unsupported Date/Time format for period start: 2024-01-01T12 or end: 2024-01-01T12")),
Arguments.of(null, "2024-01-02", "2024-01-01", new InvalidRequestException("Invalid Interval - the ending boundary: 2024-01-01 must be greater than or equal to the starting boundary: 2024-01-02")),
Arguments.of("Middle-Earth/Combe", "2024-01-02", "2024-01-03", new InvalidRequestException("Invalid value for Timezone header: Middle-Earth/Combe")),

34
pom.xml
View File

@ -869,6 +869,7 @@
<developer>
<id>delopst</id>
<name>Primož Delopst</name>
<organization>Better</organization>
</developer>
<developer>
<id>Zach Smith</id>
@ -1160,27 +1161,38 @@
<dependency>
<groupId>org.simplejavamail</groupId>
<artifactId>simple-java-mail</artifactId>
<version>6.6.1</version>
<version>8.11.2</version>
<exclusions>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<groupId>com.github.bbottema</groupId>
<artifactId>jetbrains-runtime-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.activation</groupId>
<artifactId>jakarta.activation</artifactId>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>2.1.0-rc-1</version>
<exclusions>
<exclusion>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>com.icegreen</groupId>
<artifactId>greenmail-junit5</artifactId>
<version>1.6.4</version>
<version>2.1.0-rc-1</version>
<scope>compile</scope>
</dependency>
<!-- mail end -->