Streamline ValidationSupportChain (#6508)

* Tests passing

* Cleanup

* Cleanupo

* Test fixes

* Test fix

* Cleanup

* Update hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java

Co-authored-by: Ken Stevens <khstevens@gmail.com>

* Update hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java

Co-authored-by: Ken Stevens <khstevens@gmail.com>

* Update hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java

Co-authored-by: Ken Stevens <khstevens@gmail.com>

* Account for review comments

* Spotless

* Compile fix

* Test fixes

* Test cleanup

* Test cleanup

* Test fixes

* Resolve fixme

* Test fix

* Test fixes

* Test fixes

* Test fixes

* Fix

* Test fix

* Test fixes

* Test fixes

* HAPI version bump

* Try to address intermittent

---------

Co-authored-by: Ken Stevens <khstevens@gmail.com>
This commit is contained in:
James Agnew 2024-11-27 17:45:17 -05:00 committed by GitHub
parent 061390d76b
commit 362dc095ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
133 changed files with 3193 additions and 990 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -713,6 +713,8 @@ public class FhirContext {
"org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport";
String commonCodeSystemsSupportType =
"org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService";
String snapshotGeneratingType =
"org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport";
if (ReflectionUtil.typeExists(inMemoryTermSvcType)) {
IValidationSupport inMemoryTermSvc = ReflectionUtil.newInstanceOrReturnNull(
inMemoryTermSvcType,
@ -724,11 +726,23 @@ public class FhirContext {
IValidationSupport.class,
new Class<?>[] {FhirContext.class},
new Object[] {this});
IValidationSupport snapshotGeneratingSupport = null;
if (getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
snapshotGeneratingSupport = ReflectionUtil.newInstanceOrReturnNull(
snapshotGeneratingType,
IValidationSupport.class,
new Class<?>[] {FhirContext.class},
new Object[] {this});
}
retVal = ReflectionUtil.newInstanceOrReturnNull(
"org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain",
IValidationSupport.class,
new Class<?>[] {IValidationSupport[].class},
new Object[] {new IValidationSupport[] {retVal, inMemoryTermSvc, commonCodeSystemsSupport}});
new Object[] {
new IValidationSupport[] {
retVal, inMemoryTermSvc, commonCodeSystemsSupport, snapshotGeneratingSupport
}
});
assert retVal != null
: "Failed to instantiate "
+ "org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain";

View File

@ -22,11 +22,26 @@ package ca.uhn.fhir.context.support;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import java.util.Objects;
public class ConceptValidationOptions {
private boolean myValidateDisplay;
private boolean myInferSystem;
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof ConceptValidationOptions)) return false;
ConceptValidationOptions that = (ConceptValidationOptions) theO;
return myValidateDisplay == that.myValidateDisplay && myInferSystem == that.myInferSystem;
}
@Override
public int hashCode() {
return Objects.hash(myValidateDisplay, myInferSystem);
}
public boolean isInferSystem() {
return myInferSystem;
}

View File

@ -23,7 +23,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.util.ILockable;
import ca.uhn.fhir.util.ReflectionUtil;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -48,6 +50,13 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
private static final Map<FhirVersionEnum, IValidationSupport> ourImplementations =
Collections.synchronizedMap(new HashMap<>());
/**
* Userdata key indicating the source package ID for this package
*/
public static final String SOURCE_PACKAGE_ID =
DefaultProfileValidationSupport.class.getName() + "_SOURCE_PACKAGE_ID";
private final FhirContext myCtx;
/**
* This module just delegates all calls to a concrete implementation which will
@ -62,7 +71,8 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
*
* @param theFhirContext The context to use
*/
public DefaultProfileValidationSupport(FhirContext theFhirContext) {
public DefaultProfileValidationSupport(@Nonnull FhirContext theFhirContext) {
Validate.notNull(theFhirContext, "FhirContext must not be null");
myCtx = theFhirContext;
IValidationSupport strategy;
@ -106,33 +116,45 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
@Override
public List<IBaseResource> fetchAllConformanceResources() {
return myDelegate.fetchAllConformanceResources();
List<IBaseResource> retVal = myDelegate.fetchAllConformanceResources();
addPackageInformation(retVal);
return retVal;
}
@Override
public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
return myDelegate.fetchAllStructureDefinitions();
List<T> retVal = myDelegate.fetchAllStructureDefinitions();
addPackageInformation(retVal);
return retVal;
}
@Nullable
@Override
public <T extends IBaseResource> List<T> fetchAllNonBaseStructureDefinitions() {
return myDelegate.fetchAllNonBaseStructureDefinitions();
List<T> retVal = myDelegate.fetchAllNonBaseStructureDefinitions();
addPackageInformation(retVal);
return retVal;
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
return myDelegate.fetchCodeSystem(theSystem);
IBaseResource retVal = myDelegate.fetchCodeSystem(theSystem);
addPackageInformation(retVal);
return retVal;
}
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
return myDelegate.fetchStructureDefinition(theUrl);
IBaseResource retVal = myDelegate.fetchStructureDefinition(theUrl);
addPackageInformation(retVal);
return retVal;
}
@Override
public IBaseResource fetchValueSet(String theUrl) {
return myDelegate.fetchValueSet(theUrl);
IBaseResource retVal = myDelegate.fetchValueSet(theUrl);
addPackageInformation(retVal);
return retVal;
}
public void flush() {
@ -158,4 +180,43 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
return urlValueString;
}
private <T extends IBaseResource> void addPackageInformation(List<T> theResources) {
if (theResources != null) {
theResources.forEach(this::addPackageInformation);
}
}
private void addPackageInformation(IBaseResource theResource) {
if (theResource != null) {
String sourcePackageId = null;
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG:
sourcePackageId = "hl7.fhir.r2.core";
break;
case DSTU2_1:
return;
case DSTU3:
sourcePackageId = "hl7.fhir.r3.core";
break;
case R4:
sourcePackageId = "hl7.fhir.r4.core";
break;
case R4B:
sourcePackageId = "hl7.fhir.r4b.core";
break;
case R5:
sourcePackageId = "hl7.fhir.r5.core";
break;
}
Validate.notNull(
sourcePackageId,
"Don't know how to handle package ID: %s",
myCtx.getVersion().getVersion());
theResource.setUserData(SOURCE_PACKAGE_ID, sourcePackageId);
}
}
}

View File

@ -41,6 +41,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -116,7 +117,8 @@ public interface IValidationSupport {
@Nonnull String theValueSetUrlToExpand)
throws ResourceNotFoundException {
Validate.notBlank(theValueSetUrlToExpand, "theValueSetUrlToExpand must not be null or blank");
IBaseResource valueSet = fetchValueSet(theValueSetUrlToExpand);
IBaseResource valueSet =
theValidationSupportContext.getRootValidationSupport().fetchValueSet(theValueSetUrlToExpand);
if (valueSet == null) {
throw new ResourceNotFoundException(
Msg.code(2024) + "Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSetUrlToExpand));
@ -212,8 +214,8 @@ public interface IValidationSupport {
() -> fetchStructureDefinition(theUri), () -> fetchValueSet(theUri), () -> fetchCodeSystem(theUri)
};
return (T) Arrays.stream(sources)
.map(t -> t.get())
.filter(t -> t != null)
.map(Supplier::get)
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
}
@ -792,6 +794,7 @@ public interface IValidationSupport {
return myValue;
}
@Override
public String getType() {
return TYPE_STRING;
}
@ -826,6 +829,7 @@ public interface IValidationSupport {
return myDisplay;
}
@Override
public String getType() {
return TYPE_CODING;
}
@ -1431,10 +1435,9 @@ public interface IValidationSupport {
}
/**
* <p
* Warning: This method's behaviour and naming is preserved for backwards compatibility, BUT the actual naming and
* function are not aligned.
* </p
* When validating a CodeableConcept containing multiple codings, this method can be used to control whether
* the validator requires all codings in the CodeableConcept to be valid in order to consider the
* CodeableConcept valid.
* <p>
* See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation, and the refer to the values below
* for the behaviour associated with each value.
@ -1449,7 +1452,7 @@ public interface IValidationSupport {
* </p>
* @return true or false depending on the desired coding validation behaviour.
*/
default boolean isEnabledValidationForCodingsLogicalAnd() {
default boolean isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid() {
return false;
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.context.support;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
/**
* Represents parameters which can be passed to the $lookup operation for codes.
@ -72,4 +73,20 @@ public class LookupCodeRequest {
}
return myPropertyNames;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof LookupCodeRequest)) return false;
LookupCodeRequest that = (LookupCodeRequest) theO;
return Objects.equals(mySystem, that.mySystem)
&& Objects.equals(myCode, that.myCode)
&& Objects.equals(myDisplayLanguage, that.myDisplayLanguage)
&& Objects.equals(myPropertyNames, that.myPropertyNames);
}
@Override
public int hashCode() {
return Objects.hash(mySystem, myCode, myDisplayLanguage, myPropertyNames);
}
}

View File

@ -42,7 +42,7 @@ public class ValidationSupportContext {
return myCurrentlyGeneratingSnapshots;
}
public boolean isEnabledValidationForCodingsLogicalAnd() {
return myRootValidationSupport.isEnabledValidationForCodingsLogicalAnd();
public boolean isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid() {
return myRootValidationSupport.isCodeableConceptValidationSuccessfulIfNotAllCodingsAreValid();
}
}

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.context.support;
import org.apache.commons.lang3.Validate;
import java.util.Objects;
/**
* Options for ValueSet expansion
*
@ -126,4 +128,23 @@ public class ValueSetExpansionOptions {
myDisplayLanguage = theDisplayLanguage;
return this;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (!(theO instanceof ValueSetExpansionOptions)) return false;
ValueSetExpansionOptions that = (ValueSetExpansionOptions) theO;
return myFailOnMissingCodeSystem == that.myFailOnMissingCodeSystem
&& myCount == that.myCount
&& myOffset == that.myOffset
&& myIncludeHierarchy == that.myIncludeHierarchy
&& Objects.equals(myFilter, that.myFilter)
&& Objects.equals(myDisplayLanguage, that.myDisplayLanguage);
}
@Override
public int hashCode() {
return Objects.hash(
myFailOnMissingCodeSystem, myCount, myOffset, myIncludeHierarchy, myFilter, myDisplayLanguage);
}
}

View File

@ -342,6 +342,8 @@ public class Constants {
*/
public static final String HIBERNATE_INTEGRATION_ENVERS_ENABLED = "hibernate.integration.envers.enabled";
public static final String OPENTELEMETRY_BASE_NAME = "io.hapifhir";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;
CHARSET_US_ASCII = StandardCharsets.ISO_8859_1;

View File

@ -33,7 +33,7 @@ public class SleepUtil {
@SuppressWarnings("BusyWait")
public void sleepAtLeast(long theMillis, boolean theLogProgress) {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() <= start + theMillis) {
while (System.currentTimeMillis() < start + theMillis) {
try {
long timeSinceStarted = System.currentTimeMillis() - start;
long timeToSleep = Math.max(0, theMillis - timeSinceStarted);

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
@ -12,7 +12,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -40,7 +40,6 @@ import jakarta.annotation.Nonnull;
import jakarta.servlet.ServletException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
@ -343,7 +342,8 @@ public class ValidatorExamples {
// START SNIPPET: validateSupplyProfiles
FhirContext ctx = FhirContext.forR4();
// Create a chain that will hold our modules
// Create a chain that will hold our modules and caches the
// values they supply
ValidationSupportChain supportChain = new ValidationSupportChain();
// DefaultProfileValidationSupport supplies base FHIR definitions. This is generally required
@ -368,12 +368,9 @@ public class ValidatorExamples {
// Add the custom definitions to the chain
supportChain.addValidationSupport(prePopulatedSupport);
// Wrap the chain in a cache to improve performance
CachingValidationSupport cache = new CachingValidationSupport(supportChain);
// Create a validator using the FhirInstanceValidator module. We can use this
// validator to perform validation
FhirInstanceValidator validatorModule = new FhirInstanceValidator(cache);
FhirInstanceValidator validatorModule = new FhirInstanceValidator(supportChain);
FhirValidator validator = ctx.newValidator().registerValidatorModule(validatorModule);
ValidationResult result = validator.validateWithResult(input);
// END SNIPPET: validateSupplyProfiles
@ -403,12 +400,9 @@ public class ValidatorExamples {
remoteTermSvc.setBaseUrl("http://hapi.fhir.org/baseR4");
supportChain.addValidationSupport(remoteTermSvc);
// Wrap the chain in a cache to improve performance
CachingValidationSupport cache = new CachingValidationSupport(supportChain);
// Create a validator using the FhirInstanceValidator module. We can use this
// validator to perform validation
FhirInstanceValidator validatorModule = new FhirInstanceValidator(cache);
FhirInstanceValidator validatorModule = new FhirInstanceValidator(supportChain);
FhirValidator validator = ctx.newValidator().registerValidatorModule(validatorModule);
ValidationResult result = validator.validateWithResult(input);
// END SNIPPET: validateUsingRemoteTermSvr
@ -462,12 +456,11 @@ public class ValidatorExamples {
new CommonCodeSystemsTerminologyService(ctx),
new InMemoryTerminologyServerValidationSupport(ctx),
new SnapshotGeneratingValidationSupport(ctx));
CachingValidationSupport validationSupport = new CachingValidationSupport(validationSupportChain);
// Create a validator. Note that for good performance you can create as many validator objects
// as you like, but you should reuse the same validation support object in all of the,.
FhirValidator validator = ctx.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupport);
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupportChain);
validator.registerValidatorModule(instanceValidator);
// Create a test patient to validate

View File

@ -0,0 +1,13 @@
---
type: perf
issue: 6508
title: "The ValidationSupportChain module has been rewritten to improve validator performance. This change:
* Adds new caching capabilities to ValidationSupportChain. This is an improvement over the previous separate caching module because the chain can now remember which entries in the cache responded affirmative to `isValueSetSupported()` and will therefore be more efficient about trying entries in the chain. It also makes debugging much less confusing as there is less recursion and the caches don't use loadingCache.
* Importantly, the caching in ValidationSupportChain caches negative lookups (i.e. items that could not be found by URL) as well as positive lookups. This is a change from the historical caching behaviour.
* Changes ValidationSupportChain to never expire StructureDefinition entries in the cache, which is needed because the validator makes assumptions about structuredefinitions never changing. Fixes #6424.
* Modifies `VersionSpecificWorkerContextWrapper` so that it doesn't use a separate cache and instead relies on the caching provided by ValidationSupportChain. This class previously used a cache because it converts arbitrary versions of FHIR StructureDefinitions into the canonical version required by the validator (R5), but these converted versions are now stored in the userdata map of objects returned by and cached by ValidationSupportChain. This makes the caching more predictable since there is only one cache to track.
* Adds OpenTelemetry support to ValidationSupportChain, with metrics for tracking the cache size.
* Deprecates CachingValidationSupport since caching is now provided by ValidationSupportChain. CachingValidationSupport is now just a passthrough and should be removed from applications. It will be removed from the library in a future release.
* Removes ConceptMap caching from TermReachSvcImpl, as this caching is both redundant and inefficient as it operates within a database transaction.
These changes result in very significant performance improvements when performing validation in the JPA server. Throughput improvements of 1000% have been recorded in benchmarking use cases involving large profiles and remote terminology services enabled. Many other validation use cases should see significant improvements as well."

View File

@ -16,6 +16,21 @@ There are a several implementations of the [IValidationSupport](/hapi-fhir/apido
This module can be used to combine multiple implementations together so that for every request, each support class instance in the chain is tried in sequence. Note that nearly all methods in the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) interface are permitted to return `null` if they are not able to service a particular method call. So for example, if a call to the [`validateCode`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html#validateCode(ca.uhn.fhir.context.support.ValidationSupportContext,ca.uhn.fhir.context.support.ConceptValidationOptions,java.lang.String,java.lang.String,java.lang.String,java.lang.String)) method is made, the validator will try each module in the chain until one of them returns a non-null response.
The following chaining logic is used:
* Calls to `fetchAll...` methods such as `fetchAllConformanceResources()` and `fetchAllStructureDefinitions()` will call every method in the chain in order, and aggregate the results into a single list to return.
* Calls to fetch or validate codes, such as `validateCode(...)` and `lookupCode(...)` will first test each module in the chain using the`isCodeSystemSupported(...)` or `isValueSetSupported(...)` methods (depending on whether a ValueSet URL is present in the method parameters) and will invoke any methods in the chain which return that they can handle the given CodeSystem/ValueSet URL. The first non-null value returned by a method in the chain that can support the URL will be returned to the caller.
* All other methods will invoke the method in the chain in order, and will return immediately as soon as a non-null value is returned.
The following caching logic is used if caching is enabled using `CacheConfiguration`. You can use `CacheConfiguration.disabled()` if you want to disable caching.
* Calls to fetch StructureDefinitions including `fetchAllStructureDefinitions()` and `fetchStructureDefinition(...)` are cached in a non-expiring cache. This is because the `FhirInstanceValidator` module makes assumptions that these objects will not change for the lifetime of the validator for performance reasons.
* Calls to all other `fetchAll...` methods including `fetchAllConformanceResources()` and `fetchAllSearchParameters()` cache their results in an expiring cache, but will refresh that cache asynchronously.
* Results of `generateSnapshot(...)` are not cached, as this method is generally called in contexts where the results are cached.
* Results of all other methods are stored in an expiring cache.
Note that caching functionality used to be provided by a separate provider called {@literal CachingValidationSupport} but that functionality has been moved into this class as of HAPI FHIR 8.0.0, because it is possible to provide a more efficient chain when these functions are combined.
# DefaultProfileValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) / [Source](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java)
@ -44,12 +59,6 @@ This module contains a series of HashMaps that store loaded conformance resource
This module can be used to load FHIR NPM Packages and supply the conformance resources within them to the validator. See [Validating Using Packages](./instance_validator.html#packages) for am example of how to use this module.
# CachingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java)
This module caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk, but it also has value even for purely in-memory validation since validating codes against a ValueSet can require the expansion of that ValueSet.
# SnapshotGeneratingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java)
@ -161,6 +170,12 @@ This validation support module may be placed at the end of a ValidationSupportCh
Note that this module must also be activated by calling [setAllowNonExistentCodeSystem(true)](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.html#setAllowNonExistentCodeSystem(boolean)) in order to specify that unknown code systems should be allowed.
# CachingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java)
This module is deprecated and no longer provides any functionality. Caching is provided by [ValidationSupportChain](#validationsupportchain).
# Recipes

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.api.IDaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.config.util.ResourceCountCacheUtil;
import ca.uhn.fhir.jpa.config.util.ValidationSupportConfigUtil;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.search.HSearchSortHelperImpl;
@ -32,15 +31,12 @@ import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.rest.api.IResourceSupportedSvc;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
@Configuration
@Import({JpaConfig.class})
@ -64,12 +60,6 @@ public class HapiJpaConfig {
return new StaleSearchDeletingSvcImpl();
}
@Primary
@Bean
public CachingValidationSupport validationSupportChain(JpaValidationSupportChain theJpaValidationSupportChain) {
return ValidationSupportConfigUtil.newCachingValidationSupport(theJpaValidationSupportChain);
}
@Bean
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return new DatabaseBackedPagingProvider();

View File

@ -168,6 +168,7 @@ import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig;
import ca.uhn.fhir.jpa.util.JpaHapiTransactionService;
import ca.uhn.fhir.jpa.util.MemoryCacheService;
import ca.uhn.fhir.jpa.util.PersistenceContextProvider;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl;
import ca.uhn.fhir.jpa.validation.ValidationSettings;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
@ -185,6 +186,7 @@ import ca.uhn.fhir.util.MetaTagSorterAlphabetical;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import jakarta.annotation.Nullable;
import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@ -230,11 +232,26 @@ public class JpaConfig {
public static final String PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER =
"PersistedJpaSearchFirstPageBundleProvider";
public static final String HISTORY_BUILDER = "HistoryBuilder";
public static final String DEFAULT_PROFILE_VALIDATION_SUPPORT = "myDefaultProfileValidationSupport";
private static final String HAPI_DEFAULT_SCHEDULER_GROUP = "HAPI";
@Autowired
public JpaStorageSettings myStorageSettings;
@Autowired
private FhirContext myFhirContext;
@Bean
public ValidationSupportChain.CacheConfiguration validationSupportChainCacheConfiguration() {
return ValidationSupportChain.CacheConfiguration.defaultValues();
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
@Primary
public IValidationSupport jpaValidationSupportChain() {
return new JpaValidationSupportChain(myFhirContext, validationSupportChainCacheConfiguration());
}
@Bean("myDaoRegistry")
public DaoRegistry daoRegistry() {
return new DaoRegistry();

View File

@ -20,31 +20,31 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.JpaPersistedResourceValidationSupport;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
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.CachingValidationSupport;
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.common.hapi.validation.validator.HapiToHl7OrgDstu2ValidatingSupportWrapper;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class ValidationSupportConfig {
@Bean(name = "myDefaultProfileValidationSupport")
public DefaultProfileValidationSupport defaultProfileValidationSupport(FhirContext theFhirContext) {
return new DefaultProfileValidationSupport(theFhirContext);
@Autowired
private FhirContext myFhirContext;
@Bean(name = JpaConfig.DEFAULT_PROFILE_VALIDATION_SUPPORT)
public DefaultProfileValidationSupport defaultProfileValidationSupport() {
return new DefaultProfileValidationSupport(myFhirContext);
}
@Bean
@ -56,11 +56,6 @@ public class ValidationSupportConfig {
return retVal;
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT_CHAIN)
public JpaValidationSupportChain jpaValidationSupportChain(FhirContext theFhirContext) {
return new JpaValidationSupportChain(theFhirContext);
}
@Bean(name = JpaConfig.JPA_VALIDATION_SUPPORT)
public IValidationSupport jpaValidationSupport(FhirContext theFhirContext) {
return new JpaPersistedResourceValidationSupport(theFhirContext);
@ -68,26 +63,13 @@ public class ValidationSupportConfig {
@Bean(name = "myInstanceValidator")
public IInstanceValidatorModule instanceValidator(
FhirContext theFhirContext,
CachingValidationSupport theCachingValidationSupport,
ValidationSupportChain theValidationSupportChain,
IValidationSupport theValidationSupport,
DaoRegistry theDaoRegistry) {
if (theFhirContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
FhirInstanceValidator val = new FhirInstanceValidator(theCachingValidationSupport);
val.setValidatorResourceFetcher(
jpaValidatorResourceFetcher(theFhirContext, theValidationSupport, theDaoRegistry));
val.setValidatorPolicyAdvisor(jpaValidatorPolicyAdvisor());
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
val.setValidationSupport(theCachingValidationSupport);
return val;
} else {
CachingValidationSupport cachingValidationSupport = new CachingValidationSupport(
new HapiToHl7OrgDstu2ValidatingSupportWrapper(theValidationSupportChain));
FhirInstanceValidator retVal = new FhirInstanceValidator(cachingValidationSupport);
retVal.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
return retVal;
}
FhirContext theFhirContext, IValidationSupport theValidationSupportChain, DaoRegistry theDaoRegistry) {
FhirInstanceValidator val = new FhirInstanceValidator(theValidationSupportChain);
val.setValidatorResourceFetcher(
jpaValidatorResourceFetcher(theFhirContext, theValidationSupportChain, theDaoRegistry));
val.setValidatorPolicyAdvisor(jpaValidatorPolicyAdvisor());
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.Warning);
return val;
}
@Bean

View File

@ -1,43 +0,0 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package ca.uhn.fhir.jpa.config.util;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
public final class ValidationSupportConfigUtil {
private ValidationSupportConfigUtil() {}
public static CachingValidationSupport newCachingValidationSupport(
JpaValidationSupportChain theJpaValidationSupportChain) {
return newCachingValidationSupport(theJpaValidationSupportChain, false);
}
public static CachingValidationSupport newCachingValidationSupport(
JpaValidationSupportChain theJpaValidationSupportChain,
boolean theIsEnabledValidationForCodingsLogicalAnd) {
// Short timeout for code translation because TermConceptMappingSvcImpl has its own caching
CachingValidationSupport.CacheTimeouts cacheTimeouts =
CachingValidationSupport.CacheTimeouts.defaultValues().setTranslateCodeMillis(1000);
return new CachingValidationSupport(
theJpaValidationSupportChain, cacheTimeouts, theIsEnabledValidationForCodingsLogicalAnd);
}
}

View File

@ -48,6 +48,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@ -243,8 +246,23 @@ public class HistoryBuilder {
Subquery<Date> pastDateSubQuery = theQuery.subquery(Date.class);
Root<ResourceHistoryTable> subQueryResourceHistory = pastDateSubQuery.from(ResourceHistoryTable.class);
Expression myUpdatedMostRecent = theCriteriaBuilder.max(subQueryResourceHistory.get("myUpdated"));
/*
* This conversion from the Date in myRangeEndInclusive into a ZonedDateTime is an experiment -
* There is an intermittent test failure in testSearchHistoryWithAtAndGtParameters() that I can't
* figure out. But I've added a ton of logging to the error it fails with and I noticed that
* we emit SQL along the lines of
* select coalesce(max(rht2_0.RES_UPDATED), timestamp with time zone '2024-10-05 18:24:48.172000000Z')
* for this date, and all other dates are in GMT so this is an experiment. If nothing changes,
* we can roll this back to
* theCriteriaBuilder.literal(myRangeStartInclusive)
* JA 20241005
*/
ZonedDateTime rangeStart =
ZonedDateTime.ofInstant(Instant.ofEpochMilli(myRangeStartInclusive.getTime()), ZoneId.of("GMT"));
Expression myUpdatedMostRecentOrDefault =
theCriteriaBuilder.coalesce(myUpdatedMostRecent, theCriteriaBuilder.literal(myRangeStartInclusive));
theCriteriaBuilder.coalesce(myUpdatedMostRecent, theCriteriaBuilder.literal(rangeStart));
pastDateSubQuery
.select(myUpdatedMostRecentOrDefault)

View File

@ -164,6 +164,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
assert myStructureDefinitionType != null;
return fetchResource(myStructureDefinitionType, theUrl);
}

View File

@ -117,7 +117,6 @@ import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.common.EntityReference;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hibernate.search.mapper.pojo.massindexing.impl.PojoMassIndexingLoggingMonitor;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.context.ConversionContext40_50;
@ -284,9 +283,6 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
@Autowired
private HibernatePropertiesProvider myHibernatePropertiesProvider;
@Autowired
private CachingValidationSupport myCachingValidationSupport;
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@ -2509,9 +2505,7 @@ public class TermReadSvcImpl implements ITermReadSvc, IHasScheduledJobs {
* results while they test changes, which is probably a worthwhile sacrifice
*/
private void afterValueSetExpansionStatusChange() {
// TODO: JA2 - Move this caching into the memorycacheservice, and only purge the
// relevant individual cache
myCachingValidationSupport.invalidateCaches();
provideValidationSupport().invalidateCaches();
}
private synchronized boolean isPreExpandingValueSets() {

View File

@ -20,6 +20,9 @@
package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.model.dstu2.resource.Subscription;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
@ -29,31 +32,32 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static ca.uhn.fhir.subscription.SubscriptionConstants.ORDER_SUBSCRIPTION_ACTIVATING;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Interceptor which requires newly created {@link Subscription subscriptions} to be in
* {@link SubscriptionStatusEnum#REQUESTED} state and prevents clients from changing the status.
*/
public class SubscriptionsRequireManualActivationInterceptorDstu2 extends ServerOperationInterceptorAdapter {
@Interceptor
public class SubscriptionsRequireManualActivationInterceptorDstu2 {
@Autowired
@Qualifier("mySubscriptionDaoDstu2")
private IFhirResourceDao<Subscription> myDao;
@Override
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, order = ORDER_SUBSCRIPTION_ACTIVATING)
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource);
}
}
@Override
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, order = ORDER_SUBSCRIPTION_ACTIVATING)
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource);

View File

@ -20,13 +20,15 @@
package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
@ -34,26 +36,28 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static ca.uhn.fhir.subscription.SubscriptionConstants.ORDER_SUBSCRIPTION_ACTIVATING;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Interceptor which requires newly created {@link Subscription subscriptions} to be in
* {@link SubscriptionStatus#REQUESTED} state and prevents clients from changing the status.
*/
public class SubscriptionsRequireManualActivationInterceptorDstu3 extends ServerOperationInterceptorAdapter {
@Interceptor
public class SubscriptionsRequireManualActivationInterceptorDstu3 {
@Autowired
@Qualifier("mySubscriptionDaoDstu3")
private IFhirResourceDao<Subscription> myDao;
@Override
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, order = ORDER_SUBSCRIPTION_ACTIVATING)
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource);
}
}
@Override
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, order = ORDER_SUBSCRIPTION_ACTIVATING)
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource);

View File

@ -20,13 +20,15 @@
package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Subscription;
import org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType;
@ -34,26 +36,28 @@ import org.hl7.fhir.r4.model.Subscription.SubscriptionStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static ca.uhn.fhir.subscription.SubscriptionConstants.ORDER_SUBSCRIPTION_ACTIVATING;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* Interceptor which requires newly created {@link Subscription subscriptions} to be in
* {@link SubscriptionStatus#REQUESTED} state and prevents clients from changing the status.
*/
public class SubscriptionsRequireManualActivationInterceptorR4 extends ServerOperationInterceptorAdapter {
@Interceptor
public class SubscriptionsRequireManualActivationInterceptorR4 {
@Autowired
@Qualifier("mySubscriptionDaoR4")
private IFhirResourceDao<Subscription> myDao;
@Override
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, order = ORDER_SUBSCRIPTION_ACTIVATING)
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (myDao.getContext().getResourceType(theResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
verifyStatusOk(RestOperationTypeEnum.CREATE, null, theResource);
}
}
@Override
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, order = ORDER_SUBSCRIPTION_ACTIVATING)
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
if (myDao.getContext().getResourceType(theNewResource).equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
verifyStatusOk(RestOperationTypeEnum.UPDATE, theOldResource, theNewResource);

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.config.JpaConfig;
import ca.uhn.fhir.jpa.packages.NpmJpaValidationSupport;
import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
@ -39,7 +40,7 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
private final FhirContext myFhirContext;
@Autowired
@Qualifier("myJpaValidationSupport")
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT)
public IValidationSupport myJpaValidationSupport;
@Qualifier("myDefaultProfileValidationSupport")
@ -64,7 +65,13 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
/**
* Constructor
*/
public JpaValidationSupportChain(FhirContext theFhirContext) {
public JpaValidationSupportChain(
FhirContext theFhirContext, ValidationSupportChain.CacheConfiguration theCacheConfiguration) {
super(theCacheConfiguration);
assert theFhirContext != null;
assert theCacheConfiguration != null;
myFhirContext = theFhirContext;
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -64,6 +64,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static ca.uhn.fhir.subscription.SubscriptionConstants.ORDER_SUBSCRIPTION_VALIDATING;
import static org.apache.commons.lang3.StringUtils.isBlank;
@Interceptor
@ -92,14 +93,14 @@ public class SubscriptionValidatingInterceptor {
@Autowired
private SubscriptionChannelTypeValidatorFactory mySubscriptionChannelTypeValidatorFactory;
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, order = ORDER_SUBSCRIPTION_VALIDATING)
public void resourcePreCreate(
IBaseResource theResource, RequestDetails theRequestDetails, RequestPartitionId theRequestPartitionId) {
validateSubmittedSubscription(
theResource, theRequestDetails, theRequestPartitionId, Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED);
}
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
@Hook(value = Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, order = ORDER_SUBSCRIPTION_VALIDATING)
public void resourceUpdated(
IBaseResource theOldResource,
IBaseResource theResource,

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -58,8 +58,6 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
public static final String URL_MY_VALUE_SET = "http://example.com/my_value_set";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3TerminologyTest.class);
@Autowired
private CachingValidationSupport myCachingValidationSupport;
@Autowired
private ITermDeferredStorageSvc myTermDeferredStorageSvc;
@AfterEach
@ -69,10 +67,12 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(false);
}
@Override
@BeforeEach
public void before() {
public void before() throws Exception {
super.before();
myStorageSettings.setMaximumExpansionSize(5000);
myCachingValidationSupport.invalidateCaches();
myValidationSupport.invalidateCaches();
}
private CodeSystem createExternalCs() {

View File

@ -14,7 +14,6 @@ import ca.uhn.fhir.test.utilities.ProxyUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
@ -55,8 +54,6 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
@Autowired
private IValidatorModule myValidatorModule;
@Autowired
private CachingValidationSupport myValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@Autowired
@Qualifier(JpaConfig.JPA_VALIDATION_SUPPORT)

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -18,6 +18,7 @@ import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -38,7 +39,7 @@ import static org.mockito.Mockito.when;
public class JpaPersistedResourceValidationSupportFromValidationChainTest {
private static final FhirContext ourCtx = FhirContext.forR4();
private IValidationSupport jpaValidator;
private JpaPersistedResourceValidationSupport jpaValidator;
@Mock
private DaoRegistry myDaoRegistry;
@ -58,6 +59,7 @@ public class JpaPersistedResourceValidationSupportFromValidationChainTest {
@BeforeEach
public void setUp() {
jpaValidator = new JpaPersistedResourceValidationSupport(ourCtx);
jpaValidator.start();
ReflectionTestUtils.setField(jpaValidator, "myDaoRegistry", myDaoRegistry);
}

View File

@ -75,7 +75,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
public void before() throws Exception {
super.before();
myStorageSettings.setMaximumExpansionSize(5000);
myCachingValidationSupport.invalidateCaches();
myValidationSupport.invalidateCaches();
}
private CodeSystem createExternalCs() {

View File

@ -38,6 +38,7 @@ import ch.qos.logback.classic.Level;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.UnknownCodeSystemWarningValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -2237,6 +2238,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
createStructureDefinitionInDao();
// execute
((ValidationSupportChain)myValidationSupport).invalidateExpiringCaches();
final String outcomePatientValidateAfterStructDef = validate(PATIENT_WITH_REAL_URL);
// verify

View File

@ -169,7 +169,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
logAllValueSets();
myCachingValidationSupport.invalidateCaches();
myValidationSupport.invalidateCaches();
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "child10", null, "http://vs");
assertNotNull(outcome);
@ -272,7 +272,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
logAllValueSets();
myCachingValidationSupport.invalidateCaches();
myValidationSupport.invalidateCaches();
outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "child10", null, "http://vs");
assertNotNull(outcome);

View File

@ -0,0 +1,496 @@
package ca.uhn.fhir.jpa.interceptor;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import jakarta.annotation.Nonnull;
import jakarta.servlet.http.HttpServletRequest;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* This test verifies how {@link RemoteTerminologyServiceValidationSupport} interacts with
* the rest of the ValidationSupportChain. The aim here is that we should perform as few
* interactions across the network as we can, so any caching that avoids a lookup through
* the remote module is a good thing. We're also testing that we don't open more database
* connections than we need to, since every connection is a delay.
*/
public class RemoteTerminologyServiceJpaR4Test extends BaseJpaR4Test {
private static final MyCodeSystemProvider ourCodeSystemProvider = new MyCodeSystemProvider();
private static final MyValueSetProvider ourValueSetProvider = new MyValueSetProvider();
@RegisterExtension
private static final RestfulServerExtension ourTerminologyServer = new RestfulServerExtension(FhirContext.forR4Cached())
.registerProvider(ourCodeSystemProvider)
.registerProvider(ourValueSetProvider);
@Autowired
private ValidationSupportChain myValidationSupportChain;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
private RemoteTerminologyServiceValidationSupport myRemoteTerminologyService;
private ValidationSupportChain myInternalValidationSupport;
@Override
@BeforeEach
public void before() throws Exception {
super.before();
myFhirInstanceValidator.setBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore);
List<IValidationSupport> original = myValidationSupportChain.getValidationSupports();
myInternalValidationSupport = new ValidationSupportChain(original);
myRemoteTerminologyService = new RemoteTerminologyServiceValidationSupport(myFhirContext, ourTerminologyServer.getBaseUrl());
myValidationSupportChain.addValidationSupport(0, myRemoteTerminologyService);
myValidationSupportChain.invalidateCaches();
// Warm this as it's needed once by the FhirPath evaluator on startup
// so this avoids having different connection counts depending on
// which test method is called first. This is a non-expiring cache, so
// pre-warming here isn't affecting anything meaningful.
myValidationSupportChain.fetchAllStructureDefinitions();
}
@AfterEach
public void after() throws Exception {
myValidationSupportChain.logCacheSizes();
myValidationSupportChain.removeValidationSupport(myRemoteTerminologyService);
ourValueSetProvider.clearAll();
ourCodeSystemProvider.clearAll();
}
@Test
public void testValidateSimpleCode() {
Patient p = new Patient();
p.setGender(Enumerations.AdministrativeGender.FEMALE);
// Test 1
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
IBaseOperationOutcome outcome = validate(p);
assertSuccess(outcome);
// 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"
);
assertThat(ourCodeSystemProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/administrative-gender",
"http://hl7.org/fhir/administrative-gender"
);
// Test 2 (should rely on caches)
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
outcome = validate(p);
assertSuccess(outcome);
// Verify 2
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
}
@Test
public void testValidateSimpleCode_SupportedByRemoteService() {
Patient p = new Patient();
p.setGender(Enumerations.AdministrativeGender.FEMALE);
ourValueSetProvider.add((ValueSet) requireNonNull(myInternalValidationSupport.fetchValueSet("http://hl7.org/fhir/ValueSet/administrative-gender")));
ourCodeSystemProvider.add((CodeSystem) requireNonNull(myInternalValidationSupport.fetchCodeSystem("http://hl7.org/fhir/administrative-gender")));
// Test 1
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
IBaseOperationOutcome outcome = validate(p);
assertSuccess(outcome);
// Verify 1
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/ValueSet/administrative-gender",
"http://hl7.org/fhir/ValueSet/administrative-gender"
);
assertThat(ourValueSetProvider.myValidatedCodes).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/ValueSet/administrative-gender#http://hl7.org/fhir/administrative-gender#female"
);
assertThat(ourCodeSystemProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/administrative-gender"
);
assertThat(ourCodeSystemProvider.myValidatedCodes).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/administrative-gender#female#null"
);
// Test 2 (should rely on caches)
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
outcome = validate(p);
assertSuccess(outcome);
// Verify 2
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().isEmpty();
assertThat(ourValueSetProvider.myValidatedCodes).asList().isEmpty();
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.myValidatedCodes).asList().isEmpty();
}
/**
* If the remote terminology service is serving up stub ValueSet and CodeSystem
* resources, make sure we still behave in a sane way. This probably wouldn't
* happen exactly like this, but the idea here is that the server could
* serve up weird contents where our internal services couldn't determine
* the implicit system from the ValueSet.
*/
@Test
public void testValidateSimpleCode_SupportedByRemoteService_EmptyValueSet() {
Patient p = new Patient();
p.setGender(Enumerations.AdministrativeGender.FEMALE);
ourValueSetProvider.add((ValueSet) new ValueSet().setUrl("http://hl7.org/fhir/ValueSet/administrative-gender").setId("gender"));
ourCodeSystemProvider.add((CodeSystem) new CodeSystem().setUrl("http://hl7.org/fhir/administrative-gender").setId("gender"));
// Test 1
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
IBaseOperationOutcome outcome = validate(p);
assertSuccess(outcome);
// Verify 1
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/ValueSet/administrative-gender",
"http://hl7.org/fhir/ValueSet/administrative-gender"
);
assertThat(ourValueSetProvider.myValidatedCodes).asList().containsExactlyInAnyOrder(
"http://hl7.org/fhir/ValueSet/administrative-gender#null#female"
);
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.myValidatedCodes).asList().isEmpty();
// Test 2 (should rely on caches)
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
outcome = validate(p);
assertSuccess(outcome);
// Verify 2
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().isEmpty();
assertThat(ourValueSetProvider.myValidatedCodes).asList().isEmpty();
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.myValidatedCodes).asList().isEmpty();
myValidationSupportChain.logCacheSizes();
}
@Test
public void testValidateSimpleExtension() {
// Setup
myStructureDefinitionDao.create(createFooExtensionStructureDefinition(), mySrd);
Patient p = new Patient();
p.addExtension("http://foo", new StringType("BAR"));
// Test 1
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
IBaseOperationOutcome outcome = validate(p);
assertSuccess(outcome);
// Verify 1
myCaptureQueriesListener.logSelectQueries();
Assertions.assertEquals(4, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
// Test 2 (should rely on caches)
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
outcome = validate(p);
assertSuccess(outcome);
// Verify 2
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
}
@Test
public void testValidateMultipleCodings() {
// Setup
Patient p = new Patient();
p.addIdentifier()
// Valid type
.setType(new CodeableConcept().addCoding(new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "DL", null)))
.setSystem("http://my-system-1")
.setValue("1");
p.addIdentifier()
// Valid type
.setType(new CodeableConcept().addCoding(new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "PPN", null)))
.setSystem("http://my-system-2")
.setValue("2");
p.addIdentifier()
// Invalid type
.setType(new CodeableConcept().addCoding(new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "FOO", null)))
.setSystem("http://my-system-3")
.setValue("3");
// Test 1
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
IBaseOperationOutcome outcome = validate(p);
assertHasIssuesContainingMessages(outcome,
"Unknown code 'http://terminology.hl7.org/CodeSystem/v2-0203#FOO'",
"None of the codings provided are in the value set 'IdentifierType'");
// 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"
);
assertThat(ourCodeSystemProvider.mySearchUrls).asList().containsExactlyInAnyOrder(
"http://terminology.hl7.org/CodeSystem/v2-0203",
"http://terminology.hl7.org/CodeSystem/v2-0203"
);
assertEquals(0, ourValueSetProvider.myValidatedCodes.size());
assertEquals(0, ourCodeSystemProvider.myValidatedCodes.size());
// Test 2 (should rely on caches)
ourCodeSystemProvider.clearCalls();
ourValueSetProvider.clearCalls();
myCaptureQueriesListener.clear();
validate(p);
// Verify 2
Assertions.assertEquals(0, myCaptureQueriesListener.countGetConnections());
assertThat(ourValueSetProvider.mySearchUrls).asList().isEmpty();
assertThat(ourCodeSystemProvider.mySearchUrls).asList().isEmpty();
assertEquals(0, ourValueSetProvider.myValidatedCodes.size());
assertEquals(0, ourCodeSystemProvider.myValidatedCodes.size());
}
private void assertSuccess(IBaseOperationOutcome theOutcome) {
OperationOutcome oo = (OperationOutcome) theOutcome;
assertEquals(1, oo.getIssue().size(), () -> encode(oo));
assertThat(oo.getIssue().get(0).getDiagnostics()).as(() -> encode(oo)).contains("No issues detected");
}
private void assertHasIssuesContainingMessages(IBaseOperationOutcome theOutcome, String... theDiagnosticMessageFragments) {
OperationOutcome oo = (OperationOutcome) theOutcome;
assertEquals(theDiagnosticMessageFragments.length, oo.getIssue().size(), () -> encode(oo));
for (int i = 0; i < theDiagnosticMessageFragments.length; i++) {
assertThat(oo.getIssue().get(i).getDiagnostics()).as(() -> encode(oo)).contains(theDiagnosticMessageFragments[i]);
}
}
private IBaseOperationOutcome validate(Patient p) {
return myPatientDao.validate(p, null, myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(p), EncodingEnum.JSON, ValidationModeEnum.CREATE, null, mySrd).getOperationOutcome();
}
/**
* Create a StructureDefinition for an extension with URL <a href="http://foo">http://foo</a>
*/
@Nonnull
private static StructureDefinition createFooExtensionStructureDefinition() {
StructureDefinition sd = new StructureDefinition();
sd.setUrl("http://foo");
sd.setFhirVersion(Enumerations.FHIRVersion._4_0_1);
sd.setKind(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE);
sd.setAbstract(false);
sd.addContext().setType(StructureDefinition.ExtensionContextType.ELEMENT).setExpression("Patient");
sd.setType("Extension");
sd.setBaseDefinition("http://hl7.org/fhir/StructureDefinition/Extension");
sd.setDerivation(StructureDefinition.TypeDerivationRule.CONSTRAINT);
ElementDefinition e0 = sd.getDifferential().addElement();
e0.setId("Extension");
e0.setPath("Extension");
ElementDefinition e1 = sd.getDifferential().addElement();
e1.setId("Extension.url");
e1.setPath("Extension.url");
e1.setFixed(new UriType("http://foo"));
ElementDefinition e2 = sd.getDifferential().addElement();
e2.setId("Extension.value[x]");
e2.setPath("Extension.value[x]");
e2.addType().setCode("string");
return sd;
}
private static String toValue(IPrimitiveType<String> theUrlType) {
return theUrlType != null ? theUrlType.getValue() : null;
}
private static class MyCodeSystemProvider implements IResourceProvider {
private final ListMultimap<String, CodeSystem> myUrlToCodeSystems = MultimapBuilder.hashKeys().arrayListValues().build();
private final List<String> mySearchUrls = new ArrayList<>();
private final List<String> myValidatedCodes = new ArrayList<>();
public void clearAll() {
myUrlToCodeSystems.clear();
clearCalls();
}
public void clearCalls() {
mySearchUrls.clear();
myValidatedCodes.clear();
}
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theCodeSystemUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay
) {
myValidatedCodes.add(toValue(theCodeSystemUrl) + "#" + toValue(theCode) + "#" + toValue(theDisplay));
Parameters retVal = new Parameters();
retVal.addParameter("result", new BooleanType(true));
return retVal;
}
@Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
String url = theUrlParam != null ? theUrlParam.getValue() : null;
mySearchUrls.add(url);
return myUrlToCodeSystems.get(defaultString(url));
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
public void add(CodeSystem theCs) {
assert theCs != null;
assert isNotBlank(theCs.getUrl());
myUrlToCodeSystems.put(theCs.getUrl(), theCs);
}
}
@SuppressWarnings("unused")
private static class MyValueSetProvider implements IResourceProvider {
private final ListMultimap<String, ValueSet> myUrlToValueSets = MultimapBuilder.hashKeys().arrayListValues().build();
private final List<String> mySearchUrls = new ArrayList<>();
private final List<String> myValidatedCodes = new ArrayList<>();
public void clearAll() {
myUrlToValueSets.clear();
clearCalls();
}
public void clearCalls() {
mySearchUrls.clear();
myValidatedCodes.clear();
}
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "valueSet") ValueSet theValueSet
) {
myValidatedCodes.add(toValue(theValueSetUrl) + "#" + toValue(theSystem) + "#" + toValue(theCode));
Parameters retVal = new Parameters();
retVal.addParameter("result", new BooleanType(true));
return retVal;
}
@Search
public List<ValueSet> find(@OptionalParam(name = "url") UriParam theUrlParam) {
String url = theUrlParam != null ? theUrlParam.getValue() : null;
mySearchUrls.add(url);
List<ValueSet> retVal = myUrlToValueSets.get(defaultString(url));
ourLog.info("Remote terminology fetch ValueSet[{}] - Found: {}", url, !retVal.isEmpty());
return retVal;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return ValueSet.class;
}
public void add(ValueSet theVs) {
assert theVs != null;
assert isNotBlank(theVs.getUrl());
myUrlToValueSets.put(theVs.getUrl(), theVs);
}
}
}

View File

@ -2366,18 +2366,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
IIdType id = idCreated.toUnqualifiedVersionless();
for (int i = 0; i < 10; i++) {
sleepOneClick();
sleepAtLeast(100);
preDates.add(new Date());
sleepOneClick();
sleepAtLeast(100);
patient.setId(id);
patient.getName().get(0).getFamilyElement().setValue(methodName + "_i" + i);
ids.add(myPatientDao.update(patient, mySrd).getId().toUnqualified().getValue());
sleepOneClick();
}
List<String> idValues;
myCaptureQueriesListener.clear();
idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + id.getIdPart() + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3)));
myCaptureQueriesListener.logSelectQueries();
assertThat(idValues).as(idValues.toString()).containsExactly(ids.get(3), ids.get(2), ids.get(1), ids.get(0));
idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3)));

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -1,7 +1,5 @@
package ca.uhn.fhir.jpa.dao.r5;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
@ -17,7 +15,6 @@ import ca.uhn.fhir.jpa.util.ValueSetTestUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.CodeSystem;
@ -38,10 +35,12 @@ import java.io.IOException;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
@ -51,7 +50,6 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
@Autowired
protected ITermDeferredStorageSvc myTerminologyDeferredStorageSvc;
private IIdType myExtensionalVsId;
@AfterEach
@ -61,7 +59,6 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
myStorageSettings.setExpungeEnabled(new JpaStorageSettings().isExpungeEnabled());
}
@BeforeEach
@Transactional
public void before02() throws IOException {
@ -251,7 +248,6 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
}
/**
* See #4305
*/
@ -267,7 +263,7 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
// Update valueset
vs.setName("Hello");
assertEquals("2", myValueSetDao.update(vs, mySrd).getId().getVersionIdPart());
runInTransaction(()->{
runInTransaction(() -> {
Optional<ResourceTable> resource = myResourceTableDao.findById(id.getIdPartAsLong());
assertTrue(resource.isPresent());
});
@ -287,13 +283,12 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
myValueSetDao.expunge(id, new ExpungeOptions().setExpungeDeletedResources(true).setExpungeOldVersions(true), mySrd);
// Verify expunged
runInTransaction(()->{
runInTransaction(() -> {
Optional<ResourceTable> resource = myResourceTableDao.findById(id.getIdPartAsLong());
assertFalse(resource.isPresent());
});
}
@Test
public void testExpandByValueSet_ExceedsMaxSize() {
// Add a bunch of codes
@ -336,12 +331,12 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
logAllValueSets();
myCachingValidationSupport.invalidateCaches();
myValidationSupport.invalidateCaches();
// Validate code
ValidationSupportContext ctx = new ValidationSupportContext(myValidationSupport);
ConceptValidationOptions options= new ConceptValidationOptions();
ConceptValidationOptions options = new ConceptValidationOptions();
IValidationSupport.CodeValidationResult outcome = myValidationSupport.validateCode(ctx, options, "http://cs", "CODE4", null, "http://vs");
assertNotNull(outcome);
assertTrue(outcome.isOk());
@ -354,9 +349,6 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
assertThat(expansionMessage).startsWith("ValueSet was expanded using an expansion that was pre-calculated");
}
@Autowired
protected CachingValidationSupport myCachingValidationSupport;
@Test
public void testValidateCodeAgainstBuiltInValueSetAndCodeSystemWithValidCode() {
IPrimitiveType<String> display = null;

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -118,7 +118,6 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import ca.uhn.test.util.LogbackTestExtension;
import jakarta.persistence.EntityManager;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -246,8 +245,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Autowired
protected ITermReadSvc myHapiTerminologySvc;
@Autowired
protected CachingValidationSupport myCachingValidationSupport;
@Autowired
protected ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc;
@Autowired
protected ISearchDao mySearchEntityDao;

View File

@ -271,6 +271,8 @@ public abstract class BaseJpaTest extends BaseTest {
@Autowired
private IValidationSupport myJpaPersistedValidationSupport;
@Autowired
private IValidationSupport myValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@Autowired
private IResourceTableDao myResourceTableDao;
@ -398,6 +400,10 @@ public abstract class BaseJpaTest extends BaseTest {
if (myFhirInstanceValidator != null) {
myFhirInstanceValidator.invalidateCaches();
}
if (myValidationSupport != null) {
myValidationSupport.invalidateCaches();
}
JpaStorageSettings defaultConfig = new JpaStorageSettings();
myStorageSettings.setAdvancedHSearchIndexing(defaultConfig.isAdvancedHSearchIndexing());
myStorageSettings.setAllowContainsSearches(defaultConfig.isAllowContainsSearches());

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -58,4 +58,7 @@ public class SubscriptionConstants {
"http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-payload-content";
public static final String SUBSCRIPTION_TOPIC_STATUS =
"http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition/backport-subscription-status-r4";
public static final int ORDER_SUBSCRIPTION_VALIDATING = 100;
public static final int ORDER_SUBSCRIPTION_ACTIVATING = 200;
}

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -21,7 +21,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -27,6 +27,8 @@ import net.ttddyy.dsproxy.QueryInfo;
import net.ttddyy.dsproxy.listener.MethodExecutionContext;
import net.ttddyy.dsproxy.proxy.ParameterSetOperation;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
@ -38,7 +40,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
public abstract class BaseCaptureQueriesListener
implements ProxyDataSourceBuilder.SingleQueryExecution, ProxyDataSourceBuilder.SingleMethodExecution {
private static final Logger ourLog = LoggerFactory.getLogger(BaseCaptureQueriesListener.class);
private boolean myCaptureQueryStackTrace = false;
/**
@ -112,6 +114,9 @@ public abstract class BaseCaptureQueriesListener
@Nullable
protected abstract AtomicInteger provideCommitCounter();
@Nullable
protected abstract AtomicInteger provideGetConnectionCounter();
@Nullable
protected abstract AtomicInteger provideRollbackCounter();
@ -125,6 +130,9 @@ public abstract class BaseCaptureQueriesListener
case "rollback":
counter = provideRollbackCounter();
break;
case "getConnection":
counter = provideGetConnectionCounter();
break;
}
if (counter != null) {
@ -132,10 +140,23 @@ public abstract class BaseCaptureQueriesListener
}
}
/**
* @return Returns the number of times the connection pool was asked for a new connection
*/
public int countGetConnections() {
return provideGetConnectionCounter().get();
}
/**
* @return Returns the number of DB commits which have happened against connections from the pool
*/
public int countCommits() {
return provideCommitCounter().get();
}
/**
* @return Returns the number of DB rollbacks which have happened against connections from the pool
*/
public int countRollbacks() {
return provideRollbackCounter().get();
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.util.StopWatch;
import com.google.common.collect.Queues;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.collections4.queue.CircularFifoQueue;
import org.hl7.fhir.r4.model.InstantType;
@ -58,6 +59,7 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
private static final int CAPACITY = 10000;
private static final Logger ourLog = LoggerFactory.getLogger(CircularQueueCaptureQueriesListener.class);
private Queue<SqlQuery> myQueries;
private AtomicInteger myGetConnectionCounter;
private AtomicInteger myCommitCounter;
private AtomicInteger myRollbackCounter;
@ -91,6 +93,12 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
return myCommitCounter;
}
@Nullable
@Override
protected AtomicInteger provideGetConnectionCounter() {
return myGetConnectionCounter;
}
@Override
protected AtomicInteger provideRollbackCounter() {
return myRollbackCounter;
@ -101,6 +109,7 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
*/
public void clear() {
myQueries.clear();
myGetConnectionCounter.set(0);
myCommitCounter.set(0);
myRollbackCounter.set(0);
}
@ -110,6 +119,7 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
*/
public void startCollecting() {
myQueries = Queues.synchronizedQueue(new CircularFifoQueue<>(CAPACITY));
myGetConnectionCounter = new AtomicInteger(0);
myCommitCounter = new AtomicInteger(0);
myRollbackCounter = new AtomicInteger(0);
}
@ -167,10 +177,18 @@ public class CircularQueueCaptureQueriesListener extends BaseCaptureQueriesListe
return getQueriesMatching(thePredicate, threadName);
}
/**
* @deprecated Use {@link #countCommits()}
*/
@Deprecated
public int getCommitCount() {
return myCommitCounter.get();
}
/**
* @deprecated Use {@link #countRollbacks()}
*/
@Deprecated
public int getRollbackCount() {
return myRollbackCounter.get();
}

View File

@ -19,6 +19,7 @@
*/
package ca.uhn.fhir.jpa.util;
import jakarta.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -32,6 +33,7 @@ import java.util.stream.Collectors;
public class CurrentThreadCaptureQueriesListener extends BaseCaptureQueriesListener {
private static final ThreadLocal<Queue<SqlQuery>> ourQueues = new ThreadLocal<>();
private static final ThreadLocal<AtomicInteger> ourGetConnections = new ThreadLocal<>();
private static final ThreadLocal<AtomicInteger> ourCommits = new ThreadLocal<>();
private static final ThreadLocal<AtomicInteger> ourRollbacks = new ThreadLocal<>();
private static final Logger ourLog = LoggerFactory.getLogger(CurrentThreadCaptureQueriesListener.class);
@ -46,6 +48,12 @@ public class CurrentThreadCaptureQueriesListener extends BaseCaptureQueriesListe
return ourCommits.get();
}
@Nullable
@Override
protected AtomicInteger provideGetConnectionCounter() {
return ourGetConnections.get();
}
@Override
protected AtomicInteger provideRollbackCounter() {
return ourRollbacks.get();
@ -57,6 +65,7 @@ public class CurrentThreadCaptureQueriesListener extends BaseCaptureQueriesListe
public static SqlQueryList getCurrentQueueAndStopCapturing() {
Queue<SqlQuery> retVal = ourQueues.get();
ourQueues.remove();
ourGetConnections.remove();
ourCommits.remove();
ourRollbacks.remove();
if (retVal == null) {
@ -76,6 +85,7 @@ public class CurrentThreadCaptureQueriesListener extends BaseCaptureQueriesListe
*/
public static void startCapturing() {
ourQueues.set(new ArrayDeque<>());
ourGetConnections.set(new AtomicInteger(0));
ourCommits.set(new AtomicInteger(0));
ourRollbacks.set(new AtomicInteger(0));
}

View File

@ -20,6 +20,7 @@
package ca.uhn.fhir.jpa.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -75,10 +76,16 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
target = dao.read(id, (RequestDetails) appContext);
} catch (ResourceNotFoundException e) {
ourLog.info("Failed to resolve local reference: {}", theUrl);
try {
target = fetchByUrl(theUrl, dao, (RequestDetails) appContext);
} catch (ResourceNotFoundException e2) {
ourLog.info("Failed to find resource by URL: {}", theUrl);
RuntimeResourceDefinition def = myFhirContext.getResourceDefinition(resourceType);
if (def.getChildByName("url") != null) {
try {
target = fetchByUrl(theUrl, dao, (RequestDetails) appContext);
} catch (ResourceNotFoundException e2) {
ourLog.info("Failed to find resource by URL: {}", theUrl);
return null;
}
} else {
return null;
}
}
@ -86,7 +93,7 @@ public class ValidatorResourceFetcher implements IValidatorResourceFetcher {
return new JsonParser(myVersionSpecificContextWrapper)
.parse(myFhirContext.newJsonParser().encodeResourceToString(target), resourceType);
} catch (Exception e) {
throw new FHIRException(Msg.code(576) + e);
throw new FHIRException(Msg.code(576) + e, e);
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>7.7.7-SNAPSHOT</version>
<version>7.7.8-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

Some files were not shown because too many files have changed in this diff Show More