Custom search parameters could not chain an extension param

This commit is contained in:
James Agnew 2017-05-17 10:46:22 -04:00
parent 5ac91bfb94
commit 7bb9e5edd9
4 changed files with 336 additions and 210 deletions

View File

@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substring;
import java.math.BigDecimal;
import java.math.MathContext;
@ -67,15 +68,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
@ -438,27 +431,57 @@ public class SearchBuilder implements ISearchBuilder {
} else {
List<Class<? extends IBaseResource>> resourceTypes;
final List<Class<? extends IBaseResource>> resourceTypes;
String resourceId;
if (!ref.getValue().matches("[a-zA-Z]+\\/.*")) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType);
String paramPath = myCallingDao.getSearchParamByName(resourceDef, theParamName).getPath();
if (paramPath.endsWith(".as(Reference)")) {
paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference";
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(myResourceName, theParamName);
resourceTypes = new ArrayList<Class<? extends IBaseResource>>();
Set<String> targetTypes = param.getTargets();
if (targetTypes != null && !targetTypes.isEmpty()) {
for (String next : targetTypes) {
resourceTypes.add(myContext.getResourceDefinition(next).getImplementingClass());
}
}
BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, paramPath);
if (def instanceof RuntimeChildChoiceDefinition) {
RuntimeChildChoiceDefinition choiceDef = (RuntimeChildChoiceDefinition) def;
resourceTypes = choiceDef.getResourceTypes();
} else if (def instanceof RuntimeChildResourceDefinition) {
RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def;
resourceTypes = resDef.getResourceTypes();
} else {
throw new ConfigurationException("Property " + paramPath + " of type " + myResourceName + " is not a resource: " + def.getClass());
if (resourceTypes.isEmpty()) {
RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceType);
String paramPath = myCallingDao.getSearchParamByName(resourceDef, theParamName).getPath();
if (paramPath.endsWith(".as(Reference)")) {
paramPath = paramPath.substring(0, paramPath.length() - ".as(Reference)".length()) + "Reference";
}
if (paramPath.contains(".extension(")) {
int startIdx = paramPath.indexOf(".extension(");
int endIdx = paramPath.indexOf(')', startIdx);
if (startIdx != -1 && endIdx != -1) {
paramPath = paramPath.substring(0, startIdx + 10) + paramPath.substring(endIdx + 1);
}
}
BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, paramPath);
if (def instanceof RuntimeChildChoiceDefinition) {
RuntimeChildChoiceDefinition choiceDef = (RuntimeChildChoiceDefinition) def;
resourceTypes.addAll(choiceDef.getResourceTypes());
} else if (def instanceof RuntimeChildResourceDefinition) {
RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def;
resourceTypes.addAll(resDef.getResourceTypes());
} else {
throw new ConfigurationException("Property " + paramPath + " of type " + myResourceName + " is not a resource: " + def.getClass());
}
}
if (resourceTypes.isEmpty()) {
for (BaseRuntimeElementDefinition<?> next : myContext.getElementDefinitions()) {
if (next instanceof RuntimeResourceDefinition) {
RuntimeResourceDefinition nextResDef = (RuntimeResourceDefinition)next;
resourceTypes.add(nextResDef.getImplementingClass());
}
}
}
resourceId = ref.getValue();
} else {
@ -888,32 +911,32 @@ public class SearchBuilder implements ISearchBuilder {
private Predicate createCompositeParamPart(String theResourceName, Root<ResourceTable> theRoot, RuntimeSearchParam theParam, IQueryParameterType leftValue) {
Predicate retVal = null;
switch (theParam.getParamType()) {
case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin);
break;
}
case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
retVal = createPredicateToken(leftValue, theResourceName, theParam.getName(), myBuilder, tokenJoin);
break;
}
case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case COMPOSITE:
case HAS:
case NUMBER:
case REFERENCE:
case URI:
break;
case STRING: {
From<ResourceIndexedSearchParamString, ResourceIndexedSearchParamString> stringJoin = theRoot.join("myParamsString", JoinType.INNER);
retVal = createPredicateString(leftValue, theResourceName, theParam.getName(), myBuilder, stringJoin);
break;
}
case TOKEN: {
From<ResourceIndexedSearchParamToken, ResourceIndexedSearchParamToken> tokenJoin = theRoot.join("myParamsToken", JoinType.INNER);
retVal = createPredicateToken(leftValue, theResourceName, theParam.getName(), myBuilder, tokenJoin);
break;
}
case DATE: {
From<ResourceIndexedSearchParamDate, ResourceIndexedSearchParamDate> dateJoin = theRoot.join("myParamsDate", JoinType.INNER);
retVal = createPredicateDate(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case QUANTITY: {
From<ResourceIndexedSearchParamQuantity, ResourceIndexedSearchParamQuantity> dateJoin = theRoot.join("myParamsQuantity", JoinType.INNER);
retVal = createPredicateQuantity(leftValue, theResourceName, theParam.getName(), myBuilder, dateJoin);
break;
}
case COMPOSITE:
case HAS:
case NUMBER:
case REFERENCE:
case URI:
break;
}
if (retVal == null) {
@ -984,41 +1007,41 @@ public class SearchBuilder implements ISearchBuilder {
String invalidMessageName) {
Predicate num;
switch (thePrefix) {
case GREATERTHAN:
num = builder.gt(thePath, theValue);
break;
case GREATERTHAN_OR_EQUALS:
num = builder.ge(thePath, theValue);
break;
case LESSTHAN:
num = builder.lt(thePath, theValue);
break;
case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue);
break;
case APPROXIMATE:
case EQUAL:
case NOT_EQUAL:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred;
Predicate highPred;
if (thePrefix != ParamPrefixEnum.NOT_EQUAL) {
lowPred = builder.ge(thePath.as(BigDecimal.class), low);
highPred = builder.le(thePath.as(BigDecimal.class), high);
num = builder.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high);
} else {
// Prefix was "ne", so reverse it!
lowPred = builder.lt(thePath.as(BigDecimal.class), low);
highPred = builder.gt(thePath.as(BigDecimal.class), high);
num = builder.or(lowPred, highPred);
}
break;
default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg);
case GREATERTHAN:
num = builder.gt(thePath, theValue);
break;
case GREATERTHAN_OR_EQUALS:
num = builder.ge(thePath, theValue);
break;
case LESSTHAN:
num = builder.lt(thePath, theValue);
break;
case LESSTHAN_OR_EQUALS:
num = builder.le(thePath, theValue);
break;
case APPROXIMATE:
case EQUAL:
case NOT_EQUAL:
BigDecimal mul = calculateFuzzAmount(thePrefix, theValue);
BigDecimal low = theValue.subtract(mul, MathContext.DECIMAL64);
BigDecimal high = theValue.add(mul, MathContext.DECIMAL64);
Predicate lowPred;
Predicate highPred;
if (thePrefix != ParamPrefixEnum.NOT_EQUAL) {
lowPred = builder.ge(thePath.as(BigDecimal.class), low);
highPred = builder.le(thePath.as(BigDecimal.class), high);
num = builder.and(lowPred, highPred);
ourLog.trace("Searching for {} <= val <= {}", low, high);
} else {
// Prefix was "ne", so reverse it!
lowPred = builder.lt(thePath.as(BigDecimal.class), low);
highPred = builder.gt(thePath.as(BigDecimal.class), high);
num = builder.or(lowPred, highPred);
}
break;
default:
String msg = myContext.getLocalizer().getMessage(SearchBuilder.class, invalidMessageName, thePrefix.getValue(), theParam.getValueAsQueryToken(myContext));
throw new InvalidRequestException(msg);
}
if (theParamName == null) {
@ -1388,36 +1411,36 @@ public class SearchBuilder implements ISearchBuilder {
String[] sortAttrName;
switch (param.getParamType()) {
case STRING:
joinAttrName = "myParamsString";
sortAttrName = new String[] { "myValueExact" };
break;
case DATE:
joinAttrName = "myParamsDate";
sortAttrName = new String[] { "myValueLow" };
break;
case REFERENCE:
joinAttrName = "myResourceLinks";
sortAttrName = new String[] { "myTargetResourcePid" };
break;
case TOKEN:
joinAttrName = "myParamsToken";
sortAttrName = new String[] { "mySystem", "myValue" };
break;
case NUMBER:
joinAttrName = "myParamsNumber";
sortAttrName = new String[] { "myValue" };
break;
case URI:
joinAttrName = "myParamsUri";
sortAttrName = new String[] { "myUri" };
break;
case QUANTITY:
joinAttrName = "myParamsQuantity";
sortAttrName = new String[] { "myValue" };
break;
default:
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName());
case STRING:
joinAttrName = "myParamsString";
sortAttrName = new String[] { "myValueExact" };
break;
case DATE:
joinAttrName = "myParamsDate";
sortAttrName = new String[] { "myValueLow" };
break;
case REFERENCE:
joinAttrName = "myResourceLinks";
sortAttrName = new String[] { "myTargetResourcePid" };
break;
case TOKEN:
joinAttrName = "myParamsToken";
sortAttrName = new String[] { "mySystem", "myValue" };
break;
case NUMBER:
joinAttrName = "myParamsNumber";
sortAttrName = new String[] { "myValue" };
break;
case URI:
joinAttrName = "myParamsUri";
sortAttrName = new String[] { "myUri" };
break;
case QUANTITY:
joinAttrName = "myParamsQuantity";
sortAttrName = new String[] { "myValue" };
break;
default:
throw new InvalidRequestException("This server does not support _sort specifications of type " + param.getParamType() + " - Can't serve _sort=" + theSort.getParamName());
}
From<?, ?> join = theFrom.join(joinAttrName, JoinType.LEFT);
@ -1500,10 +1523,10 @@ public class SearchBuilder implements ISearchBuilder {
for (int i = 0; i < pids.size(); i += maxLoad) {
int to = i + maxLoad;
to = Math.min(to, pids.size());
List<Long> pidsSubList = pids.subList(i, to);
List<Long> pidsSubList = pids.subList(i, to);
doLoadPids(theResourceListToPopulate, theRevIncludedPids, theForHistoryOperation, entityManager, context, theDao, position, pidsSubList);
}
}
}
private void doLoadPids(List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation, EntityManager entityManager, FhirContext context, IDao theDao,
@ -1724,49 +1747,49 @@ public class SearchBuilder implements ISearchBuilder {
RuntimeSearchParam nextParamDef = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName);
if (nextParamDef != null) {
switch (nextParamDef.getParamType()) {
case DATE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateDate(theResourceName, theParamName, nextAnd);
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateQuantity(theResourceName, theParamName, nextAnd);
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateReference(theResourceName, theParamName, nextAnd);
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateString(theResourceName, theParamName, nextAnd);
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateToken(theResourceName, theParamName, nextAnd);
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateNumber(theResourceName, theParamName, nextAnd);
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateComposite(theResourceName, nextParamDef, nextAnd);
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateUri(theResourceName, theParamName, nextAnd);
}
break;
case HAS:
// should not happen
break;
case DATE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateDate(theResourceName, theParamName, nextAnd);
}
break;
case QUANTITY:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateQuantity(theResourceName, theParamName, nextAnd);
}
break;
case REFERENCE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateReference(theResourceName, theParamName, nextAnd);
}
break;
case STRING:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateString(theResourceName, theParamName, nextAnd);
}
break;
case TOKEN:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateToken(theResourceName, theParamName, nextAnd);
}
break;
case NUMBER:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateNumber(theResourceName, theParamName, nextAnd);
}
break;
case COMPOSITE:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateComposite(theResourceName, nextParamDef, nextAnd);
}
break;
case URI:
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
addPredicateUri(theResourceName, theParamName, nextAnd);
}
break;
case HAS:
// should not happen
break;
}
} else {
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
@ -1786,35 +1809,35 @@ public class SearchBuilder implements ISearchBuilder {
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
case DATE:
qp = new DateParam();
break;
case NUMBER:
qp = new NumberParam();
break;
case QUANTITY:
qp = new QuantityParam();
break;
case STRING:
qp = new StringParam();
break;
case TOKEN:
qp = new TokenParam();
break;
case COMPOSITE:
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
if (compositeOf.size() != 2) {
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
}
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
qp = new CompositeParam<IQueryParameterType, IQueryParameterType>(leftParam, rightParam);
break;
case REFERENCE:
qp = new ReferenceParam();
break;
default:
throw new InternalErrorException("Don't know how to convert param type: " + theParam.getParamType());
}
return qp;
}
@ -1908,8 +1931,8 @@ public class SearchBuilder implements ISearchBuilder {
}
private void fetchNext() {
// If we don't have
// If we don't have
if (myResultsIterator == null) {
final TypedQuery<Long> query = createQuery(mySort);
myResultsIterator = query.getResultList().iterator();

View File

@ -38,7 +38,7 @@ public abstract class BaseResourceIndexedSearchParam implements Serializable {
// TODO: make this nullable=false and a primitive (written may 2017)
@Field()
@Column(name = "SP_MISSING", nullable = true)
private Boolean myMissing;
private Boolean myMissing = Boolean.FALSE;
@Field
@Column(name = "SP_NAME", length = MAX_SP_NAME, nullable = false)

View File

@ -201,35 +201,134 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu
}
@Test
public void testSearchForExtensionReference() {
SearchParameter eyeColourSp = new SearchParameter();
eyeColourSp.addBase("Patient");
eyeColourSp.setCode("sibling");
eyeColourSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.REFERENCE);
eyeColourSp.setTitle("Sibling");
eyeColourSp.setExpression("Patient.extension('http://acme.org/sibling')");
eyeColourSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
eyeColourSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
public void testSearchForExtensionReferenceWithNonMatchingTarget() {
SearchParameter siblingSp = new SearchParameter();
siblingSp.addBase("Patient");
siblingSp.setCode("sibling");
siblingSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.REFERENCE);
siblingSp.setTitle("Sibling");
siblingSp.setExpression("Patient.extension('http://acme.org/sibling')");
siblingSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
siblingSp.getTarget().add(new CodeType("Organization"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
Patient p1 = new Patient();
p1.setActive(true);
p1.addName().setFamily("P1");
IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless();
Patient p2 = new Patient();
p2.setActive(true);
p2.addName().setFamily("P2");
p2.addExtension().setUrl("http://acme.org/sibling").setValue(new Reference(p1id));
IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless();
// Try with custom gender SP
SearchParameterMap map = new SearchParameterMap();
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
// Search by ref
map = new SearchParameterMap();
map.add("sibling", new ReferenceParam(p1id.getValue()));
IBundleProvider results = myPatientDao.search(map);
List<String> foundResources = toUnqualifiedVersionlessIdValues(results);
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, empty());
// Search by chain
map = new SearchParameterMap();
map.add("sibling", new ReferenceParam("name", "P1"));
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, empty());
}
@Test
public void testSearchForExtensionReferenceWithoutTarget() {
SearchParameter siblingSp = new SearchParameter();
siblingSp.addBase("Patient");
siblingSp.setCode("sibling");
siblingSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.REFERENCE);
siblingSp.setTitle("Sibling");
siblingSp.setExpression("Patient.extension('http://acme.org/sibling')");
siblingSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless();
Patient p2 = new Patient();
p2.addName().setFamily("P2");
p2.addExtension().setUrl("http://acme.org/sibling").setValue(new Reference(p1id));
IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless();
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
// Search by ref
map = new SearchParameterMap();
map.add("sibling", new ReferenceParam(p1id.getValue()));
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, contains(p2id.getValue()));
// Search by chain
map = new SearchParameterMap();
map.add("sibling", new ReferenceParam("name", "P1"));
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, contains(p2id.getValue()));
}
@Test
public void testSearchForExtensionReferenceWithTarget() {
SearchParameter siblingSp = new SearchParameter();
siblingSp.addBase("Patient");
siblingSp.setCode("sibling");
siblingSp.setType(org.hl7.fhir.dstu3.model.Enumerations.SearchParamType.REFERENCE);
siblingSp.setTitle("Sibling");
siblingSp.setExpression("Patient.extension('http://acme.org/sibling')");
siblingSp.setXpathUsage(org.hl7.fhir.dstu3.model.SearchParameter.XPathUsageType.NORMAL);
siblingSp.setStatus(org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus.ACTIVE);
siblingSp.getTarget().add(new CodeType("Patient"));
mySearchParameterDao.create(siblingSp, mySrd);
mySearchParamRegsitry.forceRefresh();
Patient p1 = new Patient();
p1.addName().setFamily("P1");
IIdType p1id = myPatientDao.create(p1).getId().toUnqualifiedVersionless();
Patient p2 = new Patient();
p2.addName().setFamily("P2");
p2.addExtension().setUrl("http://acme.org/sibling").setValue(new Reference(p1id));
IIdType p2id = myPatientDao.create(p2).getId().toUnqualifiedVersionless();
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
// Search by ref
map = new SearchParameterMap();
map.add("sibling", new ReferenceParam(p1id.getValue()));
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, contains(p2id.getValue()));
// Search by chain
map = new SearchParameterMap();
map.add("sibling", new ReferenceParam("name", "P1"));
results = myPatientDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, contains(p2id.getValue()));
}

View File

@ -93,6 +93,10 @@
will live after all. Interceptors registered to this method will now be treated
appropriately if they implement IServerOperationInterceptor too.
</action>
<action type="fix">
JPA server did not correctly support searching on a custom search parameter whose
path pointed to an extension, where the client used a chained value.
</action>
</release>
<release version="2.4" date="2017-04-19">
<action type="add">