concurrent validation performance improvement (#3327)
This commit is contained in:
parent
42ff607561
commit
ed2ea91f56
|
@ -13,7 +13,6 @@ import ca.uhn.fhir.util.bundle.BundleEntryParts;
|
||||||
import ca.uhn.fhir.util.bundle.EntryListAccumulator;
|
import ca.uhn.fhir.util.bundle.EntryListAccumulator;
|
||||||
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
||||||
import ca.uhn.fhir.util.bundle.SearchBundleEntryParts;
|
import ca.uhn.fhir.util.bundle.SearchBundleEntryParts;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||||
|
@ -27,7 +26,6 @@ import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -231,7 +229,7 @@ public class BundleUtil {
|
||||||
retVal.addAll(partsToIBaseMap.values());
|
retVal.addAll(partsToIBaseMap.values());
|
||||||
|
|
||||||
//Blow away the entries and reset them in the right order.
|
//Blow away the entries and reset them in the right order.
|
||||||
TerserUtil.clearField(theContext, "entry", theBundle);
|
TerserUtil.clearField(theContext, theBundle, "entry");
|
||||||
TerserUtil.setField(theContext, "entry", theBundle, retVal.toArray(new IBase[0]));
|
TerserUtil.setField(theContext, "entry", theBundle, retVal.toArray(new IBase[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -315,12 +315,11 @@ public final class TerserUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the specified field on the resource provided
|
* Clears the specified field on the resource provided
|
||||||
*
|
* @param theFhirContext Context holding resource definition
|
||||||
* @param theFhirContext Context holding resource definition
|
|
||||||
* @param theFieldName
|
|
||||||
* @param theResource
|
* @param theResource
|
||||||
|
* @param theFieldName
|
||||||
*/
|
*/
|
||||||
public static void clearField(FhirContext theFhirContext, String theFieldName, IBaseResource theResource) {
|
public static void clearField(FhirContext theFhirContext, IBaseResource theResource, String theFieldName) {
|
||||||
BaseRuntimeChildDefinition childDefinition = getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
|
BaseRuntimeChildDefinition childDefinition = getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource);
|
||||||
clear(childDefinition.getAccessor().getValues(theResource));
|
clear(childDefinition.getAccessor().getValues(theResource));
|
||||||
}
|
}
|
||||||
|
@ -341,7 +340,7 @@ public final class TerserUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the provided field with the given values. This method will add to the collection of existing field values
|
* Sets the provided field with the given values. This method will add to the collection of existing field values
|
||||||
* in case of multiple cardinality. Use {@link #clearField(FhirContext, String, IBaseResource)}
|
* in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
|
||||||
* to remove values before setting
|
* to remove values before setting
|
||||||
*
|
*
|
||||||
* @param theFhirContext Context holding resource definition
|
* @param theFhirContext Context holding resource definition
|
||||||
|
@ -355,7 +354,7 @@ public final class TerserUtil {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the provided field with the given values. This method will add to the collection of existing field values
|
* Sets the provided field with the given values. This method will add to the collection of existing field values
|
||||||
* in case of multiple cardinality. Use {@link #clearField(FhirContext, String, IBaseResource)}
|
* in case of multiple cardinality. Use {@link #clearField(FhirContext, IBaseResource, String)}
|
||||||
* to remove values before setting
|
* to remove values before setting
|
||||||
*
|
*
|
||||||
* @param theFhirContext Context holding resource definition
|
* @param theFhirContext Context holding resource definition
|
||||||
|
|
|
@ -26,10 +26,10 @@ 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.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.BundleUtil;
|
import ca.uhn.fhir.util.BundleUtil;
|
||||||
|
import ca.uhn.fhir.util.TerserUtil;
|
||||||
import ca.uhn.fhir.validation.schematron.SchematronProvider;
|
import ca.uhn.fhir.validation.schematron.SchematronProvider;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -40,7 +40,6 @@ import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
@ -70,6 +69,7 @@ public class FhirValidator {
|
||||||
private List<IValidatorModule> myValidators = new ArrayList<>();
|
private List<IValidatorModule> myValidators = new ArrayList<>();
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
private boolean myConcurrentBundleValidation;
|
private boolean myConcurrentBundleValidation;
|
||||||
|
private boolean mySkipContainedReferenceValidation;
|
||||||
|
|
||||||
private ExecutorService myExecutorService;
|
private ExecutorService myExecutorService;
|
||||||
|
|
||||||
|
@ -271,10 +271,18 @@ public class FhirValidator {
|
||||||
// Async validation tasks
|
// Async validation tasks
|
||||||
List<ConcurrentValidationTask> validationTasks = IntStream.range(0, entries.size())
|
List<ConcurrentValidationTask> validationTasks = IntStream.range(0, entries.size())
|
||||||
.mapToObj(index -> {
|
.mapToObj(index -> {
|
||||||
|
IBaseResource resourceToValidate;
|
||||||
IBaseResource entry = entries.get(index);
|
IBaseResource entry = entries.get(index);
|
||||||
String entryPathPrefix = String.format("Bundle.entry[%d].resource.ofType(%s)", index, entry.fhirType());
|
|
||||||
|
if (mySkipContainedReferenceValidation) {
|
||||||
|
resourceToValidate = withoutContainedResources(entry);
|
||||||
|
} else {
|
||||||
|
resourceToValidate = entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
String entryPathPrefix = String.format("Bundle.entry[%d].resource.ofType(%s)", index, resourceToValidate.fhirType());
|
||||||
Future<ValidationResult> future = myExecutorService.submit(() -> {
|
Future<ValidationResult> future = myExecutorService.submit(() -> {
|
||||||
IValidationContext<IBaseResource> entryValidationContext = ValidationContext.forResource(theValidationContext.getFhirContext(), entry, theOptions);
|
IValidationContext<IBaseResource> entryValidationContext = ValidationContext.forResource(theValidationContext.getFhirContext(), resourceToValidate, theOptions);
|
||||||
return validateResource(entryValidationContext);
|
return validateResource(entryValidationContext);
|
||||||
});
|
});
|
||||||
return new ConcurrentValidationTask(entryPathPrefix, future);
|
return new ConcurrentValidationTask(entryPathPrefix, future);
|
||||||
|
@ -284,6 +292,16 @@ public class FhirValidator {
|
||||||
return new ValidationResult(myContext, validationMessages);
|
return new ValidationResult(myContext, validationMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IBaseResource withoutContainedResources(IBaseResource theEntry) {
|
||||||
|
if (TerserUtil.hasValues(myContext, theEntry, "contained")) {
|
||||||
|
IBaseResource deepCopy = TerserUtil.clone(myContext, theEntry);
|
||||||
|
TerserUtil.clearField(myContext, deepCopy, "contained");
|
||||||
|
return deepCopy;
|
||||||
|
} else {
|
||||||
|
return theEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static List<SingleValidationMessage> buildValidationMessages(List<ConcurrentValidationTask> validationTasks) {
|
static List<SingleValidationMessage> buildValidationMessages(List<ConcurrentValidationTask> validationTasks) {
|
||||||
List<SingleValidationMessage> retval = new ArrayList<>();
|
List<SingleValidationMessage> retval = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
|
@ -374,6 +392,23 @@ public class FhirValidator {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is true, any resource that has contained resources will first be deep-copied and then the contained
|
||||||
|
* resources remove from the copy and this copy without contained resources will be validated.
|
||||||
|
*/
|
||||||
|
public boolean isSkipContainedReferenceValidation() {
|
||||||
|
return mySkipContainedReferenceValidation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is true, any resource that has contained resources will first be deep-copied and then the contained
|
||||||
|
* resources remove from the copy and this copy without contained resources will be validated.
|
||||||
|
*/
|
||||||
|
public FhirValidator setSkipContainedReferenceValidation(boolean theSkipContainedReferenceValidation) {
|
||||||
|
mySkipContainedReferenceValidation = theSkipContainedReferenceValidation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
// Simple Tuple to keep track of bundle path and associate aync future task
|
// Simple Tuple to keep track of bundle path and associate aync future task
|
||||||
static class ConcurrentValidationTask {
|
static class ConcurrentValidationTask {
|
||||||
private final String myResourcePathPrefix;
|
private final String myResourcePathPrefix;
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: perf
|
||||||
|
issue: 3327
|
||||||
|
jira: SMILE-3438
|
||||||
|
title: "Improved performance of validating resources when skip contained resources is enabled."
|
|
@ -460,7 +460,7 @@ class TerserUtilTest {
|
||||||
Patient p1 = new Patient();
|
Patient p1 = new Patient();
|
||||||
p1.addName().setFamily("Doe");
|
p1.addName().setFamily("Doe");
|
||||||
|
|
||||||
TerserUtil.clearField(ourFhirContext, "name", p1);
|
TerserUtil.clearField(ourFhirContext, p1, "name");
|
||||||
|
|
||||||
assertEquals(0, p1.getName().size());
|
assertEquals(0, p1.getName().size());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
|
import org.hl7.fhir.r4.model.StringType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class FhirValidatorTest {
|
||||||
|
FhirContext ourFhirContext = FhirContext.forR4Cached();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithoutContainedResourcesDoesNotAlter() {
|
||||||
|
// setup
|
||||||
|
FhirValidator validator = new FhirValidator(ourFhirContext);
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addName().addGiven("bob");
|
||||||
|
Observation obs = new Observation();
|
||||||
|
obs.setValue(new StringType("heavy"));
|
||||||
|
patient.getContained().add(obs);
|
||||||
|
|
||||||
|
// run
|
||||||
|
Patient patientWithoutContained = (Patient)validator.withoutContainedResources(patient);
|
||||||
|
|
||||||
|
// check
|
||||||
|
assertEquals("bob", patientWithoutContained.getNameFirstRep().getGivenAsSingleString());
|
||||||
|
assertThat(patientWithoutContained.getContained(), hasSize(0));
|
||||||
|
|
||||||
|
assertEquals("bob", patient.getNameFirstRep().getGivenAsSingleString());
|
||||||
|
assertThat(patient.getContained(), hasSize(1));
|
||||||
|
assertEquals("heavy", ((Observation)patient.getContained().get(0)).getValue().toString());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue