Add fallback for versioned base FHIR StructureDefinition lookups (#911)
* Add fallback for versioned base FHIR StructureDefinition lookups Problem: Implementation Guides (e.g., ch-core) may reference base FHIR resources with explicit versions like "http://hl7.org/fhir/StructureDefinition/Organization|4.0.1". These lookups fail because base FHIR StructureDefinitions are intentionally cached without version (see hapi-fhir PR #7236). This causes validation errors like: - "Unable to resolve the profile reference '...|4.0.1'" - "Invalid Resource target type. Found Organization, but expected one of ([])" Solution: Add VersionedUrlFallbackValidationSupport that intercepts versioned lookups for base FHIR StructureDefinitions (http://hl7.org/fhir/StructureDefinition/*) and falls back to: 1. Major.minor version (e.g., 4.0.1 -> 4.0) 2. Non-versioned URL The fallback logs a warning when triggered, allowing visibility into which IGs reference versioned base resources. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Remove duplicates * formatting * removing unneeded thread local * corrected according to review * cleanup * removed not needed const * tests include some chain now * Update src/test/java/ca/uhn/fhir/jpa/starter/validation/VersionedUrlFallbackValidationSupportTest.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * updated doc * adding tests * Add TODO comment for core fix in validation support Added a comment indicating a TODO for future improvement. * Update TODO comment for core fix in validation support --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
fc395b613a
commit
63256fe1d2
@@ -0,0 +1,29 @@
|
||||
package ca.uhn.fhir.jpa.starter.validation;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuration that enables versioned URL fallback behavior for FHIR validation.
|
||||
*
|
||||
* This wraps the validation support chain to add fallback logic for versioned canonical URLs.
|
||||
* When a versioned URL like "http://hl7.org/fhir/StructureDefinition/Organization|4.0.1"
|
||||
* cannot be found, it will automatically fall back to Non-versioned URL (without the |version suffix)
|
||||
*
|
||||
* This is useful when Implementation Guides reference versioned base FHIR resources
|
||||
* that aren't loaded with exact version matching.
|
||||
*/
|
||||
@Configuration
|
||||
public class VersionedUrlFallbackConfig {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(VersionedUrlFallbackConfig.class);
|
||||
|
||||
public VersionedUrlFallbackConfig(FhirContext theFhirContext, ValidationSupportChain theValidationSupportChain) {
|
||||
ourLog.info("Adding VersionedUrlFallbackValidationSupport to validation chain");
|
||||
theValidationSupportChain.addValidationSupport(
|
||||
0, new VersionedUrlFallbackValidationSupport(theFhirContext, theValidationSupportChain));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package ca.uhn.fhir.jpa.starter.validation;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A validation support that provides fallback behavior for versioned canonical URLs.
|
||||
*
|
||||
* When a versioned URL like "http://hl7.org/fhir/StructureDefinition/Organization|4.0.1"
|
||||
* is requested, this support first tries the exact versioned URL, then falls back to
|
||||
* the non-versioned URL if not found.
|
||||
*
|
||||
* For non-versioned URLs or URLs not matching the configured prefixes, this support
|
||||
* returns null to let other supports in the chain handle the request.
|
||||
*
|
||||
* This addresses issues where profiles reference versioned base FHIR resources that
|
||||
* aren't available with exact version matching in the validation context.
|
||||
*/
|
||||
// TODO: this should be fixed in core
|
||||
public class VersionedUrlFallbackValidationSupport implements IValidationSupport {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(VersionedUrlFallbackValidationSupport.class);
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
private final IValidationSupport myChain;
|
||||
private final Set<String> myUrlPrefixes;
|
||||
|
||||
/**
|
||||
* Creates a fallback validation support that only applies to URLs starting with the default prefix
|
||||
* (http://hl7.org/fhir/StructureDefinition/).
|
||||
*/
|
||||
public VersionedUrlFallbackValidationSupport(FhirContext theFhirContext, IValidationSupport theChain) {
|
||||
this(theFhirContext, theChain, Set.of(URL_PREFIX_STRUCTURE_DEFINITION));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fallback validation support that only applies to URLs starting with the specified prefixes.
|
||||
*
|
||||
* @param theFhirContext the FHIR context
|
||||
* @param theChain the validation support chain to delegate fallback lookups to
|
||||
* @param theUrlPrefixes the URL prefixes to apply fallback logic to (e.g., "http://hl7.org/fhir/StructureDefinition/").
|
||||
* Pass an empty set to apply to all URLs.
|
||||
*/
|
||||
public VersionedUrlFallbackValidationSupport(
|
||||
FhirContext theFhirContext, IValidationSupport theChain, Set<String> theUrlPrefixes) {
|
||||
myFhirContext = theFhirContext;
|
||||
myChain = theChain;
|
||||
myUrlPrefixes = theUrlPrefixes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirContext getFhirContext() {
|
||||
return myFhirContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) {
|
||||
return doFetchWithFallback(theUri, uri -> myChain.fetchResource(theClass, uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBaseResource fetchStructureDefinition(String theUrl) {
|
||||
return doFetchWithFallback(theUrl, myChain::fetchStructureDefinition);
|
||||
}
|
||||
|
||||
private <T extends IBaseResource> T doFetchWithFallback(String theUrl, Function<String, T> theFetcher) {
|
||||
// Check if this is a versioned URL (contains |)
|
||||
int pipeIndex = theUrl.indexOf('|');
|
||||
if (pipeIndex <= 0) {
|
||||
// Not a versioned URL, let other supports handle it
|
||||
return null;
|
||||
}
|
||||
|
||||
String baseUrl = theUrl.substring(0, pipeIndex);
|
||||
|
||||
// Check if this URL matches our configured prefixes
|
||||
if (!matchesPrefix(baseUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try exact versioned URL first
|
||||
T result = theFetcher.apply(theUrl);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Try non-versioned URL fallback
|
||||
result = theFetcher.apply(baseUrl);
|
||||
if (result != null) {
|
||||
ourLog.warn(
|
||||
"Requested versioned canonical '{}' not found, falling back to non-versioned '{}'",
|
||||
theUrl,
|
||||
baseUrl);
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean matchesPrefix(String theUrl) {
|
||||
if (myUrlPrefixes.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (String prefix : myUrlPrefixes) {
|
||||
if (theUrl.startsWith(prefix)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "VersionedUrlFallbackValidationSupport";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user