Improving performance, using caching when testing for primitives (#6252) (#6253)

* Improving performance, using caching when testing for primitives (#6252)

Caching primitive type names for faster lookup if a type is primitive.

* Credit for #6253

---------

Co-authored-by: James Agnew <jamesagnew@gmail.com>
This commit is contained in:
Max Bureck 2024-10-08 15:29:42 +02:00 committed by GitHub
parent 6f94e228b0
commit 9a73079c33
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 97 additions and 5 deletions

View File

@ -0,0 +1,6 @@
---
type: perf
issue: 6253
title: "A cache has been added to the validation services layer which results
in improved validation performance. Thanks to Max Bureck for the
contribution!"

View File

@ -55,11 +55,15 @@ import org.slf4j.LoggerFactory;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -69,6 +73,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
private final VersionCanonicalizer myVersionCanonicalizer; private final VersionCanonicalizer myVersionCanonicalizer;
private final LoadingCache<ResourceKey, IBaseResource> myFetchResourceCache; private final LoadingCache<ResourceKey, IBaseResource> myFetchResourceCache;
private volatile List<StructureDefinition> myAllStructures; private volatile List<StructureDefinition> myAllStructures;
private volatile Set<String> myAllPrimitiveTypes;
private Parameters myExpansionProfile; private Parameters myExpansionProfile;
public VersionSpecificWorkerContextWrapper( public VersionSpecificWorkerContextWrapper(
@ -617,11 +622,23 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
@Override @Override
public boolean isPrimitiveType(String theType) { public boolean isPrimitiveType(String theType) {
List<StructureDefinition> allStructures = new ArrayList<>(allStructures()); return allPrimitiveTypes().contains(theType);
return allStructures.stream() }
private Set<String> allPrimitiveTypes() {
Set<String> retVal = myAllPrimitiveTypes;
if (retVal == null) {
// Collector may be changed to Collectors.toUnmodifiableSet() when switching to Android API level >= 33
retVal = allStructures().stream()
.filter(structureDefinition -> .filter(structureDefinition ->
structureDefinition.getKind() == StructureDefinition.StructureDefinitionKind.PRIMITIVETYPE) structureDefinition.getKind() == StructureDefinition.StructureDefinitionKind.PRIMITIVETYPE)
.anyMatch(structureDefinition -> theType.equals(structureDefinition.getName())); .map(StructureDefinition::getName)
.filter(Objects::nonNull)
.collect(collectingAndThen(toSet(), Collections::unmodifiableSet));
myAllPrimitiveTypes = retVal;
}
return retVal;
} }
@Override @Override

View File

@ -7,7 +7,10 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks;
import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -22,6 +25,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings; import static org.mockito.Mockito.withSettings;
import java.util.List;
public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestWithInlineMocks { public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestWithInlineMocks {
final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes(); final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes();
@ -96,6 +101,66 @@ public class VersionSpecificWorkerContextWrapperTest extends BaseValidationTestW
verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()); verify(validationSupport, times(1)).validateCode(any(), any(), eq("http://codesystems.com/system"), eq("code0"), any(), any());
} }
@Test
public void isPrimitive_primitive() {
// setup
IValidationSupport validationSupport = mockValidationSupport();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached());
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer);
List<StructureDefinition> structDefs = createStructureDefinitions();
when(mockContext.getRootValidationSupport().<StructureDefinition>fetchAllStructureDefinitions()).thenReturn(structDefs);
assertThat(wrapper.isPrimitiveType("boolean")).isTrue();
// try again to check if lookup after cache is built is working
assertThat(wrapper.isPrimitiveType("string")).isTrue();
}
@Test
public void isPrimitive_not_primitive() {
// setup
IValidationSupport validationSupport = mockValidationSupport();
ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport);
VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(FhirContext.forR5Cached());
VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, versionCanonicalizer);
List<StructureDefinition> structDefs = createStructureDefinitions();
when(mockContext.getRootValidationSupport().<StructureDefinition>fetchAllStructureDefinitions()).thenReturn(structDefs);
assertThat(wrapper.isPrimitiveType("Person")).isFalse();
// try again to check if lookup after cache is built is working
assertThat(wrapper.isPrimitiveType("Organization")).isFalse();
// Assert that unknown types are not regarded as primitive
assertThat(wrapper.isPrimitiveType("Unknown")).isFalse();
}
private List<StructureDefinition> createStructureDefinitions() {
StructureDefinition stringType = createPrimitive("string");
StructureDefinition boolType = createPrimitive("boolean");
StructureDefinition personType = createComplex("Person");
StructureDefinition orgType = createComplex("Organization");
return List.of(personType, boolType, orgType, stringType);
}
private StructureDefinition createComplex(String name){
return createStructureDefinition(name).setKind(StructureDefinitionKind.COMPLEXTYPE);
}
private StructureDefinition createPrimitive(String name){
return createStructureDefinition(name).setKind(StructureDefinitionKind.PRIMITIVETYPE);
}
private StructureDefinition createStructureDefinition(String name) {
StructureDefinition sd = new StructureDefinition();
sd.setUrl("http://hl7.org/fhir/StructureDefinition/"+name).setName(name);
return sd;
}
private IValidationSupport mockValidationSupportWithTwoBinaries() { private IValidationSupport mockValidationSupportWithTwoBinaries() {
IValidationSupport validationSupport; IValidationSupport validationSupport;
validationSupport = mockValidationSupport(); validationSupport = mockValidationSupport();

View File

@ -944,6 +944,10 @@
<id>plchldr</id> <id>plchldr</id>
<name>Jonas Beyer</name> <name>Jonas Beyer</name>
</developer> </developer>
<developer>
<id>Boereck</id>
<name>Max Bureck</name>
</developer>
</developers> </developers>
<licenses> <licenses>