Merge pull request #5978
* Fix authorization handling for Bundle resources in the output. When t… * Merge remote-tracking branch 'origin/master' into mm-20240529-test-se… * Add test case for the described scenario * Merge remote-tracking branch 'origin/master' * Add Bundle search test with custom search parameters. * Update test to simpler one to illustrate the problem * SearchParameter for Bundle document referencing resource through comp… * Merge remote-tracking branch 'origin/master' into mm-20240605-search-… * Merge another test into the new class * Remove unused json file. Small formatting fix in a test. * Add more use cases and adjust an existing one
This commit is contained in:
parent
5302a7da52
commit
00a6591586
|
@ -776,7 +776,7 @@ public class BundleUtil {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (isPlaceholderReference) {
|
if (isPlaceholderReference) {
|
||||||
if (theUrl.equals(next.getUrl())
|
if (theUrl.equals(next.getFullUrl())
|
||||||
|| theUrl.equals(nextResource.getIdElement().getValue())) {
|
|| theUrl.equals(nextResource.getIdElement().getValue())) {
|
||||||
return nextResource;
|
return nextResource;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 5988
|
||||||
|
title: "Previously, when creating a custom SearchParameter for a document Bundle that references an entry resource
|
||||||
|
through the composition (e.g. Bundle.entry[0].resource.as(Composition).subject.resolve().as(Patient).identifier),
|
||||||
|
the search using that parameter would not return the matching document Bundle resources, despite the fullUrl of the entry matching the composite reference.
|
||||||
|
This problem does not exist if the reference match can be done against the entry id.
|
||||||
|
However an id is not required so the match should be done against the fullUrl.
|
||||||
|
This issue has been fixed."
|
|
@ -655,15 +655,8 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
|
||||||
|
|
||||||
public List<String> createResourceLinkPaths(
|
public List<String> createResourceLinkPaths(
|
||||||
String theResourceName, String theParamName, List<String> theParamQualifiers) {
|
String theResourceName, String theParamName, List<String> theParamQualifiers) {
|
||||||
int linkIndex = theParamName.indexOf('.');
|
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
|
||||||
if (linkIndex == -1) {
|
if (param != null) {
|
||||||
|
|
||||||
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
|
|
||||||
if (param == null) {
|
|
||||||
// This can happen during recursion, if not all the possible target types of one link in the chain
|
|
||||||
// support the next link
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
List<String> path = param.getPathsSplit();
|
List<String> path = param.getPathsSplit();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -681,40 +674,48 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
boolean containsChain = theParamName.contains(".");
|
||||||
|
if (containsChain) {
|
||||||
|
int linkIndex = theParamName.indexOf('.');
|
||||||
String paramNameHead = theParamName.substring(0, linkIndex);
|
String paramNameHead = theParamName.substring(0, linkIndex);
|
||||||
String paramNameTail = theParamName.substring(linkIndex + 1);
|
String paramNameTail = theParamName.substring(linkIndex + 1);
|
||||||
String qualifier = theParamQualifiers.get(0);
|
String qualifier = !theParamQualifiers.isEmpty() ? theParamQualifiers.get(0) : null;
|
||||||
|
List<String> nextQualifiersList = !theParamQualifiers.isEmpty()
|
||||||
|
? theParamQualifiers.subList(1, theParamQualifiers.size())
|
||||||
|
: List.of();
|
||||||
|
|
||||||
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, paramNameHead);
|
param = mySearchParamRegistry.getActiveSearchParam(theResourceName, paramNameHead);
|
||||||
if (param == null) {
|
if (param != null) {
|
||||||
// This can happen during recursion, if not all the possible target types of one link in the chain
|
Set<String> tailPaths = param.getTargets().stream()
|
||||||
// support the next link
|
.filter(t -> isBlank(qualifier) || qualifier.equals(t))
|
||||||
return new ArrayList<>();
|
.map(t -> createResourceLinkPaths(t, paramNameTail, nextQualifiersList))
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.map(t -> t.substring(t.indexOf('.') + 1))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
List<String> path = param.getPathsSplit();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SearchParameters can declare paths on multiple resource
|
||||||
|
* types. Here we only want the ones that actually apply.
|
||||||
|
* Then append all the tail paths to each of the applicable head paths
|
||||||
|
*/
|
||||||
|
return path.stream()
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(t -> t.startsWith(theResourceName + "."))
|
||||||
|
.map(head -> tailPaths.stream()
|
||||||
|
.map(tail -> head + "." + tail)
|
||||||
|
.collect(Collectors.toSet()))
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
Set<String> tailPaths = param.getTargets().stream()
|
|
||||||
.filter(t -> isBlank(qualifier) || qualifier.equals(t))
|
|
||||||
.map(t -> createResourceLinkPaths(
|
|
||||||
t, paramNameTail, theParamQualifiers.subList(1, theParamQualifiers.size())))
|
|
||||||
.flatMap(Collection::stream)
|
|
||||||
.map(t -> t.substring(t.indexOf('.') + 1))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
List<String> path = param.getPathsSplit();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* SearchParameters can declare paths on multiple resource
|
|
||||||
* types. Here we only want the ones that actually apply.
|
|
||||||
* Then append all the tail paths to each of the applicable head paths
|
|
||||||
*/
|
|
||||||
return path.stream()
|
|
||||||
.map(String::trim)
|
|
||||||
.filter(t -> t.startsWith(theResourceName + "."))
|
|
||||||
.map(head ->
|
|
||||||
tailPaths.stream().map(tail -> head + "." + tail).collect(Collectors.toSet()))
|
|
||||||
.flatMap(Collection::stream)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This can happen during recursion, if not all the possible target types of one link in the chain
|
||||||
|
// support the next link
|
||||||
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryParameterType mapReferenceChainToRawParamType(
|
private IQueryParameterType mapReferenceChainToRawParamType(
|
||||||
|
|
|
@ -3,9 +3,6 @@ package ca.uhn.fhir.jpa.dao.r4;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||||
import ca.uhn.fhir.jpa.util.SqlQuery;
|
import ca.uhn.fhir.jpa.util.SqlQuery;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
|
@ -13,12 +10,9 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.AuditEvent;
|
import org.hl7.fhir.r4.model.AuditEvent;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
import org.hl7.fhir.r4.model.Composition;
|
|
||||||
import org.hl7.fhir.r4.model.Device;
|
import org.hl7.fhir.r4.model.Device;
|
||||||
import org.hl7.fhir.r4.model.Encounter;
|
import org.hl7.fhir.r4.model.Encounter;
|
||||||
import org.hl7.fhir.r4.model.Enumerations;
|
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.Location;
|
import org.hl7.fhir.r4.model.Location;
|
||||||
import org.hl7.fhir.r4.model.MessageHeader;
|
import org.hl7.fhir.r4.model.MessageHeader;
|
||||||
|
@ -27,32 +21,22 @@ import org.hl7.fhir.r4.model.Organization;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.r4.model.Quantity;
|
import org.hl7.fhir.r4.model.Quantity;
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.hl7.fhir.r4.model.SearchParameter;
|
|
||||||
import org.hl7.fhir.r4.model.StringType;
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.CsvSource;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import java.sql.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChainingR4SearchTest extends BaseJpaR4Test {
|
public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
MatchUrlService myMatchUrlService;
|
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
public void after() throws Exception {
|
public void after() throws Exception {
|
||||||
|
|
||||||
|
@ -359,7 +343,6 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
@Test
|
@Test
|
||||||
public void testShouldNotResolveATwoLinkChainWithAContainedResourceWhenContainedResourceIndexingIsTurnedOff() {
|
public void testShouldNotResolveATwoLinkChainWithAContainedResourceWhenContainedResourceIndexingIsTurnedOff() {
|
||||||
// setup
|
// setup
|
||||||
IIdType oid1;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
@ -843,8 +826,6 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
// setup
|
// setup
|
||||||
myStorageSettings.setIndexOnContainedResources(true);
|
myStorageSettings.setIndexOnContainedResources(true);
|
||||||
|
|
||||||
IIdType oid1;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
Organization org = new Organization();
|
Organization org = new Organization();
|
||||||
org.setId("org");
|
org.setId("org");
|
||||||
|
@ -1575,79 +1556,6 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
|
||||||
countUnionStatementsInGeneratedQuery("/Observation?subject:Location.name=Smith", 1);
|
countUnionStatementsInGeneratedQuery("/Observation?subject:Location.name=Smith", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@CsvSource({
|
|
||||||
// search url expected count
|
|
||||||
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=1980-01-01, 1, correct identifier correct birthdate",
|
|
||||||
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-1, 1, correct birthdate correct identifier",
|
|
||||||
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=2000-01-01, 0, correct identifier incorrect birthdate",
|
|
||||||
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-1, 0, incorrect birthdate correct identifier",
|
|
||||||
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=1980-01-01, 0, incorrect identifier correct birthdate",
|
|
||||||
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-2, 0, correct birthdate incorrect identifier",
|
|
||||||
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=2000-01-01, 0, incorrect identifier incorrect birthdate",
|
|
||||||
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-2, 0, incorrect birthdate incorrect identifier",
|
|
||||||
// try sort by composition sp
|
|
||||||
"/Bundle?composition.patient.identifier=system|value-1&_sort=composition.patient.birthdate, 1, correct identifier sort by birthdate",
|
|
||||||
|
|
||||||
})
|
|
||||||
public void testMultipleChainedBundleCompositionSearchParameters(String theSearchUrl, int theExpectedCount, String theMessage) {
|
|
||||||
createSearchParameter("bundle-composition-patient-birthdate",
|
|
||||||
"composition.patient.birthdate",
|
|
||||||
"Bundle",
|
|
||||||
"Bundle.entry.resource.ofType(Patient).birthDate",
|
|
||||||
Enumerations.SearchParamType.DATE
|
|
||||||
);
|
|
||||||
|
|
||||||
createSearchParameter("bundle-composition-patient-identifier",
|
|
||||||
"composition.patient.identifier",
|
|
||||||
"Bundle",
|
|
||||||
"Bundle.entry.resource.ofType(Patient).identifier",
|
|
||||||
Enumerations.SearchParamType.TOKEN
|
|
||||||
);
|
|
||||||
|
|
||||||
createDocumentBundleWithPatientDetails("1980-01-01", "system", "value-1");
|
|
||||||
|
|
||||||
List<String> ids = myTestDaoSearch.searchForIds(theSearchUrl);
|
|
||||||
assertThat(ids).as(theMessage).hasSize(theExpectedCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createSearchParameter(String theId, String theCode, String theBase, String theExpression, Enumerations.SearchParamType theType) {
|
|
||||||
SearchParameter searchParameter = new SearchParameter();
|
|
||||||
searchParameter.setId(theId);
|
|
||||||
searchParameter.setCode(theCode);
|
|
||||||
searchParameter.setName(theCode);
|
|
||||||
searchParameter.setUrl("http://example.org/SearchParameter/" + theId);
|
|
||||||
searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
|
||||||
searchParameter.addBase(theBase);
|
|
||||||
searchParameter.setType(theType);
|
|
||||||
searchParameter.setExpression(theExpression);
|
|
||||||
searchParameter = (SearchParameter) mySearchParameterDao.update(searchParameter, mySrd).getResource();
|
|
||||||
mySearchParamRegistry.forceRefresh();
|
|
||||||
assertNotNull(mySearchParamRegistry.getActiveSearchParam(theBase, searchParameter.getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createDocumentBundleWithPatientDetails(String theBirthDate, String theIdentifierSystem, String theIdentifierValue) {
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.setBirthDate(Date.valueOf(theBirthDate));
|
|
||||||
patient.addIdentifier().setSystem(theIdentifierSystem).setValue(theIdentifierValue);
|
|
||||||
patient = (Patient) myPatientDao.create(patient, mySrd).getResource();
|
|
||||||
assertSearchReturns(myPatientDao, SearchParameterMap.newSynchronous(), 1);
|
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.setType(Bundle.BundleType.DOCUMENT);
|
|
||||||
Composition composition = new Composition();
|
|
||||||
composition.setType(new CodeableConcept().addCoding(new Coding().setCode("code").setSystem("http://example.org")));
|
|
||||||
bundle.addEntry().setResource(composition);
|
|
||||||
composition.getSubject().setReference(patient.getIdElement().getValue());
|
|
||||||
bundle.addEntry().setResource(patient);
|
|
||||||
myBundleDao.create(bundle, mySrd);
|
|
||||||
assertSearchReturns(myBundleDao, SearchParameterMap.newSynchronous(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertSearchReturns(IFhirResourceDao<?> theDao, SearchParameterMap theSearchParams, int theExpectedCount){
|
|
||||||
assertEquals(theExpectedCount, theDao.search(theSearchParams, mySrd).size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) {
|
private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) {
|
||||||
myCaptureQueriesListener.clear();
|
myCaptureQueriesListener.clear();
|
||||||
myTestDaoSearch.searchForIds(theUrl);
|
myTestDaoSearch.searchForIds(theUrl);
|
||||||
|
|
|
@ -0,0 +1,246 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
|
||||||
|
import ca.uhn.fhir.jpa.test.config.TestHSearchAddInConfig;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.Composition;
|
||||||
|
import org.hl7.fhir.r4.model.Enumerations;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
|
import org.hl7.fhir.r4.model.SearchParameter;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
|
||||||
|
import java.sql.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
@ContextConfiguration(classes = {TestHSearchAddInConfig.NoFT.class})
|
||||||
|
public class FhirResourceDaoR4SearchBundleNoFTTest extends BaseJpaR4Test {
|
||||||
|
@Test
|
||||||
|
public void searchDocumentBundle_withLocalReferenceUsingId_returnsCorrectly() {
|
||||||
|
createBundleSearchParameter("Bundle-composition-patient-identifier",
|
||||||
|
Enumerations.SearchParamType.TOKEN,
|
||||||
|
"composition.patient.identifier",
|
||||||
|
"Bundle.entry[0].resource.as(Composition).subject.resolve().as(Patient).identifier");
|
||||||
|
|
||||||
|
String patientId = "Patient/ABC";
|
||||||
|
String identifierSystem = "http://foo";
|
||||||
|
String identifierValue = "bar";
|
||||||
|
|
||||||
|
String patientUrl = "http://example.com/fhir/" + patientId;
|
||||||
|
|
||||||
|
Composition composition = new Composition();
|
||||||
|
composition.setSubject(new Reference(patientUrl));
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(patientId);
|
||||||
|
patient.addIdentifier().setSystem(identifierSystem).setValue(identifierValue);
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
bundle.addEntry().setResource(composition);
|
||||||
|
bundle.addEntry().setResource(patient);
|
||||||
|
|
||||||
|
DaoMethodOutcome createOutcome = myBundleDao.create(bundle, mySrd);
|
||||||
|
assertTrue(createOutcome.getCreated());
|
||||||
|
IIdType bundleId = createOutcome.getId();
|
||||||
|
|
||||||
|
verifySearchCompositionPatientReturnsBundle(identifierSystem, identifierValue, bundleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchDocumentBundle_withPlaceholderReferenceUsingFullUrl_returnsCorrectly() {
|
||||||
|
createBundleSearchParameter("Bundle-composition-patient-identifier",
|
||||||
|
Enumerations.SearchParamType.TOKEN,
|
||||||
|
"composition.patient.identifier",
|
||||||
|
"Bundle.entry[0].resource.as(Composition).subject.resolve().as(Patient).identifier");
|
||||||
|
|
||||||
|
String patientUrl = "urn:uuid:" + UUID.randomUUID();
|
||||||
|
String identifierSystem = "http://foo";
|
||||||
|
String identifierValue = "bar";
|
||||||
|
|
||||||
|
Composition composition = new Composition();
|
||||||
|
composition.setSubject(new Reference(patientUrl));
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addIdentifier().setSystem(identifierSystem).setValue(identifierValue);
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
bundle.addEntry().setResource(composition);
|
||||||
|
bundle.addEntry().setFullUrl(patientUrl).setResource(patient);
|
||||||
|
|
||||||
|
DaoMethodOutcome createOutcome = myBundleDao.create(bundle, mySrd);
|
||||||
|
assertTrue(createOutcome.getCreated());
|
||||||
|
IIdType bundleId = createOutcome.getId();
|
||||||
|
|
||||||
|
verifySearchCompositionPatientReturnsBundle(identifierSystem, identifierValue, bundleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchDocumentBundle_withPlaceholderReferenceUsingId_returnsCorrectly() {
|
||||||
|
createBundleSearchParameter("Bundle-composition-patient-identifier",
|
||||||
|
Enumerations.SearchParamType.TOKEN,
|
||||||
|
"composition.patient.identifier",
|
||||||
|
"Bundle.entry[0].resource.as(Composition).subject.resolve().as(Patient).identifier");
|
||||||
|
|
||||||
|
String patientId = "urn:uuid:" + UUID.randomUUID();
|
||||||
|
String identifierSystem = "http://foo";
|
||||||
|
String identifierValue = "bar";
|
||||||
|
|
||||||
|
Composition composition = new Composition();
|
||||||
|
composition.setSubject(new Reference(patientId));
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(patientId);
|
||||||
|
patient.addIdentifier().setSystem(identifierSystem).setValue(identifierValue);
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
bundle.addEntry().setResource(composition);
|
||||||
|
bundle.addEntry().setResource(patient);
|
||||||
|
|
||||||
|
DaoMethodOutcome createOutcome = myBundleDao.create(bundle, mySrd);
|
||||||
|
assertTrue(createOutcome.getCreated());
|
||||||
|
IIdType bundleId = createOutcome.getId();
|
||||||
|
|
||||||
|
verifySearchCompositionPatientReturnsBundle(identifierSystem, identifierValue, bundleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchDocumentBundle_withExternalReference_returnsCorrectly() {
|
||||||
|
String searchParamCode = "composition.subject";
|
||||||
|
createBundleSearchParameter("Bundle-composition-subject",
|
||||||
|
Enumerations.SearchParamType.REFERENCE,
|
||||||
|
searchParamCode,
|
||||||
|
"Bundle.entry[0].resource.as(Composition).subject");
|
||||||
|
|
||||||
|
String patientId = "Patient/ABC";
|
||||||
|
String identifierSystem = "http://foo";
|
||||||
|
String identifierValue = "bar";
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(patientId);
|
||||||
|
patient.addIdentifier().setSystem(identifierSystem).setValue(identifierValue);
|
||||||
|
DaoMethodOutcome createPatientOutcome = myPatientDao.update(patient, mySrd);
|
||||||
|
assertTrue(createPatientOutcome.getCreated());
|
||||||
|
|
||||||
|
Composition composition = new Composition();
|
||||||
|
composition.getSubject().setReference(createPatientOutcome.getId().toUnqualifiedVersionless().getValue());
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
bundle.addEntry().setResource(composition);
|
||||||
|
|
||||||
|
DaoMethodOutcome createBundleOutcome = myBundleDao.create(bundle, mySrd);
|
||||||
|
assertTrue(createBundleOutcome.getCreated());
|
||||||
|
IIdType bundleId = createBundleOutcome.getId();
|
||||||
|
|
||||||
|
verifySearchReturnsBundle(SearchParameterMap.newSynchronous(searchParamCode, new ReferenceParam(patientId)), bundleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifySearchCompositionPatientReturnsBundle(String theIdentifierSystem, String theIdentifierValue, IIdType theBundleId) {
|
||||||
|
final String systemAndValue = theIdentifierSystem + "|" + theIdentifierValue;
|
||||||
|
verifySearchReturnsBundle(SearchParameterMap.newSynchronous("composition.patient.identifier", new TokenParam(theIdentifierValue)), theBundleId);
|
||||||
|
verifySearchReturnsBundle(SearchParameterMap.newSynchronous("composition.patient.identifier", new TokenParam(theIdentifierSystem, theIdentifierValue)), theBundleId);
|
||||||
|
verifySearchReturnsBundle(SearchParameterMap.newSynchronous("composition", new ReferenceParam("patient.identifier", theIdentifierValue)), theBundleId);
|
||||||
|
verifySearchReturnsBundle(SearchParameterMap.newSynchronous("composition", new ReferenceParam("patient.identifier", systemAndValue)), theBundleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({
|
||||||
|
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=1980-01-01, true, correct identifier correct birthdate",
|
||||||
|
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-1, true, correct birthdate correct identifier",
|
||||||
|
"/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=2000-01-01, false, correct identifier incorrect birthdate",
|
||||||
|
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-1, false, incorrect birthdate correct identifier",
|
||||||
|
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=1980-01-01, false, incorrect identifier correct birthdate",
|
||||||
|
"/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-2, false, correct birthdate incorrect identifier",
|
||||||
|
"/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=2000-01-01, false, incorrect identifier incorrect birthdate",
|
||||||
|
"/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-2, false, incorrect birthdate incorrect identifier",
|
||||||
|
// try sort by composition sp
|
||||||
|
"/Bundle?composition.patient.identifier=system|value-1&_sort=composition.patient.birthdate, true, correct identifier sort by birthdate",
|
||||||
|
|
||||||
|
})
|
||||||
|
public void searchDocumentBundle_withExternalReferenceAndEntryCopy_returnsCorrectly(String theSearchUrl, boolean theShouldMatch, String theMessage) {
|
||||||
|
createBundleSearchParameter("bundle-composition-patient-birthdate",
|
||||||
|
Enumerations.SearchParamType.DATE,
|
||||||
|
"composition.patient.birthdate",
|
||||||
|
"Bundle.entry.resource.ofType(Patient).birthDate"
|
||||||
|
);
|
||||||
|
|
||||||
|
createBundleSearchParameter("bundle-composition-patient-identifier",
|
||||||
|
Enumerations.SearchParamType.TOKEN,
|
||||||
|
"composition.patient.identifier",
|
||||||
|
"Bundle.entry.resource.ofType(Patient).identifier"
|
||||||
|
);
|
||||||
|
|
||||||
|
String identifierSystem = "system";
|
||||||
|
String identifierValue = "value-1";
|
||||||
|
String birthDateString = "1980-01-01";
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setBirthDate(Date.valueOf(birthDateString));
|
||||||
|
patient.addIdentifier().setSystem(identifierSystem).setValue(identifierValue);
|
||||||
|
|
||||||
|
DaoMethodOutcome createPatientOutcome = myPatientDao.create(patient, mySrd);
|
||||||
|
assertTrue(createPatientOutcome.getCreated());
|
||||||
|
|
||||||
|
Composition composition = new Composition();
|
||||||
|
composition.setSubject(new Reference(createPatientOutcome.getId().getValue()));
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
bundle.addEntry().setResource(composition);
|
||||||
|
bundle.addEntry().setResource(patient);
|
||||||
|
|
||||||
|
DaoMethodOutcome createBundleOutcome = myBundleDao.create(bundle, mySrd);
|
||||||
|
assertTrue(createBundleOutcome.getCreated());
|
||||||
|
IIdType bundleId = createBundleOutcome.getId();
|
||||||
|
|
||||||
|
List<String> ids = myTestDaoSearch.searchForIds(theSearchUrl);
|
||||||
|
if (theShouldMatch) {
|
||||||
|
assertThat(ids).as(theMessage).containsExactlyInAnyOrder(bundleId.getIdPart());
|
||||||
|
} else {
|
||||||
|
assertThat(ids).as(theMessage).hasSize(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createBundleSearchParameter(String id, Enumerations.SearchParamType theType, String theCode, String theExpression) {
|
||||||
|
SearchParameter sp = new SearchParameter()
|
||||||
|
.setCode(theCode)
|
||||||
|
.addBase("Bundle")
|
||||||
|
.setType(theType)
|
||||||
|
.setExpression(theExpression)
|
||||||
|
.setXpathUsage(SearchParameter.XPathUsageType.NORMAL)
|
||||||
|
.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
sp.setId("SearchParameter/" + id);
|
||||||
|
sp.setUrl("http://example.com/fhir/" + sp.getId());
|
||||||
|
ourLog.info("SP: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(sp));
|
||||||
|
IBaseResource resource = mySearchParameterDao.update(sp, mySrd).getResource();
|
||||||
|
assertNotNull(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifySearchReturnsBundle(SearchParameterMap theSearchParameterMap, IIdType theBundleId) {
|
||||||
|
IBundleProvider searchOutcome = myBundleDao.search(theSearchParameterMap, mySrd);
|
||||||
|
assertEquals(1, searchOutcome.size());
|
||||||
|
assertEquals(theBundleId, searchOutcome.getAllResources().get(0).getIdElement());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import ca.uhn.fhir.batch2.jobs.reindex.ReindexAppCtx;
|
import ca.uhn.fhir.batch2.jobs.reindex.ReindexAppCtx;
|
||||||
import ca.uhn.fhir.batch2.model.JobInstance;
|
import ca.uhn.fhir.batch2.model.JobInstance;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
|
@ -27,7 +26,6 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.util.ClasspathUtil;
|
import ca.uhn.fhir.util.ClasspathUtil;
|
||||||
import ca.uhn.fhir.util.HapiExtensions;
|
import ca.uhn.fhir.util.HapiExtensions;
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Appointment;
|
import org.hl7.fhir.r4.model.Appointment;
|
||||||
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
|
import org.hl7.fhir.r4.model.Appointment.AppointmentStatus;
|
||||||
|
@ -36,13 +34,11 @@ import org.hl7.fhir.r4.model.ChargeItem;
|
||||||
import org.hl7.fhir.r4.model.CodeType;
|
import org.hl7.fhir.r4.model.CodeType;
|
||||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
import org.hl7.fhir.r4.model.Composition;
|
|
||||||
import org.hl7.fhir.r4.model.Condition;
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
import org.hl7.fhir.r4.model.DateType;
|
import org.hl7.fhir.r4.model.DateType;
|
||||||
import org.hl7.fhir.r4.model.DecimalType;
|
import org.hl7.fhir.r4.model.DecimalType;
|
||||||
import org.hl7.fhir.r4.model.DiagnosticReport;
|
import org.hl7.fhir.r4.model.DiagnosticReport;
|
||||||
import org.hl7.fhir.r4.model.Encounter;
|
|
||||||
import org.hl7.fhir.r4.model.Enumerations;
|
import org.hl7.fhir.r4.model.Enumerations;
|
||||||
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
||||||
import org.hl7.fhir.r4.model.Extension;
|
import org.hl7.fhir.r4.model.Extension;
|
||||||
|
@ -74,9 +70,8 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
|
@ -304,48 +299,6 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
||||||
mySearchParamRegistry.forceRefresh();
|
mySearchParamRegistry.forceRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBundleComposition() {
|
|
||||||
SearchParameter fooSp = new SearchParameter();
|
|
||||||
fooSp.setCode("foo");
|
|
||||||
fooSp.addBase("Bundle");
|
|
||||||
fooSp.setType(Enumerations.SearchParamType.REFERENCE);
|
|
||||||
fooSp.setTitle("FOO SP");
|
|
||||||
fooSp.setExpression("Bundle.entry[0].resource.as(Composition).encounter");
|
|
||||||
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
|
|
||||||
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
|
|
||||||
|
|
||||||
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(fooSp));
|
|
||||||
|
|
||||||
mySearchParameterDao.create(fooSp, mySrd);
|
|
||||||
mySearchParamRegistry.forceRefresh();
|
|
||||||
|
|
||||||
Encounter enc = new Encounter();
|
|
||||||
enc.setStatus(Encounter.EncounterStatus.ARRIVED);
|
|
||||||
String encId = myEncounterDao.create(enc).getId().toUnqualifiedVersionless().getValue();
|
|
||||||
|
|
||||||
Composition composition = new Composition();
|
|
||||||
composition.getEncounter().setReference(encId);
|
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.setType(Bundle.BundleType.DOCUMENT);
|
|
||||||
bundle.addEntry().setResource(composition);
|
|
||||||
|
|
||||||
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle));
|
|
||||||
String bundleId = myBundleDao.create(bundle).getId().toUnqualifiedVersionless().getValue();
|
|
||||||
|
|
||||||
SearchParameterMap map;
|
|
||||||
|
|
||||||
map = new SearchParameterMap();
|
|
||||||
map.setLoadSynchronous(true);
|
|
||||||
map.add("foo", new ReferenceParam(encId));
|
|
||||||
IBundleProvider results = myBundleDao.search(map);
|
|
||||||
assertThat(toUnqualifiedVersionlessIdValues(results)).contains(bundleId);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreateInvalidUnquotedExtensionUrl() {
|
public void testCreateInvalidUnquotedExtensionUrl() {
|
||||||
String invalidExpression = "Patient.extension.where(url=http://foo).value";
|
String invalidExpression = "Patient.extension.where(url=http://foo).value";
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
package ca.uhn.fhir.jpa.dao.r4;
|
package ca.uhn.fhir.jpa.dao.r4;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||||
|
@ -89,7 +85,6 @@ import org.hl7.fhir.r4.model.CodeableConcept;
|
||||||
import org.hl7.fhir.r4.model.Coding;
|
import org.hl7.fhir.r4.model.Coding;
|
||||||
import org.hl7.fhir.r4.model.Communication;
|
import org.hl7.fhir.r4.model.Communication;
|
||||||
import org.hl7.fhir.r4.model.CommunicationRequest;
|
import org.hl7.fhir.r4.model.CommunicationRequest;
|
||||||
import org.hl7.fhir.r4.model.Composition;
|
|
||||||
import org.hl7.fhir.r4.model.Condition;
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
|
import org.hl7.fhir.r4.model.ContactPoint.ContactPointSystem;
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
import org.hl7.fhir.r4.model.DateTimeType;
|
||||||
|
@ -144,8 +139,6 @@ import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.CsvSource;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -184,12 +177,13 @@ import static ca.uhn.fhir.util.DateUtils.convertDateToIso8601String;
|
||||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||||
import static org.apache.commons.lang3.StringUtils.leftPad;
|
import static org.apache.commons.lang3.StringUtils.leftPad;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -5733,95 +5727,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Index for
|
|
||||||
* [base]/Bundle?composition.patient.identifier=foo
|
|
||||||
*/
|
|
||||||
@ParameterizedTest
|
|
||||||
@CsvSource({
|
|
||||||
"true , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b",
|
|
||||||
"false, urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b , urn:uuid:5c34dc2c-9b5d-4ec1-b30b-3e2d4371508b",
|
|
||||||
"true , Patient/ABC , Patient/ABC ",
|
|
||||||
"false, Patient/ABC , Patient/ABC ",
|
|
||||||
"true , Patient/ABC , http://example.com/fhir/Patient/ABC ",
|
|
||||||
"false, Patient/ABC , http://example.com/fhir/Patient/ABC ",
|
|
||||||
})
|
|
||||||
public void testCreateAndSearchForFullyChainedSearchParameter(boolean theUseFullChainInName, String thePatientId, String theFullUrl) {
|
|
||||||
// Setup 1
|
|
||||||
|
|
||||||
myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED);
|
|
||||||
|
|
||||||
SearchParameter sp = new SearchParameter();
|
|
||||||
sp.setId("SearchParameter/Bundle-composition-patient-identifier");
|
|
||||||
sp.setCode("composition.patient.identifier");
|
|
||||||
sp.setName("composition.patient.identifier");
|
|
||||||
sp.setUrl("http://example.org/SearchParameter/Bundle-composition-patient-identifier");
|
|
||||||
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
|
||||||
sp.setType(Enumerations.SearchParamType.TOKEN);
|
|
||||||
sp.setExpression("Bundle.entry[0].resource.as(Composition).subject.resolve().as(Patient).identifier");
|
|
||||||
sp.addBase("Bundle");
|
|
||||||
ourLog.info("SP: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(sp));
|
|
||||||
mySearchParameterDao.update(sp, mySrd);
|
|
||||||
|
|
||||||
mySearchParamRegistry.forceRefresh();
|
|
||||||
|
|
||||||
// Test 1
|
|
||||||
|
|
||||||
Composition composition = new Composition();
|
|
||||||
composition.setSubject(new Reference(thePatientId));
|
|
||||||
|
|
||||||
Patient patient = new Patient();
|
|
||||||
patient.setId(new IdType(theFullUrl));
|
|
||||||
patient.addIdentifier().setSystem("http://foo").setValue("bar");
|
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.setType(Bundle.BundleType.DOCUMENT);
|
|
||||||
bundle
|
|
||||||
.addEntry()
|
|
||||||
.setResource(composition);
|
|
||||||
bundle
|
|
||||||
.addEntry()
|
|
||||||
.setFullUrl(theFullUrl)
|
|
||||||
.setResource(patient);
|
|
||||||
|
|
||||||
myBundleDao.create(bundle, mySrd);
|
|
||||||
|
|
||||||
Bundle bundle2 = new Bundle();
|
|
||||||
bundle2.setType(Bundle.BundleType.DOCUMENT);
|
|
||||||
myBundleDao.create(bundle2, mySrd);
|
|
||||||
|
|
||||||
// Test
|
|
||||||
|
|
||||||
SearchParameterMap map;
|
|
||||||
if (theUseFullChainInName) {
|
|
||||||
map = SearchParameterMap.newSynchronous("composition.patient.identifier", new TokenParam("http://foo", "bar"));
|
|
||||||
} else {
|
|
||||||
map = SearchParameterMap.newSynchronous("composition", new ReferenceParam("patient.identifier", "http://foo|bar"));
|
|
||||||
}
|
|
||||||
IBundleProvider outcome = myBundleDao.search(map, mySrd);
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
|
|
||||||
List<String> params = extractAllTokenIndexes();
|
|
||||||
assertThat(params).as(params.toString()).containsExactlyInAnyOrder("composition.patient.identifier http://foo|bar");
|
|
||||||
assertEquals(1, outcome.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> extractAllTokenIndexes() {
|
|
||||||
List<String> params = runInTransaction(() -> {
|
|
||||||
logAllTokenIndexes();
|
|
||||||
|
|
||||||
return myResourceIndexedSearchParamTokenDao
|
|
||||||
.findAll()
|
|
||||||
.stream()
|
|
||||||
.filter(t -> t.getParamName().contains("."))
|
|
||||||
.map(t -> t.getParamName() + " " + t.getSystem() + "|" + t.getValue())
|
|
||||||
.toList();
|
|
||||||
});
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
public class TagBelowTests {
|
public class TagBelowTests {
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.search.builder.predicate;
|
package ca.uhn.fhir.jpa.search.builder.predicate;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
||||||
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
import ca.uhn.fhir.jpa.api.svc.IIdHelperService;
|
||||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||||
|
@ -15,6 +16,8 @@ import com.healthmarketscience.sqlbuilder.InCondition;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSchema;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbSpec;
|
||||||
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
|
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
|
||||||
|
import org.hamcrest.MatcherAssert;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -27,11 +30,13 @@ import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.anyCollection;
|
import static org.mockito.ArgumentMatchers.anyCollection;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@ -48,7 +53,7 @@ public class ResourceLinkPredicateBuilderTest {
|
||||||
private ISearchParamRegistry mySearchParamRegistry;
|
private ISearchParamRegistry mySearchParamRegistry;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private IIdHelperService myIdHelperService;
|
private IIdHelperService<?> myIdHelperService;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void init() {
|
public void init() {
|
||||||
|
@ -64,14 +69,14 @@ public class ResourceLinkPredicateBuilderTest {
|
||||||
@Test
|
@Test
|
||||||
public void createEverythingPredicate_withListOfPids_returnsInPredicate() {
|
public void createEverythingPredicate_withListOfPids_returnsInPredicate() {
|
||||||
when(myResourceLinkPredicateBuilder.generatePlaceholders(anyCollection())).thenReturn(List.of(PLACEHOLDER_BASE + "1", PLACEHOLDER_BASE + "2"));
|
when(myResourceLinkPredicateBuilder.generatePlaceholders(anyCollection())).thenReturn(List.of(PLACEHOLDER_BASE + "1", PLACEHOLDER_BASE + "2"));
|
||||||
Condition condition = myResourceLinkPredicateBuilder.createEverythingPredicate("Patient", new ArrayList<>(), 1l, 2l);
|
Condition condition = myResourceLinkPredicateBuilder.createEverythingPredicate("Patient", new ArrayList<>(), 1L, 2L);
|
||||||
assertEquals(InCondition.class, condition.getClass());
|
assertEquals(InCondition.class, condition.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createEverythingPredicate_withSinglePid_returnsInCondition() {
|
public void createEverythingPredicate_withSinglePid_returnsInCondition() {
|
||||||
when(myResourceLinkPredicateBuilder.generatePlaceholders(anyCollection())).thenReturn(List.of(PLACEHOLDER_BASE + "1"));
|
when(myResourceLinkPredicateBuilder.generatePlaceholders(anyCollection())).thenReturn(List.of(PLACEHOLDER_BASE + "1"));
|
||||||
Condition condition = myResourceLinkPredicateBuilder.createEverythingPredicate("Patient", new ArrayList<>(), 1l);
|
Condition condition = myResourceLinkPredicateBuilder.createEverythingPredicate("Patient", new ArrayList<>(), 1L);
|
||||||
assertEquals(BinaryCondition.class, condition.getClass());
|
assertEquals(BinaryCondition.class, condition.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,4 +105,79 @@ public class ResourceLinkPredicateBuilderTest {
|
||||||
.hasMessage("HAPI-2498: Unsupported search modifier(s): \"[:identifier, :x, :y]\" for resource type \"Observation\". Valid search modifiers are: [:contains, :exact, :in, :iterate, :missing, :not-in, :of-type, :recurse, :text]");
|
.hasMessage("HAPI-2498: Unsupported search modifier(s): \"[:identifier, :x, :y]\" for resource type \"Observation\". Valid search modifiers are: [:contains, :exact, :in, :iterate, :missing, :not-in, :of-type, :recurse, :text]");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createResourceLinkPaths_withoutChainAndSearchParameterFoundNoQualifiers_returnsFilteredPaths() {
|
||||||
|
String paramName = "param.name";
|
||||||
|
String resourceType = "Bundle";
|
||||||
|
RuntimeSearchParam mockSearchParam = mock(RuntimeSearchParam.class);
|
||||||
|
when(mockSearchParam.getPathsSplit()).thenReturn(List.of("Patient.given", "Bundle.composition.subject", "Bundle.type"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam(resourceType, paramName)).thenReturn(mockSearchParam);
|
||||||
|
List<String> result = myResourceLinkPredicateBuilder.createResourceLinkPaths(resourceType, paramName, List.of());
|
||||||
|
MatcherAssert.assertThat(result, Matchers.containsInAnyOrder("Bundle.composition.subject", "Bundle.type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createResourceLinkPaths_withoutChainAndSearchParameterNotFoundNoQualifiers_returnsEmpty() {
|
||||||
|
String paramName = "param.name";
|
||||||
|
String resourceType = "Bundle";
|
||||||
|
List<String> result = myResourceLinkPredicateBuilder.createResourceLinkPaths(resourceType, paramName, List.of());
|
||||||
|
MatcherAssert.assertThat(result, Matchers.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createResourceLinkPaths_withChainAndSearchParameterFoundNoQualifiers_returnsPath() {
|
||||||
|
String paramName = "subject.identifier";
|
||||||
|
String resourceType = "Observation";
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Observation", "subject.identifier")).thenReturn(null);
|
||||||
|
RuntimeSearchParam observationSubjectSP = mock(RuntimeSearchParam.class);
|
||||||
|
when(observationSubjectSP.getPathsSplit()).thenReturn(List.of("Observation.subject"));
|
||||||
|
when(observationSubjectSP.getTargets()).thenReturn(Set.of("Patient"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Observation", "subject")).thenReturn(observationSubjectSP);
|
||||||
|
RuntimeSearchParam patientIdentifierSP = mock(RuntimeSearchParam.class);
|
||||||
|
when(patientIdentifierSP.getPathsSplit()).thenReturn(List.of("Patient.identifier"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Patient", "identifier")).thenReturn(patientIdentifierSP);
|
||||||
|
List<String> result = myResourceLinkPredicateBuilder.createResourceLinkPaths(resourceType, paramName, List.of());
|
||||||
|
MatcherAssert.assertThat(result, Matchers.containsInAnyOrder("Observation.subject.identifier"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createResourceLinkPaths_withChainAndSearchParameterFoundWithQualifiers_returnsPath() {
|
||||||
|
String paramName = "subject.managingOrganization.identifier";
|
||||||
|
String resourceType = "Observation";
|
||||||
|
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Observation", "subject.managingOrganization.identifier")).thenReturn(null);
|
||||||
|
|
||||||
|
RuntimeSearchParam observationSubjectSP = mock(RuntimeSearchParam.class);
|
||||||
|
when(observationSubjectSP.getPathsSplit()).thenReturn(List.of("Observation.subject"));
|
||||||
|
when(observationSubjectSP.getTargets()).thenReturn(Set.of("Patient"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Observation", "subject")).thenReturn(observationSubjectSP);
|
||||||
|
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Patient", "managingOrganization.identifier")).thenReturn(null);
|
||||||
|
|
||||||
|
RuntimeSearchParam organizationSP = mock(RuntimeSearchParam.class);
|
||||||
|
when(organizationSP.getPathsSplit()).thenReturn(List.of("Patient.managingOrganization"));
|
||||||
|
when(organizationSP.getTargets()).thenReturn(Set.of("Organization"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Patient", "managingOrganization")).thenReturn(organizationSP);
|
||||||
|
|
||||||
|
RuntimeSearchParam organizationIdentifierSP = mock(RuntimeSearchParam.class);
|
||||||
|
when(organizationIdentifierSP.getPathsSplit()).thenReturn(List.of("Organization.identifier"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Organization", "identifier")).thenReturn(organizationIdentifierSP);
|
||||||
|
|
||||||
|
List<String> result = myResourceLinkPredicateBuilder.createResourceLinkPaths(resourceType, paramName, List.of("Patient", "Organization"));
|
||||||
|
MatcherAssert.assertThat(result, Matchers.containsInAnyOrder("Observation.subject.managingOrganization.identifier"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createResourceLinkPaths_withChainAndSearchParameterFoundWithNonMatchingQualifier_returnsEmpty() {
|
||||||
|
String paramName = "subject.identifier";
|
||||||
|
String resourceType = "Observation";
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Observation", "subject.identifier")).thenReturn(null);
|
||||||
|
RuntimeSearchParam observationSubjectSP = mock(RuntimeSearchParam.class);
|
||||||
|
when(observationSubjectSP.getPathsSplit()).thenReturn(List.of("Observation.subject"));
|
||||||
|
when(observationSubjectSP.getTargets()).thenReturn(Set.of("Patient"));
|
||||||
|
when(mySearchParamRegistry.getActiveSearchParam("Observation", "subject")).thenReturn(observationSubjectSP);
|
||||||
|
List<String> result = myResourceLinkPredicateBuilder.createResourceLinkPaths(resourceType, paramName, List.of("Group"));
|
||||||
|
MatcherAssert.assertThat(result, Matchers.empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue