Add validator support for -check-references and -resolution-context

This commit is contained in:
Grahame Grieve 2025-01-14 15:51:54 +11:00
parent 99697351a0
commit 8ed34e2604
12 changed files with 393 additions and 35 deletions

View File

@ -129,6 +129,22 @@ public class ManagedFileAccess {
throw new IOException("Internal Error");
}
}
public static File file(File root, String filepath) throws IOException {
switch (accessPolicy) {
case DIRECT:
if (!inAllowedPaths(root.getAbsolutePath())) {
throw new IOException("The path '"+root.getAbsolutePath()+"' cannot be accessed by policy");
}
return new File(root.getAbsolutePath(), filepath);
case MANAGED:
return accessor.file(Utilities.path(root.getAbsolutePath(), filepath));
case PROHIBITED:
throw new IOException("Access to files is not allowed by local security policy");
default:
throw new IOException("Internal Error");
}
}
/**
* Open a FileInputStream, conforming to local security policy
**/

View File

@ -1184,4 +1184,5 @@ public class I18nConstants {
public static final String CODESYSTEM_PROPERTY_BAD_INTERNAL_REFERENCE = "CODESYSTEM_PROPERTY_BAD_INTERNAL_REFERENCE";
public static final String CODESYSTEM_PROPERTY_BAD_PROPERTY_CODE = "CODESYSTEM_PROPERTY_BAD_PROPERTY_CODE";
public static final String CODESYSTEM_DUPLICATE_CODE = "CODESYSTEM_DUPLICATE_CODE";
public static final String REFERENCE_RESOLUTION_FAILED = "REFERENCE_RESOLUTION_FAILED";
}

View File

@ -343,7 +343,8 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi
protected boolean slicingHint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) {
if (!thePass && doingHints()) {
addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical);
addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, null)
.setMessageId(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical);
}
return thePass;
}
@ -356,7 +357,8 @@ public class BaseValidator implements IValidationContextResourceLoader, IMessagi
*/
protected boolean slicingHint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text, List<ValidationMessage> sliceInfo, String id) {
if (!thePass && doingHints()) {
addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, id).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical).setSliceInfo(sliceInfo);
addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, id)
.setMessageId(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical).setSliceInfo(sliceInfo);
}
return thePass;
}

View File

@ -35,8 +35,12 @@ import org.hl7.fhir.r5.context.ILoggingService;
import org.hl7.fhir.r5.context.IWorkerContextManager;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.context.SystemOutLoggingService;
import org.hl7.fhir.r5.elementmodel.*;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.elementmodel.ParserBase;
import org.hl7.fhir.r5.elementmodel.SHCParser;
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.formats.FormatUtilities;
@ -61,9 +65,9 @@ import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.renderers.RendererFactory;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
@ -79,6 +83,7 @@ import org.hl7.fhir.r5.utils.validation.constants.CheckDisplayOption;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.IdStatus;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.ByteProvider;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.SIDUtilities;
@ -107,7 +112,6 @@ import org.hl7.fhir.validation.cli.utils.ValidationLevel;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
import org.hl7.fhir.utilities.ByteProvider;
import org.xml.sax.SAXException;
import lombok.Getter;

View File

