Do not validate auto-created placeholder resources (#3461)

* Add implementation, testing, and changelog

* add jira ref
This commit is contained in:
Tadgh 2022-03-08 14:22:04 -08:00 committed by GitHub
parent cde39dcc6c
commit 7eb1bd14d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 17 deletions

View File

@ -0,0 +1,8 @@
---
type: change
issue: 3460
jira: SMILE-3863
title: "Previously, during RepositoryValidation, we would perform validation on resources created via DaoConfig's `setAutoCreatePlaceholderReferenceTargets` property. This caused validation failures on placeholder resources
as they do not conform to any profile. This has been changed, and Repository Validation will not occur on any resource containing an extension with URL http://hapifhir.io/fhir/StructureDefinition/resource-placeholder`."

View File

@ -513,6 +513,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange()); myDaoConfig.setSuppressUpdatesWithNoChange(new DaoConfig().isSuppressUpdatesWithNoChange());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches()); myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myPagingProvider.setDefaultPageSize(BasePagingProvider.DEFAULT_DEFAULT_PAGE_SIZE); myPagingProvider.setDefaultPageSize(BasePagingProvider.DEFAULT_DEFAULT_PAGE_SIZE);
myPagingProvider.setMaximumPageSize(BasePagingProvider.DEFAULT_MAX_PAGE_SIZE); myPagingProvider.setMaximumPageSize(BasePagingProvider.DEFAULT_MAX_PAGE_SIZE);

View File

