Skip to content

Commit 00b1618

Browse files
authored
Resource Creation fails due to reference validation when endpoint validating and partitioning is enabled. (#6952)
1 parent 0e63d5d commit 00b1618

File tree

8 files changed

+102
-16
lines changed

8 files changed

+102
-16
lines changed

hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ValidateUtil.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import static org.apache.commons.lang3.StringUtils.length;
2929

3030
public class ValidateUtil {
31-
3231
/**
3332
* Throws {@link IllegalArgumentException} if theValue is less than or equal to theMinimum
3433
*/

hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/ValidationOptions.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@
2929

3030
public class ValidationOptions {
3131

32-
private static ValidationOptions ourEmpty;
3332
private Set<String> myProfiles;
3433

34+
/**
35+
* Context for the validation (a RequestDetails object)
36+
*/
37+
private Object myAppContext;
38+
3539
public ValidationOptions() {}
3640

3741
public Set<String> getProfiles() {
@@ -55,13 +59,22 @@ public ValidationOptions addProfileIfNotBlank(String theProfileUri) {
5559
return this;
5660
}
5761

62+
public ValidationOptions setAppContext(Object theContext) {
63+
myAppContext = theContext;
64+
return this;
65+
}
66+
67+
/**
68+
* Returns the AppContext (RequestDetails) set to this options object.
69+
* Can be null.
70+
*/
71+
public Object getAppContext() {
72+
return myAppContext;
73+
}
74+
5875
public static ValidationOptions empty() {
59-
ValidationOptions retVal = ourEmpty;
60-
if (retVal == null) {
61-
retVal = new ValidationOptions();
62-
retVal.myProfiles = Collections.emptySet();
63-
ourEmpty = retVal;
64-
}
65-
return retVal;
76+
ValidationOptions retval = new ValidationOptions();
77+
retval.myProfiles = Collections.emptySet();
78+
return retval;
6679
}
6780
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
type: fix
3+
issue: 6951
4+
title: "Validators not passing the RequestDetails object through
5+
would result in validation being unable to resolve
6+
resource references when partitioning is used.
7+
This has been resolved.
8+
"

hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BaseValidatingInterceptor.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import ca.uhn.fhir.validation.IValidatorModule;
3434
import ca.uhn.fhir.validation.ResultSeverityEnum;
3535
import ca.uhn.fhir.validation.SingleValidationMessage;
36+
import ca.uhn.fhir.validation.ValidationOptions;
3637
import ca.uhn.fhir.validation.ValidationResult;
3738
import org.apache.commons.lang3.Validate;
3839
import org.apache.commons.lang3.text.StrLookup;
@@ -121,7 +122,12 @@ public void setValidator(FhirValidator theValidator) {
121122
myValidator = theValidator;
122123
}
123124

124-
abstract ValidationResult doValidate(FhirValidator theValidator, T theRequest);
125+
@Deprecated
126+
public ValidationResult doValidate(FhirValidator theValidator, T theRequest) {
127+
return doValidate(theValidator, theRequest, ValidationOptions.empty());
128+
}
129+
130+
abstract ValidationResult doValidate(FhirValidator theValidator, T theRequest, ValidationOptions theOptions);
125131

126132
/**
127133
* Fail the request by throwing an {@link UnprocessableEntityException} as a result of a validation failure.
@@ -345,7 +351,9 @@ protected ValidationResult validate(T theRequest, RequestDetails theRequestDetai
345351

346352
ValidationResult validationResult;
347353
try {
348-
validationResult = doValidate(validator, theRequest);
354+
ValidationOptions options = new ValidationOptions();
355+
options.setAppContext(theRequestDetails);
356+
validationResult = doValidate(validator, theRequest, options);
349357
} catch (Exception e) {
350358
if (myIgnoreValidatorExceptions) {
351359
ourLog.warn("Validator threw an exception during validation", e);

hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import ca.uhn.fhir.rest.server.method.ResourceParameter;
3030
import ca.uhn.fhir.validation.FhirValidator;
3131
import ca.uhn.fhir.validation.ResultSeverityEnum;
32+
import ca.uhn.fhir.validation.ValidationOptions;
3233
import ca.uhn.fhir.validation.ValidationResult;
3334
import jakarta.servlet.http.HttpServletRequest;
3435
import jakarta.servlet.http.HttpServletResponse;
@@ -54,8 +55,8 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
5455
private boolean myAddValidationResultsToResponseOperationOutcome = true;
5556

5657
@Override
57-
ValidationResult doValidate(FhirValidator theValidator, String theRequest) {
58-
return theValidator.validateWithResult(theRequest);
58+
ValidationResult doValidate(FhirValidator theValidator, String theRequest, ValidationOptions theOptions) {
59+
return theValidator.validateWithResult(theRequest, theOptions);
5960
}
6061

6162
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)

hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseValidatingInterceptor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
2727
import ca.uhn.fhir.validation.FhirValidator;
2828
import ca.uhn.fhir.validation.ResultSeverityEnum;
29+
import ca.uhn.fhir.validation.ValidationOptions;
2930
import ca.uhn.fhir.validation.ValidationResult;
3031
import org.apache.commons.lang3.Validate;
3132
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -61,8 +62,8 @@ public void addExcludeOperationType(RestOperationTypeEnum theOperationType) {
6162
}
6263

6364
@Override
64-
ValidationResult doValidate(FhirValidator theValidator, IBaseResource theRequest) {
65-
return theValidator.validateWithResult(theRequest);
65+
ValidationResult doValidate(FhirValidator theValidator, IBaseResource theRequest, ValidationOptions theOptions) {
66+
return theValidator.validateWithResult(theRequest, theOptions);
6667
}
6768

6869
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)

hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import ca.uhn.fhir.util.Logs;
77
import ca.uhn.fhir.util.XmlUtil;
88
import ca.uhn.fhir.validation.IValidationContext;
9+
import ca.uhn.fhir.validation.ValidationOptions;
910
import com.google.gson.Gson;
1011
import com.google.gson.GsonBuilder;
1112
import com.google.gson.JsonArray;
@@ -203,7 +204,8 @@ public List<ValidationMessage> validate(
203204
}
204205

205206
Manager.FhirFormat format = Manager.FhirFormat.JSON;
206-
v.validate(null, messages, inputStream, format, profiles);
207+
ValidationOptions options = theValidationContext.getOptions();
208+
v.validate(options.getAppContext(), messages, inputStream, format, profiles);
207209
} else {
208210
throw new IllegalArgumentException(Msg.code(649) + "Unknown encoding: " + encoding);
209211
}

hapi-fhir-validation/src/test/java/ca/uhn/fhir/rest/server/RequestValidatingInterceptorR4Test.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import ca.uhn.fhir.rest.api.Constants;
1717
import ca.uhn.fhir.rest.api.MethodOutcome;
1818
import ca.uhn.fhir.rest.api.RequestTypeEnum;
19+
import ca.uhn.fhir.rest.api.server.RequestDetails;
1920
import ca.uhn.fhir.rest.param.StringParam;
2021
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
2122
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
@@ -24,6 +25,7 @@
2425
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
2526
import ca.uhn.fhir.util.TestUtil;
2627
import ca.uhn.fhir.util.UrlUtil;
28+
import ca.uhn.fhir.validation.FhirValidator;
2729
import ca.uhn.fhir.validation.IValidationContext;
2830
import ca.uhn.fhir.validation.IValidatorModule;
2931
import ca.uhn.fhir.validation.ResultSeverityEnum;
@@ -43,20 +45,30 @@
4345
import org.hl7.fhir.r4.model.IdType;
4446
import org.hl7.fhir.r4.model.Narrative;
4547
import org.hl7.fhir.r4.model.Patient;
48+
import org.hl7.fhir.r4.model.Reference;
49+
import org.hl7.fhir.r5.elementmodel.Element;
50+
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
51+
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
52+
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
4653
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
4754
import org.junit.jupiter.api.AfterAll;
4855
import org.junit.jupiter.api.BeforeEach;
4956
import org.junit.jupiter.api.Order;
5057
import org.junit.jupiter.api.Test;
5158
import org.junit.jupiter.api.extension.RegisterExtension;
59+
import org.mockito.ArgumentCaptor;
5260
import org.mockito.Mockito;
5361

5462
import java.io.IOException;
5563
import java.util.ArrayList;
64+
import java.util.EnumSet;
5665

5766
import static org.assertj.core.api.Assertions.assertThat;
5867
import static org.junit.jupiter.api.Assertions.assertEquals;
68+
import static org.mockito.ArgumentMatchers.any;
69+
import static org.mockito.ArgumentMatchers.anyString;
5970
import static org.mockito.Mockito.mock;
71+
import static org.mockito.Mockito.when;
6072

6173
public class RequestValidatingInterceptorR4Test extends BaseValidationTestWithInlineMocks {
6274
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RequestValidatingInterceptorR4Test.class);
@@ -91,6 +103,31 @@ public void before() {
91103
ourPort = ourServlet.getPort();
92104
}
93105

106+
@Test
107+
public void testCreateResource_whenResourceHasReference_willValidateRefWithRequestDetails() throws IOException {
108+
ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);
109+
110+
FhirValidator fhirValidator = createFhirValidatorWithReferenceChecking(argumentCaptor);
111+
112+
myInterceptor.setFailOnSeverity(null);
113+
myInterceptor.setAddResponseHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
114+
myInterceptor.setValidator(fhirValidator);
115+
116+
Patient patient = new Patient();
117+
patient.setManagingOrganization(new Reference("Organization/123"));
118+
String encoded = ourCtx.newJsonParser().encodeResourceToString(patient);
119+
120+
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
121+
httpPost.setEntity(new StringEntity(encoded, ContentType.create(Constants.CT_FHIR_JSON, "UTF-8")));
122+
123+
HttpResponse status = ourClient.getClient().execute(httpPost);
124+
IOUtils.closeQuietly(status.getEntity().getContent());
125+
126+
ourLog.info("Response was:\n{}", status);
127+
128+
assertThat(argumentCaptor.getValue()).isInstanceOf(RequestDetails.class);
129+
}
130+
94131
@Test
95132
public void testCreateJsonInvalidNoFailure() throws Exception {
96133
myInterceptor.setFailOnSeverity(null);
@@ -544,6 +581,23 @@ public void testSearch() throws Exception {
544581
assertEquals(true, ourLastRequestWasSearch);
545582
}
546583

584+
private FhirValidator createFhirValidatorWithReferenceChecking(ArgumentCaptor<Object> theArgumentCaptor) throws IOException {
585+
IValidatorResourceFetcher validatorResourceFetcher = mock(IValidatorResourceFetcher.class);
586+
IValidationPolicyAdvisor policyAdvisor = mock(IValidationPolicyAdvisor.class);
587+
588+
FhirValidator fhirValidator = ourCtx.newValidator();
589+
FhirInstanceValidator instanceValidatorModule = new FhirInstanceValidator(ourCtx);
590+
instanceValidatorModule.setValidatorResourceFetcher(validatorResourceFetcher);
591+
instanceValidatorModule.setValidatorPolicyAdvisor(policyAdvisor);
592+
fhirValidator.registerValidatorModule(instanceValidatorModule);
593+
594+
when(validatorResourceFetcher.fetch(any(), theArgumentCaptor.capture(), anyString())).thenReturn(new Element("Organization").setType("Organization"));
595+
when(policyAdvisor.policyForReference(any(), any(), any(), any(), any())).thenReturn(ReferenceValidationPolicy.CHECK_EXISTS);
596+
when(policyAdvisor.policyForElement(any(), any(), any(), any(), any())).thenReturn(EnumSet.allOf(IValidationPolicyAdvisor.ElementValidationAction.class));
597+
598+
return fhirValidator;
599+
}
600+
547601
public static class PatientProvider implements IResourceProvider {
548602

549603
public String ourLastGraphQlQueryGet;

0 commit comments

Comments
 (0)