@ -59,6 +59,15 @@ public class CliContext {
@SerializedName("assumeValidRestReferences")
private
boolean assumeValidRestReferences = false;
@JsonProperty("checkReferences")
@SerializedName("checkReferences")
private
boolean checkReferences = false;
@JsonProperty("resolutionContext")
@SerializedName("resolutionContext")
private
String resolutionContext = null;
@JsonProperty("canDoNative")
@SerializedName("canDoNative")
private
@ -381,6 +390,18 @@ public class CliContext {
return this;
}
@SerializedName("resolutionContext")
@JsonProperty("resolutionContext")
public String getResolutionContext() {
return resolutionContext;
}
@SerializedName("resolutionContext")
@JsonProperty("resolutionContext")
public CliContext setResolutionContext(String resolutionContext) {
this.resolutionContext = resolutionContext;
return this;
}
@SerializedName("langTransform")
@JsonProperty("langTransform")
@ -967,6 +988,19 @@ public class CliContext {
return this;
}
@SerializedName("checkReferences")
@JsonProperty("checkReferences")
public boolean isCheckReferences() {
return checkReferences;
}
@SerializedName("checkReferences")
@JsonProperty("checkReferences")
public CliContext setCheckReferences(boolean checkReferences) {
this.checkReferences = checkReferences;
return this;
}
@SerializedName("noInternalCaching")
@JsonProperty("noInternalCaching")
public boolean isNoInternalCaching() {
@ -1150,6 +1184,7 @@ public class CliContext {
recursive == that.recursive &&
doDebug == that.doDebug &&
assumeValidRestReferences == that.assumeValidRestReferences &&
checkReferences == that.checkReferences &&
canDoNative == that.canDoNative &&
noInternalCaching == that.noInternalCaching &&
noExtensibleBindingMessages == that.noExtensibleBindingMessages &&
@ -1161,6 +1196,7 @@ public class CliContext {
checkIPSCodes == that.checkIPSCodes &&
Objects.equals(extensions, that.extensions) &&
Objects.equals(map, that.map) &&
Objects.equals(resolutionContext, that.resolutionContext) &&
Objects.equals(htmlInMarkdownCheck, that.htmlInMarkdownCheck) &&
Objects.equals(output, that.output) &&
Objects.equals(outputSuffix, that.outputSuffix) &&
@ -1206,7 +1242,7 @@ public class CliContext {
@Override
public int hashCode() {
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, checkReferences,canDoNative, noInternalCaching, resolutionContext,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, packageName, igs, questionnaireMode, level, profiles, options, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, advisorFile, expansionParameters, format, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
@ -1222,6 +1258,7 @@ public class CliContext {
", recursive=" + recursive +
", doDebug=" + doDebug +
", assumeValidRestReferences=" + assumeValidRestReferences +
", checkReferences=" + checkReferences +
", canDoNative=" + canDoNative +
", noInternalCaching=" + noInternalCaching +
", noExtensibleBindingMessages=" + noExtensibleBindingMessages +
@ -1238,6 +1275,7 @@ public class CliContext {
", txLog='" + txLog + '\'' +
", txCache='" + txCache + '\'' +
", mapLog='" + mapLog + '\'' +
", resolutionContext='" + resolutionContext + '\'' +
", lang='" + lang + '\'' +
", srcLang='" + srcLang + '\'' +
", tgtLang='" + tgtLang + '\'' +

View File

@ -1,9 +1,14 @@
package org.hl7.fhir.validation.cli.services;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@ -12,12 +17,18 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContextManager;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.Resource;
@ -28,17 +39,16 @@ import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.AdditionalBindingPurpose;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.CodedContentValidationAction;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ResourceValidationAction;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.http.HTTPResult;
import org.hl7.fhir.utilities.http.ManagedWebAccess;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
@ -47,8 +57,6 @@ import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.cli.utils.Common;
import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation;
import javax.annotation.Nonnull;
public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator {
@ -60,6 +68,9 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
private Map<String, String> pidList = new HashMap<>();
private Map<String, NpmPackage> pidMap = new HashMap<>();
private IValidationPolicyAdvisor policyAdvisor;
private String resolutionContext;
private Map<String, String> knownFiles = new HashMap<>();
public StandAloneValidatorFetcher(FilesystemPackageCacheManager pcm, IWorkerContext context, IPackageInstaller installer) {
this.pcm = pcm;
@ -69,8 +80,98 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
}
@Override
public Element fetch(IResourceValidator validator, Object appContext, String url) throws FHIRException {
throw new FHIRException("The URL '" + url + "' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
public Element fetch(IResourceValidator validator, Object appContext, String url) throws FHIRException, IOException {
if (!Utilities.isAbsoluteUrl(url) && Utilities.startsWithInList(resolutionContext, "http:", "https:")) {
url = Utilities.pathURL(resolutionContext, url);
}
if (Utilities.isAbsoluteUrl(url)) {
HTTPResult cnt = null;
try {
cnt = ManagedWebAccess.get(Arrays.asList("web"), url, "application/json");
cnt.checkThrowException();
} catch (Exception e) {
cnt = ManagedWebAccess.get(Arrays.asList("web"), url, "application/fhir+xml");
cnt.checkThrowException();
}
if (cnt.getContentType() != null && cnt.getContentType().contains("xml")) {
return Manager.parse(context, new ByteArrayInputStream(cnt.getContent()), FhirFormat.XML).get(0).getElement();
} else {
return Manager.parse(context, new ByteArrayInputStream(cnt.getContent()), FhirFormat.JSON).get(0).getElement();
}
} else if (resolutionContext == null) {
throw new FHIRException("The URL '" + url + "' is not known to the FHIR validator, and a resolution context has not been provided as part of the setup / parameters");
} else if (resolutionContext.startsWith("file:")) {
File rc = ManagedFileAccess.file(resolutionContext.substring(5));
if (!rc.exists()) {
throw new FHIRException("The URL '" + url + "' is not known to the FHIR validator, and a resolution context has not been provided as part of the setup / parameters");
}
// first we look for the file by several different patterns
File tgt = ManagedFileAccess.file(rc, url);
if (tgt.exists()) {
return see(tgt, loadFile(tgt));
}
tgt = ManagedFileAccess.file(rc, url+".json");
if (tgt.exists()) {
return see(tgt, loadFile(tgt));
}
tgt = ManagedFileAccess.file(rc, url+".xml");
if (tgt.exists()) {
return see(tgt, loadFile(tgt));
}
String[] p = url.split("\\/");
if (p.length != 2) {
throw new FHIRException("The URL '" + url + "' was not understood - expecting type/id");
}
if (knownFiles.containsKey(p[0]+"/"+p[1])) {
tgt = ManagedFileAccess.file(knownFiles.get(p[0]+"/"+p[1]));
return loadFile(tgt);
}
tgt = ManagedFileAccess.file(rc, p[0]+"-"+p[1]+".json");
if (tgt.exists()) {
return see(tgt, loadFile(tgt));
}
tgt = ManagedFileAccess.file(rc, p[0]+"-"+p[1]+".xml");
if (tgt.exists()) {
return see(tgt, loadFile(tgt));
}
// didn't find it? now scan...
for (File f : ManagedFileAccess.listFiles(rc)) {
if (isPossibleMatch(f, p[0], p[1])) {
Element e = see(f, loadFile(f));
if (p[0].equals(e.fhirType()) && p[1].equals(e.getIdBase())) {
return e;
}
}
}
return null;
} else {
throw new FHIRException("The resolution context '"+resolutionContext+"' was not understood");
}
}
private Element see(File f, Element e) {
knownFiles.put(e.fhirType()+"/"+e.getIdBase(), f.getAbsolutePath());
return e;
}
private boolean isPossibleMatch(File f, String rt, String id) throws FileNotFoundException, IOException {
String src = TextFile.fileToString(f);
if (f.getName().endsWith(".xml")) {
return src.contains("<"+rt) && src.contains("\""+id+"\"");
} else {
return src.contains("\""+rt+"\"") && src.contains("\""+id+"\"");
}
}
private Element loadFile(File tgt) throws FHIRFormatError, DefinitionException, FHIRException, FileNotFoundException, IOException {
if (tgt.getName().endsWith(".xml")) {
return Manager.parse(context, new FileInputStream(tgt), FhirFormat.XML).get(0).getElement();
} else {
return Manager.parse(context, new FileInputStream(tgt), FhirFormat.JSON).get(0).getElement();
}
}
@Override
@ -78,7 +179,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
Object appContext,
String path,
String url) {
return ReferenceValidationPolicy.IGNORE;
return policyAdvisor.policyForReference(validator, appContext, path, url);
}
@Override
@ -344,6 +445,14 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
return policyAdvisor.getReferencePolicy();
}
public void setReferencePolicy(ReferenceValidationPolicy policy) {
if (policyAdvisor instanceof BasePolicyAdvisorForFullValidation) {
((BasePolicyAdvisorForFullValidation) policyAdvisor).setRefpol(policy);
} else {
throw new Error("Cannot set reference policy on a "+policy.getClass().getName());
}
}
public IValidationPolicyAdvisor getPolicyAdvisor() {
return policyAdvisor;
}
@ -353,4 +462,12 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
return this;
}
public String getResolutionContext() {
return resolutionContext;
}
public void setResolutionContext(String resolutionContext) {
this.resolutionContext = resolutionContext;
}
}

View File

@ -636,6 +636,12 @@ public class ValidationService {
validationEngine.setFetcher(fetcher);
validationEngine.getContext().setLocator(fetcher);
validationEngine.setPolicyAdvisor(fetcher);
if (cliContext.isCheckReferences()) {
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
} else {
fetcher.setReferencePolicy(ReferenceValidationPolicy.IGNORE);
}
fetcher.setResolutionContext(cliContext.getResolutionContext());
} else {
DisabledValidationPolicyAdvisor fetcher = new DisabledValidationPolicyAdvisor();
validationEngine.setPolicyAdvisor(fetcher);

View File

@ -38,6 +38,8 @@ public class Params {
public static final String QUESTIONNAIRE = "-questionnaire";
public static final String NATIVE = "-native";
public static final String ASSUME_VALID_REST_REF = "-assumeValidRestReferences";
public static final String CHECK_REFERENCES = "-check-references";
public static final String RESOLUTION_CONTEXT = "-resolution-context";
public static final String DEBUG = "-debug";
public static final String SCT = "-sct";
public static final String RECURSE = "-recurse";
@ -273,6 +275,10 @@ public class Params {
cliContext.setDoNative(true);
} else if (args[i].equals(ASSUME_VALID_REST_REF)) {
cliContext.setAssumeValidRestReferences(true);
} else if (args[i].equals(CHECK_REFERENCES)) {
cliContext.setCheckReferences(true);
} else if (args[i].equals(RESOLUTION_CONTEXT)) {
cliContext.setResolutionContext(args[++i]);
} else if (args[i].equals(DEBUG)) {
cliContext.setDoDebug(true);
} else if (args[i].equals(SCT)) {

View File

@ -2023,21 +2023,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private Set<String> getUnknownSystems(ValidationResult vr) {
if (vr == null) {
return null;
}
if (vr.getUnknownSystems() != null && !vr.getUnknownSystems().isEmpty()) {
return vr.getUnknownSystems();
}
if (vr.getSystem() != null) {
Set<String> set = new HashSet<String>();
set.add(vr.getSystem());
return set;
}
return null;
}
private boolean convertCDACodeToCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition logical, CodeableConcept cc) {
boolean ok = true;
cc.setText(element.getNamedChildValue("originalText", false));
@ -2525,7 +2510,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean ok = false;
CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder();
List<String> plist = new ArrayList<>();
plist.add(stripIndexes(stack.getLiteralPath()));
plist.add(stripIndexes(stripRefs(stack.getLiteralPath())));
for (String s : stack.getLogicalPaths()) {
String p = stripIndexes(s);
// all extensions are always allowed in ElementDefinition.example.value, and in fixed and pattern values. TODO: determine the logical paths from the path stated in the element definition....
@ -2683,6 +2668,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
private String stripRefs(String literalPath) {
if (literalPath.contains(".resolve().ofType(")) {
String s = literalPath.substring(literalPath.lastIndexOf(".resolve().")+18);
int i = s.indexOf(")");
s = s.substring(0, i)+s.substring(i+1);
return s;
} else {
return literalPath;
}
}
private boolean containsAny(Set<String> set, List<String> list) {
for (String p : list) {
if (set.contains(p)) {
@ -4182,9 +4178,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
try {
ext = fetcher.fetch(this, valContext.getAppContext(), ref);
} catch (IOException e) {
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
throw new FHIRException(e);
ext = null;
// it's probably an error, but here we're just giving the user information about why resolution failed
hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), path,
false, I18nConstants.REFERENCE_RESOLUTION_FAILED, ref, e.getClass().getName(), e.getMessage());
}
if (ext != null) {
setParents(ext);

View File

@ -39,6 +39,14 @@ public class BasePolicyAdvisorForFullValidation implements IValidationPolicyAdvi
this.refpol = refpol;
}
public ReferenceValidationPolicy getRefpol() {
return refpol;
}
public void setRefpol(ReferenceValidationPolicy refpol) {
this.refpol = refpol;
}
@Override
public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path, String url) {
return refpol;

View File

@ -64,7 +64,12 @@ public class NodeStack {
this.context = context;
ids = new HashMap<>();
this.element = element;
literalPath = refPath + "->" + element.getName();
int i = element.getName().indexOf(".");
if (i == -1) {
literalPath = refPath+".resolve().ofType(" + element.getName()+")";
} else {
literalPath = refPath+".resolve().ofType(" + element.getName().substring(0, i)+")"+element.getName().substring(i);
}
workingLang = validationLanguage;
}

View File

@ -3,21 +3,30 @@ package org.hl7.fhir.validation.tests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.OperationOutcomeUtilities;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.utilities.tests.CacheVerificationLogger;
import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
@ -285,4 +294,149 @@ public class ValidationEngineTests {
System.out.println("Finished");
}
@Test
public void testResolveRelativeFileValid() throws Exception {
String folder = setupFolder();
try {
ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(ve.getPcm(), ve.getContext(), ve);
ve.setFetcher(fetcher);
ve.getContext().setLocator(fetcher);
ve.setPolicyAdvisor(fetcher);
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
fetcher.setResolutionContext("file:"+folder);
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Observation.json")));
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Patient.json")));
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "resolution", "relative-url-valid.json"), null);
Assertions.assertTrue(checkOutcomes("testResolveRelativeFileValid", op,
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
} finally {
Utilities.clearDirectory(folder);
ManagedFileAccess.file(folder).delete();
}
}
@Test
public void testResolveRelativeFileInvalid() throws Exception {
String folder = setupFolder();
try {
ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(ve.getPcm(), ve.getContext(), ve);
ve.setFetcher(fetcher);
ve.getContext().setLocator(fetcher);
ve.setPolicyAdvisor(fetcher);
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
fetcher.setResolutionContext("file:"+folder);
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Observation.json")));
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Patient.json")));
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "resolution", "relative-url-invalid.json"), null);
Assertions.assertTrue(checkOutcomes("testResolveRelativeFileValid", op,
"Observation.subject null error/structure: Unable to find a profile match for Patient/example-newborn among choices: http://hl7.org/fhir/test/StructureDefinition/PatientRule\n"+
"Observation.subject null information/structure: Details for Patient/example-newborn matching against profile http://hl7.org/fhir/test/StructureDefinition/PatientRule|0.1.0\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
} finally {
Utilities.clearDirectory(folder);
ManagedFileAccess.file(folder).delete();
}
}
@Test
public void testResolveRelativeFileError() throws Exception {
String folder = setupFolder();
try {
ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(ve.getPcm(), ve.getContext(), ve);
ve.setFetcher(fetcher);
ve.getContext().setLocator(fetcher);
ve.setPolicyAdvisor(fetcher);
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
fetcher.setResolutionContext("file:"+folder);
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Observation.json")));
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Patient.json")));
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "resolution", "relative-url-error.json"), null);
Assertions.assertTrue(checkOutcomes("testResolveRelativeFileValid", op,
"Observation.subject null error/structure: Unable to resolve resource with reference 'patient/example-newborn-x'\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
} finally {
Utilities.clearDirectory(folder);
ManagedFileAccess.file(folder).delete();
}
}
@Test
public void testResolveAbsoluteValid() throws Exception {
ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(ve.getPcm(), ve.getContext(), ve);
ve.setFetcher(fetcher);
ve.getContext().setLocator(fetcher);
ve.setPolicyAdvisor(fetcher);
ve.setShowMessagesFromReferences(true);
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Observation.json")));
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Patient.json")));
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "resolution", "absolute-url-valid.json"), null);
Assertions.assertTrue(checkOutcomes("testResolveRelativeFileValid", op,
"Observation.subject.resolve().ofType(Patient).managingOrganization null error/structure: Unable to resolve resource with reference 'Organization/1'\n"+
"Observation.subject.resolve().ofType(Patient).managingOrganization null information/informational: Fetching 'Organization/1' failed. System details: org.hl7.fhir.exceptions.FHIRException: The URL 'Organization/1' is not known to the FHIR validator, and a resolution context has not been provided as part of the setup / parameters\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
}
@Test
public void testResolveAbsoluteInvalid() throws Exception {
ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(ve.getPcm(), ve.getContext(), ve);
ve.setFetcher(fetcher);
ve.getContext().setLocator(fetcher);
ve.setPolicyAdvisor(fetcher);
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Observation.json")));
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Patient.json")));
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "resolution", "absolute-url-invalid.json"), null);
Assertions.assertTrue(checkOutcomes("testResolveRelativeFileValid", op,
"Observation.subject null error/structure: Unable to find a profile match for https://hl7.org/fhir/R4/patient-example-newborn.json among choices: http://hl7.org/fhir/test/StructureDefinition/PatientRule\n"+
"Observation.subject null information/structure: Details for https://hl7.org/fhir/R4/patient-example-newborn.json matching against profile http://hl7.org/fhir/test/StructureDefinition/PatientRule|0.1.0\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
}
@Test
public void testResolveAbsoluteError() throws Exception {
ValidationEngine ve = TestUtilities.getValidationEngine("hl7.fhir.r4.core#4.0.1", DEF_TX, FhirPublication.R4, "4.0.1");
StandAloneValidatorFetcher fetcher = new StandAloneValidatorFetcher(ve.getPcm(), ve.getContext(), ve);
ve.setFetcher(fetcher);
ve.getContext().setLocator(fetcher);
ve.setPolicyAdvisor(fetcher);
fetcher.setReferencePolicy(ReferenceValidationPolicy.CHECK_VALID);
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Observation.json")));
ve.seeResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("validator", "resolution", "StructureDefinition-Patient.json")));
OperationOutcome op = ve.validate(FhirFormat.JSON, TestingUtilities.loadTestResourceStream("validator", "resolution", "absolute-url-error.json"), null);
Assertions.assertTrue(checkOutcomes("testResolveRelativeFileValid", op,
"Observation.subject null error/structure: Unable to resolve resource with reference 'http://hl7x.org/fhir/R4/Patient/Patient/example-newborn'\n"+
"Observation.subject null information/informational: Fetching 'http://hl7x.org/fhir/R4/Patient/Patient/example-newborn' failed. System details: java.net.UnknownHostException: hl7x.org\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have a performer\n"+
"Observation null warning/invalid: Best Practice Recommendation: In general, all observations should have an effective[x] ()\n"+
"Observation null warning/invariant: Constraint failed: dom-6: 'A resource should have narrative for robust management' (defined in http://hl7.org/fhir/StructureDefinition/DomainResource) (Best Practice Recommendation)"));
}
private String setupFolder() throws IOException {
String now = new SimpleDateFormat("yyyymmddhhMMss").format(new Date());
String folder = Utilities.path("[tmp]", "validator-resolution", now);
Utilities.createDirectory(folder);
for (String s : Utilities.strings("Organization-first.xml", "Patient-example-newborn.json", "patient-example.json")) {
TextFile.bytesToFile( TestingUtilities.loadTestResourceBytes("validator", "resolution", s), Utilities.path(folder, s));
}
return folder;
}
}