This commit is contained in:
James Agnew 2024-11-23 19:46:24 -05:00
parent 72ff25326c
commit 4713eeee1c
5 changed files with 76 additions and 106 deletions

View File

@ -241,13 +241,15 @@ public class JpaConfig {
@Autowired
private FhirContext myFhirContext;
@Autowired
private ValidationSupportChain.CacheConfiguration myCacheConfiguration;
@Bean
public ValidationSupportChain.CacheConfiguration validationSupportChainCacheConfiguration() {
return ValidationSupportChain.CacheConfiguration.defaultValues();
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
@Primary
public IValidationSupport jpaValidationSupportChain() {
return new JpaValidationSupportChain(myFhirContext, myCacheConfiguration);
return new JpaValidationSupportChain(myFhirContext, validationSupportChainCacheConfiguration());
}
@Bean("myDaoRegistry")

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.validation.ValidatorPolicyAdvisor;
import ca.uhn.fhir.jpa.validation.ValidatorResourceFetcher;
import ca.uhn.fhir.validation.IInstanceValidatorModule;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowired;
@ -57,11 +56,6 @@ public class ValidationSupportConfig {
return retVal;
}
@Bean
public ValidationSupportChain.CacheConfiguration validationSupportChainCacheConfiguration() {
return ValidationSupportChain.CacheConfiguration.defaultValues();
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT)
public IValidationSupport jpaValidationSupport(FhirContext theFhirContext) {
return new JpaPersistedResourceValidationSupport(theFhirContext);

View File

@ -115,7 +115,6 @@ public class RemoteTerminologyServiceJpaR4Test extends BaseJpaR4Test {
// Verify 1
Assertions.assertEquals(2, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/ValueSet/administrative-gender",
"http://hl7.org/fhir/ValueSet/administrative-gender",
"http://hl7.org/fhir/ValueSet/administrative-gender"
);
@ -300,7 +299,6 @@ public class RemoteTerminologyServiceJpaR4Test extends BaseJpaR4Test {
// Verify 1
Assertions.assertEquals(2, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/ValueSet/identifier-type",
"http://hl7.org/fhir/ValueSet/identifier-type",
"http://hl7.org/fhir/ValueSet/identifier-type"
);

View File

@ -1,5 +1,6 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@ -21,11 +22,11 @@ import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -657,8 +658,30 @@ public class ValidationSupportChain implements IValidationSupport {
return fetchResource(key, invoker, theUrl);
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) {
/*
* If we're looking for a common type with a dedicated fetch method, use that
* so that we can use a common cache location for lookups wanting a given
* URL on both methods (the validator will call both paths when looking for a
* specific URL so this improves cache efficiency).
*/
if (theClass != null) {
BaseRuntimeElementDefinition<?> elementDefinition = getFhirContext().getElementDefinition(theClass);
if (elementDefinition != null) {
switch (elementDefinition.getName()) {
case "ValueSet":
return (T) fetchValueSet(theUri);
case "CodeSystem":
return (T) fetchCodeSystem(theUri);
case "StructureDefinition":
return (T) fetchStructureDefinition(theUri);
}
}
}
Function<IValidationSupport, T> invoker = v -> v.fetchResource(theClass, theUri);
TypedResourceByUrlKey<T> key = new TypedResourceByUrlKey<>(theClass, theUri);
return fetchResource(key, invoker, theUri);
@ -956,9 +979,9 @@ public class ValidationSupportChain implements IValidationSupport {
return myCacheTimeout;
}
public CacheConfiguration setCacheTimeout(long theCacheTimeout) {
Validate.isTrue(theCacheTimeout >= 0, "Cache timeout must not be negative");
myCacheTimeout = theCacheTimeout;
public CacheConfiguration setCacheTimeout(Duration theCacheTimeout) {
Validate.isTrue(theCacheTimeout.toMillis() >= 0, "Cache timeout must not be negative");
myCacheTimeout = theCacheTimeout.toMillis();
return this;
}
@ -978,12 +1001,12 @@ public class ValidationSupportChain implements IValidationSupport {
*/
public static CacheConfiguration defaultValues() {
return new CacheConfiguration()
.setCacheTimeout(10 * DateUtils.MILLIS_PER_MINUTE)
.setCacheTimeout(Duration.ofMinutes(10))
.setCacheSize(5000);
}
public static CacheConfiguration disabled() {
return new CacheConfiguration().setCacheSize(0).setCacheTimeout(0);
return new CacheConfiguration().setCacheSize(0).setCacheTimeout(Duration.ofMillis(0));
}
}

View File

@ -14,24 +14,13 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.testing.LibraryTestRunner;
import io.opentelemetry.sdk.metrics.data.Data;
import io.opentelemetry.sdk.metrics.data.LongPointData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import jakarta.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ListResource;
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
@ -45,17 +34,14 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static io.opentelemetry.api.common.AttributeKey.stringKey;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
@ -399,7 +385,7 @@ public class ValidationSupportChainTest extends BaseTest {
final ValidationSupportChain.CacheConfiguration cacheTimeouts = ValidationSupportChain.CacheConfiguration
.defaultValues()
.setCacheTimeout(500);
.setCacheTimeout(Duration.ofMillis(500));
ValidationSupportChain chain = new ValidationSupportChain(cacheTimeouts, myValidationSupport0);
// First call should return the full list
@ -415,8 +401,7 @@ public class ValidationSupportChainTest extends BaseTest {
assertEquals(3, chain.fetchAllNonBaseStructureDefinitions().size());
// Eventually we should refresh
await().until(() -> chain.fetchAllNonBaseStructureDefinitions().size(), equalTo(2));
await().until(() -> chain.fetchAllNonBaseStructureDefinitions().size(), t -> t == 2);
}
@ParameterizedTest
@ -540,16 +525,50 @@ public class ValidationSupportChainTest extends BaseTest {
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testResource(boolean theUseCache) {
public void testResource_ValueSet(boolean theUseCache) {
// Setup
prepareMock(myValidationSupport0, myValidationSupport1, myValidationSupport2);
ValidationSupportChain chain = new ValidationSupportChain(newCacheConfiguration(theUseCache), myValidationSupport0, myValidationSupport1, myValidationSupport2);
when(myValidationSupport0.fetchValueSet(any())).thenReturn(null);
when(myValidationSupport1.fetchValueSet(any())).thenAnswer(t -> new ValueSet());
// Test
IBaseResource result = chain.fetchResource(ValueSet.class, VALUE_SET_URL_0);
// Verify
verify(myValidationSupport0, times(1)).fetchValueSet( any());
verify(myValidationSupport1, times(1)).fetchValueSet( any());
verify(myValidationSupport2, times(0)).fetchValueSet(any());
// Test again (should use cache)
IBaseResource result2 = chain.fetchResource(ValueSet.class, VALUE_SET_URL_0);
// Verify
if (theUseCache) {
assertSame(result, result2);
verify(myValidationSupport0, times(1)).fetchValueSet( any());
verify(myValidationSupport1, times(1)).fetchValueSet( any());
verify(myValidationSupport2, times(0)).fetchValueSet( any());
} else {
verify(myValidationSupport0, times(2)).fetchValueSet( any());
verify(myValidationSupport1, times(2)).fetchValueSet( any());
verify(myValidationSupport2, times(0)).fetchValueSet(any());
}
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testResource_Arbitrary(boolean theUseCache) {
// Setup
prepareMock(myValidationSupport0, myValidationSupport1, myValidationSupport2);
ValidationSupportChain chain = new ValidationSupportChain(newCacheConfiguration(theUseCache), myValidationSupport0, myValidationSupport1, myValidationSupport2);
when(myValidationSupport0.fetchResource(any(), any())).thenReturn(null);
when(myValidationSupport1.fetchResource(any(), any())).thenAnswer(t -> new ValueSet());
when(myValidationSupport1.fetchResource(any(), any())).thenAnswer(t -> new ListResource());
// Test
IBaseResource result = chain.fetchResource(ValueSet.class, VALUE_SET_URL_0);
IBaseResource result = chain.fetchResource(ListResource.class, "http://foo");
// Verify
verify(myValidationSupport0, times(1)).fetchResource(any(), any());
@ -557,7 +576,7 @@ public class ValidationSupportChainTest extends BaseTest {
verify(myValidationSupport2, times(0)).fetchResource(any(), any());
// Test again (should use cache)
IBaseResource result2 = chain.fetchResource(ValueSet.class, VALUE_SET_URL_0);
IBaseResource result2 = chain.fetchResource(ListResource.class, "http://foo");
// Verify
if (theUseCache) {
@ -704,70 +723,4 @@ public class ValidationSupportChainTest extends BaseTest {
when(theValidationSupport.fetchBinary(theExpectedBinaryKey)).thenReturn(theExpectedBinaryContent);
}
// FIXME: remove
public static final class LongCounterExample {
public static final String INSTRUMENTATION_SCOPE = "io.opentelemetry.example.metrics";
private static final File homeDirectory = FileSystemView.getFileSystemView().getHomeDirectory();
private static final AttributeKey<String> ROOT_DIRECTORY_KEY = stringKey("root directory");
// statically allocate the attributes, since they are known at init time.
private static final Attributes HOME_DIRECTORY_ATTRIBUTES =
Attributes.of(ROOT_DIRECTORY_KEY, homeDirectory.getName());
private final Meter meter;
private final Tracer tracer;
public LongCounterExample(Tracer tracer, Meter meter) {
this.tracer = tracer;
this.meter = meter;
}
void run() {
Span span = tracer.spanBuilder("workflow").setSpanKind(SpanKind.INTERNAL).startSpan();
try (Scope scope = span.makeCurrent()) {
LongCounter directoryCounter = findFile("file_to_find.txt", homeDirectory);
directoryCounter.add(1, HOME_DIRECTORY_ATTRIBUTES); // count root directory
} catch (Exception e) {
span.setStatus(StatusCode.ERROR, "Error while finding file");
} finally {
span.end();
}
}
/**
* Uses the Meter instance to create a LongCounter with the given name, description, and units.
* This is the counter that is used to count directories during filesystem traversal.
*/
LongCounter createCounter() {
return meter
.counterBuilder("directories_search_count")
.setDescription("Counts directories accessed while searching for files.")
.setUnit("unit")
.build();
}
LongCounter findFile(String name, File directory) {
LongCounter directoryCounter = createCounter();
directoryCounter.add(1, HOME_DIRECTORY_ATTRIBUTES);
return directoryCounter;
}
public static void main(String[] args) {
OpenTelemetry sdk = GlobalOpenTelemetry.get();
Tracer tracer = sdk.getTracer(INSTRUMENTATION_SCOPE, "0.13.1");
Meter meterBuilder = sdk.meterBuilder("AAAAAA").setInstrumentationVersion("1").build();
ObservableLongMeasurement bbbb = meterBuilder.counterBuilder("BBBB").buildObserver();
bbbb.record(222L);
meterBuilder.counterBuilder("BBBB").buildWithCallback(t -> t.record(123L));
Meter meter = sdk.getMeter(INSTRUMENTATION_SCOPE);
LongCounterExample example = new LongCounterExample(tracer, meter);
example.run();
}
}
}