Bump core version to 5.2.16 (#2308)

* Bump core library version

* Test fix

* Build fixes

* Some test fixes

* One more bit of docs

* Build fix

* Test fix

* Test fix

* Docs fix
This commit is contained in:
James Agnew 2021-01-21 05:30:23 -05:00 committed by GitHub
parent a538ed571a
commit 14b0688f69
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 278 additions and 175 deletions

View File

@ -32,9 +32,11 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -77,9 +79,9 @@ public interface IValidationSupport {
* Expands the given portion of a ValueSet * Expands the given portion of a ValueSet
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theExpansionOptions If provided (may be <code>null</code>), contains options controlling the expansion * @param theExpansionOptions If provided (may be <code>null</code>), contains options controlling the expansion
* @param theValueSetToExpand The valueset that should be expanded * @param theValueSetToExpand The valueset that should be expanded
* @return The expansion, or null * @return The expansion, or null
*/ */
default ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, @Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) { default ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, @Nullable ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) {
@ -116,15 +118,33 @@ public interface IValidationSupport {
* Loads a resource needed by the validation (a StructureDefinition, or a * Loads a resource needed by the validation (a StructureDefinition, or a
* ValueSet) * ValueSet)
* *
* @param theClass The type of the resource to load * <p>
* Note: Since 5.3.0, {@literal theClass} can be {@literal null}
* </p>
*
* @param theClass The type of the resource to load, or <code>null</code> to return any resource with the given canonical URI
* @param theUri The resource URI * @param theUri The resource URI
* @return Returns the resource, or <code>null</code> if no resource with the * @return Returns the resource, or <code>null</code> if no resource with the
* given URI can be found * given URI can be found
*/ */
default <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) { @SuppressWarnings("unchecked")
Validate.notNull(theClass, "theClass must not be null or blank"); default <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
Validate.notBlank(theUri, "theUri must not be null or blank"); Validate.notBlank(theUri, "theUri must not be null or blank");
if (theClass == null) {
Supplier<IBaseResource>[] sources = new Supplier[]{
() -> fetchStructureDefinition(theUri),
() -> fetchValueSet(theUri),
() -> fetchCodeSystem(theUri)
};
return (T) Arrays
.stream(sources)
.map(t -> t.get())
.filter(t -> t != null)
.findFirst()
.orElse(null);
}
switch (getFhirContext().getResourceType(theClass)) { switch (getFhirContext().getResourceType(theClass)) {
case "StructureDefinition": case "StructureDefinition":
return theClass.cast(fetchStructureDefinition(theUri)); return theClass.cast(fetchStructureDefinition(theUri));
@ -150,8 +170,8 @@ public interface IValidationSupport {
* or validated * or validated
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theSystem The URI for the code system, e.g. <code>"http://loinc.org"</code> * @param theSystem The URI for the code system, e.g. <code>"http://loinc.org"</code>
* @return Returns <code>true</code> if codes in the given code system can be * @return Returns <code>true</code> if codes in the given code system can be
* validated * validated
*/ */
@ -172,11 +192,11 @@ public interface IValidationSupport {
* binding fields (e.g. <code>Observation.code</code> in the default profile. * binding fields (e.g. <code>Observation.code</code> in the default profile.
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theOptions Provides options controlling the validation * @param theOptions Provides options controlling the validation
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>" * @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated * @param theDisplay The display name, if it should also be validated
* @return Returns a validation result object * @return Returns a validation result object
*/ */
default CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { default CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
@ -189,11 +209,11 @@ public interface IValidationSupport {
* binding fields (e.g. <code>Observation.code</code> in the default profile. * binding fields (e.g. <code>Observation.code</code> in the default profile.
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>" * @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated * @param theDisplay The display name, if it should also be validated
* @param theValueSet The ValueSet to validate against. Must not be null, and must be a ValueSet resource. * @param theValueSet The ValueSet to validate against. Must not be null, and must be a ValueSet resource.
* @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request * @return Returns a validation result object, or <code>null</code> if this validation support module can not handle this kind of request
*/ */
default CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { default CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
@ -204,9 +224,9 @@ public interface IValidationSupport {
* Look up a code using the system and code value * Look up a code using the system and code value
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theSystem The CodeSystem URL * @param theSystem The CodeSystem URL
* @param theCode The code * @param theCode The code
*/ */
default LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) { default LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode) {
return null; return null;
@ -217,8 +237,8 @@ public interface IValidationSupport {
* validation support module * validation support module
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @param theValueSetUrl The ValueSet canonical URL * @param theValueSetUrl The ValueSet canonical URL
*/ */
default boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { default boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
return false; return false;
@ -228,7 +248,7 @@ public interface IValidationSupport {
* Generate a snapshot from the given differential profile. * Generate a snapshot from the given differential profile.
* *
* @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to * @param theValidationSupportContext The validation support module will be passed in to this method. This is convenient in cases where the operation needs to make calls to
* other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter. * other method in the support chain, so that they can be passed through the entire chain. Implementations of this interface may always safely ignore this parameter.
* @return Returns null if this module does not know how to handle this request * @return Returns null if this module does not know how to handle this request
*/ */
default IBaseResource generateSnapshot(ValidationSupportContext theValidationSupportContext, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) { default IBaseResource generateSnapshot(ValidationSupportContext theValidationSupportContext, IBaseResource theInput, String theUrl, String theWebUrl, String theProfileName) {
@ -249,6 +269,25 @@ public interface IValidationSupport {
} }
enum IssueSeverity {
/**
* The issue caused the action to fail, and no further checking could be performed.
*/
FATAL,
/**
* The issue is sufficiently important to cause the action to fail.
*/
ERROR,
/**
* The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
*/
WARNING,
/**
* The issue has no relation to the degree of success of the action.
*/
INFORMATION
}
class ConceptDesignation { class ConceptDesignation {
private String myLanguage; private String myLanguage;
private String myUseSystem; private String myUseSystem;
@ -365,25 +404,6 @@ public interface IValidationSupport {
} }
} }
enum IssueSeverity {
/**
* The issue caused the action to fail, and no further checking could be performed.
*/
FATAL,
/**
* The issue is sufficiently important to cause the action to fail.
*/
ERROR,
/**
* The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
*/
WARNING,
/**
* The issue has no relation to the degree of success of the action.
*/
INFORMATION
}
class CodeValidationResult { class CodeValidationResult {
private String myCode; private String myCode;
private String myMessage; private String myMessage;

View File

@ -66,6 +66,9 @@ public class ChangelogFilesTest {
// this one is optional // this one is optional
boolean haveIssue = fieldNames.remove("issue"); boolean haveIssue = fieldNames.remove("issue");
// this one is optional
fieldNames.remove("backport");
assertThat("Invalid element in " + next + ": " + fieldNames, fieldNames, empty()); assertThat("Invalid element in " + next + ": " + fieldNames, fieldNames, empty());
if (haveIssue) { if (haveIssue) {

View File

@ -46,9 +46,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.transaction.Transactional; import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
@ -104,110 +107,13 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
@Override @Override
@SuppressWarnings({"unchecked", "unused"}) @SuppressWarnings({"unchecked", "unused"})
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) { public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
if (isBlank(theUri)) { if (isBlank(theUri)) {
return null; return null;
} }
String key = theClass.getSimpleName() + " " + theUri; String key = theClass + " " + theUri;
IBaseResource fetched = myLoadCache.get(key, t -> { IBaseResource fetched = myLoadCache.get(key, t -> doFetchResource(theClass, theUri));
IdType id = new IdType(theUri);
boolean localReference = false;
if (id.hasBaseUrl() == false && id.hasIdPart() == true) {
localReference = true;
}
String resourceName = myFhirContext.getResourceType(theClass);
IBundleProvider search;
switch (resourceName) {
case "ValueSet":
if (localReference) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(IAnyResource.SP_RES_ID, new StringParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
if (search.size() == 0) {
params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ValueSet.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
}
} else {
int versionSeparator = theUri.lastIndexOf('|');
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (versionSeparator != -1) {
params.add(ValueSet.SP_VERSION, new TokenParam(theUri.substring(versionSeparator + 1)));
params.add(ValueSet.SP_URL, new UriParam(theUri.substring(0, versionSeparator)));
} else {
params.add(ValueSet.SP_URL, new UriParam(theUri));
}
params.setSort(new SortSpec("_lastUpdated").setOrder(SortOrderEnum.DESC));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
}
break;
case "StructureDefinition": {
// Don't allow the core FHIR definitions to be overwritten
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (myFhirContext.getElementDefinition(typeName) != null) {
return myNoMatch;
}
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("StructureDefinition").search(params);
break;
}
case "Questionnaire": {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) {
params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart()));
} else {
params.add(Questionnaire.SP_URL, new UriParam(id.getValue()));
}
search = myDaoRegistry.getResourceDao("Questionnaire").search(params);
break;
}
case "CodeSystem": {
int versionSeparator = theUri.lastIndexOf('|');
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (versionSeparator != -1) {
params.add(CodeSystem.SP_VERSION, new TokenParam(theUri.substring(versionSeparator + 1)));
params.add(CodeSystem.SP_URL, new UriParam(theUri.substring(0, versionSeparator)));
} else {
params.add(CodeSystem.SP_URL, new UriParam(theUri));
}
params.setSort(new SortSpec("_lastUpdated").setOrder(SortOrderEnum.DESC));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
break;
}
case "ImplementationGuide":
case "SearchParameter": {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
break;
}
default:
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
Integer size = search.size();
if (size == null || size == 0) {
return myNoMatch;
}
if (size > 1) {
ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri);
}
return search.getResources(0, 1).get(0);
});
if (fetched == myNoMatch) { if (fetched == myNoMatch) {
return null; return null;
@ -216,6 +122,119 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
return (T) fetched; return (T) fetched;
} }
private <T extends IBaseResource> IBaseResource doFetchResource(@Nullable Class<T> theClass, String theUri) {
if (theClass == null) {
Supplier<IBaseResource>[] fetchers = new Supplier[]{
() -> doFetchResource(ValueSet.class, theUri),
() -> doFetchResource(CodeSystem.class, theUri),
() -> doFetchResource(StructureDefinition.class, theUri)
};
return Arrays
.stream(fetchers)
.map(t -> t.get())
.filter(t -> t != myNoMatch)
.findFirst()
.orElse(myNoMatch);
}
IdType id = new IdType(theUri);
boolean localReference = false;
if (id.hasBaseUrl() == false && id.hasIdPart() == true) {
localReference = true;
}
String resourceName = myFhirContext.getResourceType(theClass);
IBundleProvider search;
switch (resourceName) {
case "ValueSet":
if (localReference) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(IAnyResource.SP_RES_ID, new StringParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
if (search.size() == 0) {
params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ValueSet.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
}
} else {
int versionSeparator = theUri.lastIndexOf('|');
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (versionSeparator != -1) {
params.add(ValueSet.SP_VERSION, new TokenParam(theUri.substring(versionSeparator + 1)));
params.add(ValueSet.SP_URL, new UriParam(theUri.substring(0, versionSeparator)));
} else {
params.add(ValueSet.SP_URL, new UriParam(theUri));
}
params.setSort(new SortSpec("_lastUpdated").setOrder(SortOrderEnum.DESC));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
}
break;
case "StructureDefinition": {
// Don't allow the core FHIR definitions to be overwritten
if (theUri.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String typeName = theUri.substring("http://hl7.org/fhir/StructureDefinition/".length());
if (myFhirContext.getElementDefinition(typeName) != null) {
return myNoMatch;
}
}
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(StructureDefinition.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao("StructureDefinition").search(params);
break;
}
case "Questionnaire": {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (localReference || myFhirContext.getVersion().getVersion().isEquivalentTo(FhirVersionEnum.DSTU2)) {
params.add(IAnyResource.SP_RES_ID, new StringParam(id.getIdPart()));
} else {
params.add(Questionnaire.SP_URL, new UriParam(id.getValue()));
}
search = myDaoRegistry.getResourceDao("Questionnaire").search(params);
break;
}
case "CodeSystem": {
int versionSeparator = theUri.lastIndexOf('|');
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
if (versionSeparator != -1) {
params.add(CodeSystem.SP_VERSION, new TokenParam(theUri.substring(versionSeparator + 1)));
params.add(CodeSystem.SP_URL, new UriParam(theUri.substring(0, versionSeparator)));
} else {
params.add(CodeSystem.SP_URL, new UriParam(theUri));
}
params.setSort(new SortSpec("_lastUpdated").setOrder(SortOrderEnum.DESC));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
break;
}
case "ImplementationGuide":
case "SearchParameter": {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myDaoRegistry.getResourceDao(resourceName).search(params);
break;
}
default:
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
Integer size = search.size();
if (size == null || size == 0) {
return myNoMatch;
}
if (size > 1) {
ourLog.warn("Found multiple {} instances with URL search value of: {}", resourceName, theUri);
}
return search.getResources(0, 1).get(0);
}
@Override @Override
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return myFhirContext; return myFhirContext;

View File

@ -32,13 +32,13 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.JsonParser; import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Locale; import java.util.Locale;
public class ValidatorResourceFetcher implements IResourceValidator.IValidatorResourceFetcher { public class ValidatorResourceFetcher implements IResourceValidator.IValidatorResourceFetcher {
@ -61,7 +61,6 @@ public class ValidatorResourceFetcher implements IResourceValidator.IValidatorRe
} }
@SuppressWarnings("ConstantConditions")
@Override @Override
public Element fetch(Object appContext, String theUrl) throws FHIRException { public Element fetch(Object appContext, String theUrl) throws FHIRException {
@ -94,13 +93,13 @@ public class ValidatorResourceFetcher implements IResourceValidator.IValidatorRe
} }
@Override @Override
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException { public boolean resolveURL(Object appContext, String path, String url, String type) throws FHIRException {
return true; return true;
} }
@Override @Override
public byte[] fetchRaw(String url) throws IOException { public byte[] fetchRaw(String url) {
return new byte[0]; throw new UnsupportedOperationException();
} }
@Override @Override
@ -108,4 +107,14 @@ public class ValidatorResourceFetcher implements IResourceValidator.IValidatorRe
// ignore // ignore
} }
@Override
public CanonicalResource fetchCanonicalResource(String url) {
throw new UnsupportedOperationException();
}
@Override
public boolean fetchesCanonicalResource(String url) {
return false;
}
} }

View File

@ -144,7 +144,7 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
} catch (PreconditionFailedException e) { } catch (PreconditionFailedException e) {
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()); String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
ourLog.info(ooString); ourLog.info(ooString);
assertThat(ooString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); assertThat(ooString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' has not been checked because it is unknown"));
} }
} }

View File

@ -333,7 +333,7 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome(); OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString); ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' has not been checked because it is unknown"));
} }
} }

View File

@ -1138,7 +1138,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome(); org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString); ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' has not been checked because it is unknown"));
} }
} }
@ -1174,7 +1174,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome(); org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info(outputString); ourLog.info(outputString);
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked")); assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' has not been checked because it is unknown"));
} }
} }

View File

@ -2403,7 +2403,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
ourLog.info(respString); ourLog.info(respString);
assertEquals(412, resp.getStatusLine().getStatusCode()); assertEquals(412, resp.getStatusLine().getStatusCode());
assertThat(respString, containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked")); assertThat(respString, containsString("Profile reference 'http://foo/structuredefinition/myprofile' has not been checked because it is unknown"));
} }
} }

View File

@ -13,6 +13,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -95,6 +96,19 @@ public class FhirPathFilterInterceptorTest {
} }
@Test
public void testFilteredResponse_ExpressionReturnsExtension() throws IOException {
createPatient();
HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.extension('http://hl7.org/fhir/us/core/StructureDefinition/us-core-race')&_pretty=true");
try (CloseableHttpResponse response = myHttpClient.execute(request)) {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertThat(responseText, containsString("\"url\": \"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race\""));
}
}
@Test @Test
public void testFilteredResponse_ExpressionReturnsResource() throws IOException { public void testFilteredResponse_ExpressionReturnsResource() throws IOException {
createPatient(); createPatient();
@ -159,6 +173,11 @@ public class FhirPathFilterInterceptorTest {
private void createPatient() { private void createPatient() {
Patient p = new Patient(); Patient p = new Patient();
p.addExtension()
.setUrl("http://hl7.org/fhir/us/core/StructureDefinition/us-core-race")
.addExtension()
.setUrl("ombCategory")
.setValue(new Coding("urn:oid:2.16.840.1.113883.6.238", "2106-3", "White"));
p.setActive(true); p.setActive(true);
p.addIdentifier().setSystem("http://identifiers/1").setValue("value-1"); p.addIdentifier().setSystem("http://identifiers/1").setValue("value-1");
p.addIdentifier().setSystem("http://identifiers/2").setValue("value-2"); p.addIdentifier().setSystem("http://identifiers/2").setValue("value-2");

View File

@ -287,6 +287,11 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
return myCtx.getVersion().getVersion().getFhirVersionString(); return myCtx.getVersion().getVersion().getFhirVersionString();
} }
@Override
public String getSpecUrl() {
throw new UnsupportedOperationException();
}
@Override @Override
public UcumService getUcumService() { public UcumService getUcumService() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -302,6 +307,11 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
return false; return false;
} }
@Override
public Set<String> getCodeSystemsUsed() {
throw new UnsupportedOperationException();
}
@Override @Override
public TranslationServices translator() { public TranslationServices translator() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View File

@ -30,9 +30,9 @@ public class FhirPathR5 implements IFhirPath {
result = myEngine.evaluate((Base) theInput, thePath); result = myEngine.evaluate((Base) theInput, thePath);
} catch (FHIRException e) { } catch (FHIRException e) {
throw new FhirPathExecutionException(e); throw new FhirPathExecutionException(e);
} }
for (Base next : result) { for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) { if (!theReturnType.isAssignableFrom(next.getClass())) {
throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName()); throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
} }

View File

@ -6,14 +6,13 @@ import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -64,8 +63,8 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
} }
@Override @Override
public <T extends IBaseResource> T fetchResource(Class<T> theClass, String theUri) { public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
return loadFromCache(myCache, "fetchResource " + theClass.getName() + " " + theUri, return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri,
t -> super.fetchResource(theClass, theUri)); t -> super.fetchResource(theClass, theUri));
} }

View File

@ -32,7 +32,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
private boolean noExtensibleWarnings = false; private boolean noExtensibleWarnings = false;
private boolean noBindingMsgSuppressed = false; private boolean noBindingMsgSuppressed = false;
private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext; private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext;
private boolean errorForUnknownProfiles; private boolean errorForUnknownProfiles = true;
private boolean assumeValidRestReferences; private boolean assumeValidRestReferences;
private List<String> myExtensionDomains = Collections.emptyList(); private List<String> myExtensionDomains = Collections.emptyList();
private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher; private IResourceValidator.IValidatorResourceFetcher validatorResourceFetcher;

View File

@ -61,6 +61,9 @@ public class HapiToHl7OrgDstu2ValidatingSupportWrapper extends BaseValidationSup
} }
private Class<? extends IBaseResource> translateTypeToHapi(Class<? extends IBaseResource> theCodeSystemType) { private Class<? extends IBaseResource> translateTypeToHapi(Class<? extends IBaseResource> theCodeSystemType) {
if (theCodeSystemType == null) {
return null;
}
String resName = getFhirContext().getResourceType(theCodeSystemType); String resName = getFhirContext().getResourceType(theCodeSystemType);
return myHapiCtx.getResourceDefinition(resName).getImplementingClass(); return myHapiCtx.getResourceDefinition(resName).getImplementingClass();
} }

