Better error message for unqualified search parameter types

This commit is contained in:
James Agnew 2018-12-03 11:36:09 -05:00
parent 82f40f0423
commit c484c69664
5 changed files with 144 additions and 39 deletions

View File

@ -49,9 +49,8 @@ public class RuntimeChildResourceDefinition extends BaseRuntimeDeclaredChildDefi
myResourceTypes = theResourceTypes;
if (theResourceTypes == null || theResourceTypes.isEmpty()) {
myResourceTypes = new ArrayList<Class<? extends IBaseResource>>();
myResourceTypes = new ArrayList<>();
myResourceTypes.add(IBaseResource.class);
// throw new ConfigurationException("Field '" + theField.getName() + "' on type '" + theField.getDeclaringClass().getCanonicalName() + "' has no resource types noted");
}
}

View File

@ -1,9 +1,11 @@
package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IIdType;
import java.util.Collection;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.primitive.IdDt;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
@ -39,16 +41,42 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
return myName;
}
/**
* Include a chained search. For example:
* <pre>
* Bundle resp = ourClient
* .search()
* .forResource(QuestionnaireResponse.class)
* .where(QuestionnaireResponse.SUBJECT.hasChainedProperty(Patient.FAMILY.matches().value("SMITH")))
* .returnBundle(Bundle.class)
* .execute();
* </pre>
*/
public ICriterion<ReferenceClientParam> hasChainedProperty(ICriterion<?> theCriterion) {
return new ReferenceChainCriterion(getParamName(), theCriterion);
}
/**
* Include a chained search with a resource type. For example:
* <pre>
* Bundle resp = ourClient
* .search()
* .forResource(QuestionnaireResponse.class)
* .where(QuestionnaireResponse.SUBJECT.hasChainedProperty("Patient", Patient.FAMILY.matches().value("SMITH")))
* .returnBundle(Bundle.class)
* .execute();
* </pre>
*/
public ICriterion<ReferenceClientParam> hasChainedProperty(String theResourceType, ICriterion<?> theCriterion) {
return new ReferenceChainCriterion(getParamName(), theResourceType, theCriterion);
}
/**
* Match the referenced resource if the resource has the given ID (this can be
* the logical ID or the absolute URL of the resource)
*/
public ICriterion<ReferenceClientParam> hasId(IdDt theId) {
return new StringCriterion<ReferenceClientParam>(getParamName(), theId.getValue());
public ICriterion<ReferenceClientParam> hasId(IIdType theId) {
return new StringCriterion<>(getParamName(), theId.getValue());
}
/**
@ -56,7 +84,7 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
* the logical ID or the absolute URL of the resource)
*/
public ICriterion<ReferenceClientParam> hasId(String theId) {
return new StringCriterion<ReferenceClientParam>(getParamName(), theId);
return new StringCriterion<>(getParamName(), theId);
}
/**
@ -67,22 +95,28 @@ public class ReferenceClientParam extends BaseClientParam implements IParam {
* with the same parameter.
*/
public ICriterion<ReferenceClientParam> hasAnyOfIds(Collection<String> theIds) {
return new StringCriterion<ReferenceClientParam>(getParamName(), theIds);
return new StringCriterion<>(getParamName(), theIds);
}
private static class ReferenceChainCriterion implements ICriterion<ReferenceClientParam>, ICriterionInternal {
private final String myResourceTypeQualifier;
private String myParamName;
private ICriterionInternal myWrappedCriterion;
public ReferenceChainCriterion(String theParamName, ICriterion<?> theWrappedCriterion) {
ReferenceChainCriterion(String theParamName, ICriterion<?> theWrappedCriterion) {
this(theParamName, null, theWrappedCriterion);
}
ReferenceChainCriterion(String theParamName, String theResourceType, ICriterion<?> theWrappedCriterion) {
myParamName = theParamName;
myResourceTypeQualifier = isNotBlank(theResourceType) ? ":" + theResourceType : "";
myWrappedCriterion = (ICriterionInternal) theWrappedCriterion;
}
@Override
public String getParameterName() {
return myParamName + "." + myWrappedCriterion.getParameterName();
return myParamName + myResourceTypeQualifier + "." + myWrappedCriterion.getParameterName();
}
@Override

View File

@ -462,6 +462,11 @@ public class SearchBuilder implements ISearchBuilder {
} else if (def instanceof RuntimeChildResourceDefinition) {
RuntimeChildResourceDefinition resDef = (RuntimeChildResourceDefinition) def;
resourceTypes.addAll(resDef.getResourceTypes());
if (resourceTypes.size() == 1) {
if (resourceTypes.get(0).isInterface()) {
throw new InvalidRequestException("Unable to perform search for unqualified chain '" + theParamName + "' as this SearchParameter does not declare any target types. Add a qualifier of the form '" + theParamName + ":[ResourceType]' to perform this search.");
}
}
} else {
throw new ConfigurationException("Property " + paramPath + " of type " + myResourceName + " is not a resource: " + def.getClass());
}
@ -479,10 +484,14 @@ public class SearchBuilder implements ISearchBuilder {
resourceId = ref.getValue();
} else {
try {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(ref.getResourceType());
resourceTypes = new ArrayList<>(1);
resourceTypes.add(resDef.getImplementingClass());
resourceId = ref.getIdPart();
} catch (DataFormatException e) {
throw new InvalidRequestException("Invalid resource type: " + ref.getResourceType());
}
}
boolean foundChainMatch = false;

View File

@ -75,11 +75,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderDstu3Test.class);
private SearchCoordinatorSvcImpl mySearchCoordinatorSvcRaw;
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@Override
@After
public void after() throws Exception {
@ -240,6 +235,59 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
}
}
@Test
public void testSearchChainedReference() {
Patient p = new Patient();
p.addName().setFamily("SMITH");
IIdType pid = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
QuestionnaireResponse qr = new QuestionnaireResponse();
qr.getSubject().setReference(pid.getValue());
ourClient.create().resource(qr).execute();
Subscription subs = new Subscription();
subs.setStatus(SubscriptionStatus.ACTIVE);
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
subs.setCriteria("Observation?");
IIdType id = ourClient.create().resource(subs).execute().getId().toUnqualifiedVersionless();
// Unqualified (doesn't work because QuestionnaireRespone.subject is a Refercence(Any))
try {
ourClient
.search()
.forResource(QuestionnaireResponse.class)
.where(QuestionnaireResponse.SUBJECT.hasChainedProperty(Patient.FAMILY.matches().value("SMITH")))
.returnBundle(Bundle.class)
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Unable to perform search for unqualified chain 'subject' as this SearchParameter does not declare any target types. Add a qualifier of the form 'subject:[ResourceType]' to perform this search.", e.getMessage());
}
// Qualified
Bundle resp = ourClient
.search()
.forResource(QuestionnaireResponse.class)
.where(QuestionnaireResponse.SUBJECT.hasChainedProperty("Patient", Patient.FAMILY.matches().value("SMITH")))
.returnBundle(Bundle.class)
.execute();
assertEquals(1, resp.getEntry().size());
// Qualified With an invalid name
try {
ourClient
.search()
.forResource(QuestionnaireResponse.class)
.where(QuestionnaireResponse.SUBJECT.hasChainedProperty("FOO", Patient.FAMILY.matches().value("SMITH")))
.returnBundle(Bundle.class)
.execute();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid resource type: FOO", e.getMessage());
}
}
@Test
public void testCodeSearch() {
Subscription subs = new Subscription();
@ -1588,6 +1636,25 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(77, ids.size());
}
@Test
public void testEverythingWithOnlyPatient() {
Patient p = new Patient();
p.setActive(true);
IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
myFhirCtx.getRestfulClientFactory().setSocketTimeout(300 * 1000);
Bundle response = ourClient
.operation()
.onInstance(id)
.named("everything")
.withNoParameters(Parameters.class)
.returnResourceType(Bundle.class)
.execute();
assertEquals(1, response.getEntry().size());
}
// private void delete(String theResourceType, String theParamName, String theParamValue) {
// Bundle resources;
// do {
@ -1613,25 +1680,6 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
// }
// }
@Test
public void testEverythingWithOnlyPatient() {
Patient p = new Patient();
p.setActive(true);
IIdType id = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
myFhirCtx.getRestfulClientFactory().setSocketTimeout(300 * 1000);
Bundle response = ourClient
.operation()
.onInstance(id)
.named("everything")
.withNoParameters(Parameters.class)
.returnResourceType(Bundle.class)
.execute();
assertEquals(1, response.getEntry().size());
}
@SuppressWarnings("unused")
@Test
public void testFullTextSearch() throws Exception {
@ -4293,4 +4341,9 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
return new InstantDt(theDate).getValueAsString();
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -102,6 +102,16 @@
Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL
statements will be logged at the end of the run.
</action>
<action type="add">
In the JPA server, when performing a chained reference search on a search parameter with
a target type of
<![CDATA[<code>Reference(Any)</code>]]>, the search failed with an incomprehensible
error. This has been corrected to return an error message indicating that the chain
must be qualified with a resource type for such a field. For example,
<![CDATA[<code>QuestionnaireResponse?subject:Patient.name=smith</code>]]>
instead of
<![CDATA[<code>QuestionnaireResponse?subject.name=smith</code>]]>.
</action>
</release>
<release version="3.6.0" date="2018-11-12" description="Food">
<action type="add">