Do not validate auto-created placeholder resources (#3461)
* Add implementation, testing, and changelog * add jira ref
This commit is contained in:
parent
cde39dcc6c
commit
7eb1bd14d1
|
@ -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`."
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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());
|
||||||
|
|
Loading…
Reference in New Issue