View File

@ -17,6 +17,7 @@ import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.IResourceValidator; import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.instance.InstanceValidator; import org.hl7.fhir.validation.instance.InstanceValidator;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -103,8 +104,9 @@ class ValidatorWrapper {
public List<ValidationMessage> validate(IWorkerContext theWorkerContext, IValidationContext<?> theValidationContext) { public List<ValidationMessage> validate(IWorkerContext theWorkerContext, IValidationContext<?> theValidationContext) {
InstanceValidator v; InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new FhirInstanceValidator.NullEvaluationContext(); FHIRPathEngine.IEvaluationContext evaluationCtx = new FhirInstanceValidator.NullEvaluationContext();
XVerExtensionManager xverManager = new XVerExtensionManager(theWorkerContext);
try { try {
v = new InstanceValidator(theWorkerContext, evaluationCtx); v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager);
} catch (Exception e) { } catch (Exception e) {
throw new ConfigurationException(e); throw new ConfigurationException(e);
} }
@ -193,7 +195,12 @@ class ValidatorWrapper {
i--; i--;
} }
if (message.endsWith("' could not be resolved, so has not been checked") && next.getLevel() == ValidationMessage.IssueSeverity.WARNING) { if (
myErrorForUnknownProfiles &&
next.getLevel() == ValidationMessage.IssueSeverity.WARNING &&
message.contains("Profile reference '") &&
message.contains("' has not been checked because it is unknown")
) {
next.setLevel(ValidationMessage.IssueSeverity.ERROR); next.setLevel(ValidationMessage.IssueSeverity.ERROR);
} }

View File

@ -81,13 +81,15 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
fetchResourceName = "ValueSet"; fetchResourceName = "ValueSet";
} }
} }
Class<? extends IBaseResource> fetchResourceType = myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceDefinition(fetchResourceName).getImplementingClass();
IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchResource(fetchResourceType, key.getUri());
if (fetched == null) { Class<? extends IBaseResource> fetchResourceType;
return null; if (fetchResourceName.equals("Resource")) {
fetchResourceType = null;
} else {
fetchResourceType = myValidationSupportContext.getRootValidationSupport().getFhirContext().getResourceDefinition(fetchResourceName).getImplementingClass();
} }
IBaseResource fetched = myValidationSupportContext.getRootValidationSupport().fetchResource(fetchResourceType, key.getUri());
Resource canonical = myModelConverter.toCanonical(fetched); Resource canonical = myModelConverter.toCanonical(fetched);
@ -425,6 +427,11 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
return myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion().getFhirVersionString(); return myValidationSupportContext.getRootValidationSupport().getFhirContext().getVersion().getVersion().getFhirVersionString();
} }
@Override
public String getSpecUrl() {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean hasCache() { public boolean hasCache() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@ -440,6 +447,11 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo
return false; return false;
} }
@Override
public Set<String> getCodeSystemsUsed() {
throw new UnsupportedOperationException();
}
@Override @Override
public List<org.hl7.fhir.r5.model.StructureMap> listTransforms() { public List<org.hl7.fhir.r5.model.StructureMap> listTransforms() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();

View File

@ -664,6 +664,8 @@ public class FhirInstanceValidatorDstu3Test {
} else if (t.getMessage().contains("ValueSet as a URI SHALL start with http:// or https:// or urn:")) { } else if (t.getMessage().contains("ValueSet as a URI SHALL start with http:// or https:// or urn:")) {
// Some DSTU3 structures have missing binding information // Some DSTU3 structures have missing binding information
return false; return false;
} else if (t.getMessage().contains("The valueSet reference http://www.rfc-editor.org/bcp/bcp13.txt on element")) {
return false;
} else { } else {
return true; return true;
} }
@ -1135,7 +1137,7 @@ public class FhirInstanceValidatorDstu3Test {
myInstanceVal.setValidationSupport(myValidationSupport); myInstanceVal.setValidationSupport(myValidationSupport);
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked")); assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' has not been checked because it is unknown"));
} }
@Test @Test
@ -1314,7 +1316,7 @@ public class FhirInstanceValidatorDstu3Test {
myInstanceVal.setValidatorResourceFetcher(resourceFetcher); myInstanceVal.setValidatorResourceFetcher(resourceFetcher);
myVal.validateWithResult(input); myVal.validateWithResult(input);
verify(resourceFetcher, times(3)).resolveURL(any(), anyString(), anyString()); verify(resourceFetcher, times(3)).resolveURL(any(), anyString(), anyString(), anyString());
verify(resourceFetcher, times(4)).validationPolicy(any(), anyString(), anyString()); verify(resourceFetcher, times(4)).validationPolicy(any(), anyString(), anyString());
verify(resourceFetcher, times(4)).fetch(any(), anyString()); verify(resourceFetcher, times(4)).fetch(any(), anyString());
} }

