Add chain on _type parameter

This commit is contained in:
jamesagnew 2020-03-23 17:53:30 -04:00
parent 14316147c5
commit 0ac434ea5d
4 changed files with 134 additions and 9 deletions

View File

@ -260,6 +260,7 @@ public class Constants {
*/ */
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source"; public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
public static final String PARAM_FHIRPATH = "_fhirpath"; public static final String PARAM_FHIRPATH = "_fhirpath";
public static final String PARAM_TYPE = "_type";
static { static {
CHARSET_UTF8 = StandardCharsets.UTF_8; CHARSET_UTF8 = StandardCharsets.UTF_8;

View File

@ -135,3 +135,6 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can no
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted! ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1} ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"

View File

@ -20,14 +20,31 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
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.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IDao;
import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId; import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
@ -47,22 +64,36 @@ import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.persistence.criteria.*; import javax.persistence.criteria.From;
import java.util.*; import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
@Component @Component
@Scope("prototype") @Scope("prototype")
@ -273,20 +304,43 @@ class PredicateBuilderReference extends BasePredicateBuilder {
} }
} else { } else {
try { try {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType()); RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType());
resourceTypes = new ArrayList<>(1); resourceTypes = new ArrayList<>(1);
resourceTypes.add(resDef.getImplementingClass()); resourceTypes.add(resDef.getImplementingClass());
} catch (DataFormatException e) { } catch (DataFormatException e) {
throw new InvalidRequestException("Invalid resource type: " + theReferenceParam.getResourceType()); throw newInvalidResourceTypeException(theReferenceParam.getResourceType());
} }
}
// Handle chain on _type
String chain = theReferenceParam.getChain();
if (Constants.PARAM_TYPE.equals(chain)) {
String typeValue = theReferenceParam.getValue();
Class<? extends IBaseResource> wantedType;
try {
wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass();
} catch (DataFormatException e) {
throw newInvalidResourceTypeException(typeValue);
}
if (!resourceTypes.contains(wantedType)) {
String searchParamName = theResourceName + ":" + theParamName;
String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", typeValue, searchParamName);
throw new InvalidRequestException(msg);
}
Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
myQueryRoot.addPredicate(targetTypeParameter);
return targetTypeParameter;
} }
boolean foundChainMatch = false; boolean foundChainMatch = false;
List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>(); List<Class<? extends IBaseResource>> candidateTargetTypes = new ArrayList<>();
for (Class<? extends IBaseResource> nextType : resourceTypes) { for (Class<? extends IBaseResource> nextType : resourceTypes) {
String chain = theReferenceParam.getChain();
String remainingChain = null; String remainingChain = null;
int chainDotIndex = chain.indexOf('.'); int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) { if (chainDotIndex != -1) {
@ -355,6 +409,12 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return predicate; return predicate;
} }
@NotNull
private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
throw new InvalidRequestException(msg);
}
private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, List<Class<? extends IBaseResource>> theCandidateTargetTypes) { private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, List<Class<? extends IBaseResource>> theCandidateTargetTypes) {
String message = new StringBuilder() String message = new StringBuilder()
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ") .append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")

View File

@ -316,6 +316,67 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(ids, empty()); assertThat(ids, empty());
} }
@Test
public void testChainOnType() {
Patient sub1 = new Patient();
sub1.setActive(true);
sub1.addIdentifier().setSystem("foo").setValue("bar");
String sub1Id = myPatientDao.create(sub1).getId().toUnqualifiedVersionless().getValue();
Group sub2 = new Group();
sub2.setActive(true);
sub2.addIdentifier().setSystem("foo").setValue("bar");
String sub2Id = myGroupDao.create(sub2).getId().toUnqualifiedVersionless().getValue();
Encounter enc1 = new Encounter();
enc1.getSubject().setReference(sub1Id);
String enc1Id = myEncounterDao.create(enc1).getId().toUnqualifiedVersionless().getValue();
Encounter enc2 = new Encounter();
enc2.getSubject().setReference(sub2Id);
String enc2Id = myEncounterDao.create(enc2).getId().toUnqualifiedVersionless().getValue();
List<String> ids;
SearchParameterMap map;
IBundleProvider results;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Patient").setChain("_type"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc1Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Group").setChain("_type"));
results = myEncounterDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, hasItems(enc2Id));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Organization").setChain("_type"));
try {
myEncounterDao.search(map);
fail();
} catch (InvalidRequestException e) {
assertEquals("Resource type \"Organization\" is not a valid target type for reference search parameter: Encounter:subject", e.getMessage());
}
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "HelpImABug").setChain("_type"));
try {
myEncounterDao.search(map);
fail();
} catch (InvalidRequestException e) {
assertEquals("Invalid/unsupported resource type: \"HelpImABug\"", e.getMessage());
}
}
/** /**
* See #441 * See #441
*/ */