@ -48,6 +48,30 @@ public class RepositoryValidatingInterceptorHttpR4Test extends BaseJpaR4Test {
myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof RepositoryValidatingInterceptor); myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof RepositoryValidatingInterceptor);
} }
@Test
public void testValidationIsSkippedOnAutoCreatedPlaceholderReferencesIfConfiguredToDoSo() {
List<IRepositoryValidatingRule> rules = newRuleBuilder()
.forResourcesOfType("Observation")
.requireValidationToDeclaredProfiles()
.build();
myValInterceptor.setRules(rules);
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("http://foo").setCode("123").setDisplay("help im a bug");
obs.setStatus(Observation.ObservationStatus.AMENDED);
MethodOutcome outcome = myRestfulServerExtension
.getFhirClient()
.create()
.resource(obs)
.prefer(PreferReturnEnum.OPERATION_OUTCOME)
.execute();
String operationOutcomeEncoded = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
ourLog.info("Outcome: {}", operationOutcomeEncoded);
assertThat(operationOutcomeEncoded, containsString("All observations should have a subject"));
}
@Test @Test
public void testValidationOutcomeAddedToRequestResponse() { public void testValidationOutcomeAddedToRequestResponse() {
List<IRepositoryValidatingRule> rules = newRuleBuilder() List<IRepositoryValidatingRule> rules = newRuleBuilder()

View File

@ -6,14 +6,19 @@ import ca.uhn.fhir.rest.api.PatchTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType; import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Meta; import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.PractitionerRole;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -43,7 +48,6 @@ public class RepositoryValidatingInterceptorR4Test extends BaseJpaR4Test {
myValInterceptor = new RepositoryValidatingInterceptor(); myValInterceptor = new RepositoryValidatingInterceptor();
myValInterceptor.setFhirContext(myFhirContext); myValInterceptor.setFhirContext(myFhirContext);
myInterceptorRegistry.registerInterceptor(myValInterceptor); myInterceptorRegistry.registerInterceptor(myValInterceptor);
} }
@AfterEach @AfterEach
@ -99,7 +103,6 @@ public class RepositoryValidatingInterceptorR4Test extends BaseJpaR4Test {
patient.getMeta().addProfile("http://foo/Profile1"); patient.getMeta().addProfile("http://foo/Profile1");
patient.getMeta().addProfile("http://foo/Profile9999"); patient.getMeta().addProfile("http://foo/Profile9999");
myPatientDao.create(patient); myPatientDao.create(patient);
} }
@Test @Test
@ -244,19 +247,23 @@ public class RepositoryValidatingInterceptorR4Test extends BaseJpaR4Test {
} }
@Test @Test
public void testRequireValidation_Allowed() { public void testRequireValidationDoesNotApplyToPlaceholders() {
//Given
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
List<IRepositoryValidatingRule> rules = newRuleBuilder() List<IRepositoryValidatingRule> rules = newRuleBuilder()
.forResourcesOfType("Observation") .forResourcesOfType("Organization")
.requireValidationToDeclaredProfiles() .requireValidationToDeclaredProfiles()
.withBestPracticeWarningLevel("IGNORE")
.build(); .build();
myValInterceptor.setRules(rules); myValInterceptor.setRules(rules);
Observation obs = new Observation(); //When
obs.getCode().addCoding().setSystem("http://foo").setCode("123").setDisplay("help im a bug"); PractitionerRole pr = new PractitionerRole();
obs.setStatus(Observation.ObservationStatus.AMENDED); pr.setOrganization(new Reference("Organization/400-40343834-7383-54b4-abfe-95281da21062-ProviderOrganiz"));
//Then
try { try {
IIdType id = myObservationDao.create(obs).getId(); IIdType id = myPractitionerRoleDao.create(pr).getId();
assertEquals("1", id.getVersionIdPart()); assertEquals("1", id.getVersionIdPart());
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
// should not happen // should not happen
@ -264,6 +271,31 @@ public class RepositoryValidatingInterceptorR4Test extends BaseJpaR4Test {
} }
} }
@Test
public void testRequireAtLeastProfilesDoesNotApplyToPlaceholders() {
//Given
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
List<IRepositoryValidatingRule> rules = newRuleBuilder()
.forResourcesOfType("Organization")
.requireAtLeastOneProfileOf("http://example.com/profile1", "http://example.com/profile2")
.build();
myValInterceptor.setRules(rules);
//When
PractitionerRole pr = new PractitionerRole();
pr.setOrganization(new Reference("Organization/400-40343834-7383-54b4-abfe-95281da21062-ProviderOrganiz"));
//Then
try {
IIdType id = myPractitionerRoleDao.create(pr).getId();
assertEquals("1", id.getVersionIdPart());
} catch (PreconditionFailedException e) {
// should not happen
fail(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
}
}
@Test @Test
public void testRequireValidation_AdditionalOptions() { public void testRequireValidation_AdditionalOptions() {
List<IRepositoryValidatingRule> rules = newRuleBuilder() List<IRepositoryValidatingRule> rules = newRuleBuilder()

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.util.ExtensionUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.OperationOutcomeUtil;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@ -40,6 +41,8 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static ca.uhn.fhir.util.HapiExtensions.EXT_RESOURCE_PLACEHOLDER;
/** /**
* This interceptor enforces validation rules on any data saved in a HAPI FHIR JPA repository. * This interceptor enforces validation rules on any data saved in a HAPI FHIR JPA repository.
* See <a href="https://hapifhir.io/hapi-fhir/docs/validation/repository_validating_interceptor.html">Repository Validating Interceptor</a> * See <a href="https://hapifhir.io/hapi-fhir/docs/validation/repository_validating_interceptor.html">Repository Validating Interceptor</a>
@ -128,18 +131,29 @@ public class RepositoryValidatingInterceptor {
} }
private void handle(RequestDetails theRequestDetails, IBaseResource theNewResource) { private void handle(RequestDetails theRequestDetails, IBaseResource theNewResource) {
Validate.notNull(myFhirContext, "No FhirContext has been set for this interceptor of type: %s", getClass());
String resourceType = myFhirContext.getResourceType(theNewResource); Validate.notNull(myFhirContext, "No FhirContext has been set for this interceptor of type: %s", getClass());
Collection<IRepositoryValidatingRule> rules = myRules.get(resourceType); if (!isPlaceholderResource(theNewResource)) {
for (IRepositoryValidatingRule nextRule : rules) { String resourceType = myFhirContext.getResourceType(theNewResource);
IRepositoryValidatingRule.RuleEvaluation outcome = nextRule.evaluate(theRequestDetails, theNewResource); Collection<IRepositoryValidatingRule> rules = myRules.get(resourceType);
if (!outcome.isPasses()) { for (IRepositoryValidatingRule nextRule : rules) {
handleFailure(outcome); IRepositoryValidatingRule.RuleEvaluation outcome = nextRule.evaluate(theRequestDetails, theNewResource);
if (!outcome.isPasses()) {
handleFailure(outcome);
}
} }
} }
} }
/**
* Return true if the given resource is a placeholder resource, as identified by a specific extension
* @param theNewResource the {@link IBaseResource} to check
* @return whether or not this resource is a placeholder.
*/
private boolean isPlaceholderResource(IBaseResource theNewResource) {
return ExtensionUtil.hasExtension(theNewResource, EXT_RESOURCE_PLACEHOLDER);
}
protected void handleFailure(IRepositoryValidatingRule.RuleEvaluation theOutcome) { protected void handleFailure(IRepositoryValidatingRule.RuleEvaluation theOutcome) {
if (theOutcome.getOperationOutcome() != null) { if (theOutcome.getOperationOutcome() != null) {
String firstIssue = OperationOutcomeUtil.getFirstIssueDetails(myFhirContext, theOutcome.getOperationOutcome()); String firstIssue = OperationOutcomeUtil.getFirstIssueDetails(myFhirContext, theOutcome.getOperationOutcome());