View File

@ -254,7 +254,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
errors = myVal.validateWithResult(qa); errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors); errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString()); ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system / code1 - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)")); assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system / code1 for 'http://codesystems.com/system#code1'"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]")); assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
// Unhandled system // Unhandled system

View File

@ -1152,7 +1152,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(1, errors.size()); assertEquals(1, errors.size());
assertEquals("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked", errors.get(0).getMessage()); assertEquals("Profile reference 'http://foo/structuredefinition/myprofile' has not been checked because it is unknown", errors.get(0).getMessage());
assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity()); assertEquals(ResultSeverityEnum.ERROR, errors.get(0).getSeverity());
} }
@ -1402,7 +1402,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
myInstanceVal.setValidatorResourceFetcher(resourceFetcher); myInstanceVal.setValidatorResourceFetcher(resourceFetcher);
myVal.validateWithResult(encoded); myVal.validateWithResult(encoded);
verify(resourceFetcher, times(15)).resolveURL(any(), anyString(), anyString()); verify(resourceFetcher, times(15)).resolveURL(any(), anyString(), anyString(), anyString());
verify(resourceFetcher, times(12)).validationPolicy(any(), anyString(), anyString()); verify(resourceFetcher, times(12)).validationPolicy(any(), anyString(), anyString());
verify(resourceFetcher, times(12)).fetch(any(), anyString()); verify(resourceFetcher, times(12)).fetch(any(), anyString());
} }

View File

@ -432,8 +432,8 @@ public class FhirInstanceValidatorR5Test {
myInstanceVal.setValidatorResourceFetcher(resourceFetcher); myInstanceVal.setValidatorResourceFetcher(resourceFetcher);
myVal.validateWithResult(input); myVal.validateWithResult(input);
verify(resourceFetcher, times(13)).resolveURL(any(), anyString(), anyString()); verify(resourceFetcher, times(13)).resolveURL(any(), anyString(), anyString(), anyString());
verify(resourceFetcher, times(3)).validationPolicy(any(), anyString(), anyString()); verify(resourceFetcher, times(4)).validationPolicy(any(), anyString(), anyString());
verify(resourceFetcher, times(3)).fetch(any(), anyString()); verify(resourceFetcher, times(3)).fetch(any(), anyString());
} }
@ -794,7 +794,7 @@ public class FhirInstanceValidatorR5Test {
myInstanceVal.setValidationSupport(myValidationSupport); myInstanceVal.setValidationSupport(myValidationSupport);
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked")); assertThat(errors.toString(), containsString("Profile reference 'http://foo/structuredefinition/myprofile' has not been checked because it is unknown"));
} }
@Test @Test

View File

@ -712,7 +712,7 @@
<properties> <properties>
<fhir_core_version>5.2.0</fhir_core_version> <fhir_core_version>5.2.16</fhir_core_version>
<ucum_version>1.0.3</ucum_version> <ucum_version>1.0.3</ucum_version>
<surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args> <surefire_jvm_args>-Dfile.encoding=UTF-8 -Xmx2048m</surefire_jvm_args>