rework the way intermediary content is handled in the validator + fix impose-profile handling for IPS-AU

This commit is contained in:
Grahame Grieve 2023-09-04 13:38:22 +10:00
parent be4957c9af
commit 5cc4e3ef03
9 changed files with 72 additions and 17 deletions

View File

@ -50,7 +50,7 @@ public class FmlParser extends ParserBase {
ByteArrayInputStream stream = new ByteArrayInputStream(content);
String text = TextFile.streamToString(stream);
List<NamedElement> result = new ArrayList<>();
NamedElement ctxt = new NamedElement("fml", content);
NamedElement ctxt = new NamedElement("focus", "fml", content);
ctxt.setElement(parse(ctxt.getErrors(), text));
result.add(ctxt);
return result;

View File

@ -119,7 +119,7 @@ public class JsonParser extends ParserBase {
@Override
public List<NamedElement> parse(InputStream inStream) throws IOException, FHIRException {
byte[] content = TextFile.streamToBytes(inStream);
NamedElement ctxt = new NamedElement("json", content);
NamedElement ctxt = new NamedElement("focus", "json", content);
ByteArrayInputStream stream = new ByteArrayInputStream(content);

View File

@ -71,21 +71,24 @@ public abstract class ParserBase {
public class NamedElement {
private String name;
private String extension;
private Element element;
private byte[] content;
private List<ValidationMessage> errors = new ArrayList<>();
public NamedElement(String name, Element element, byte[] content) {
public NamedElement(String name, String extension, Element element, byte[] content) {
super();
this.name = name;
this.element = element;
this.content = content;
this.extension = extension;
}
public NamedElement(String name, byte[] content) {
public NamedElement(String name, String extension, byte[] content) {
super();
this.name = name;
this.content = content;
this.extension = extension;
}
public String getName() {
@ -106,7 +109,10 @@ public abstract class ParserBase {
public void setElement(Element element) {
this.element = element;
}
public String getFilename() {
return name+"."+extension;
}
}

View File

@ -62,7 +62,7 @@ public class SHCParser extends ParserBase {
byte[] content = TextFile.streamToBytes(inStream);
ByteArrayInputStream stream = new ByteArrayInputStream(content);
List<NamedElement> res = new ArrayList<>();
NamedElement shc = new NamedElement("shc", content);
NamedElement shc = new NamedElement("shc", "json", content);
res.add(shc);
String src = TextFile.streamToString(stream).trim();
@ -146,7 +146,7 @@ public class SHCParser extends ParserBase {
return res;
}
// ok. all checks passed, we can now validate the bundle
NamedElement bnd = new NamedElement(path, org.hl7.fhir.utilities.json.parser.JsonParser.composeBytes(cs.getJsonObject("fhirBundle")));
NamedElement bnd = new NamedElement(path, "json", org.hl7.fhir.utilities.json.parser.JsonParser.composeBytes(cs.getJsonObject("fhirBundle")));
res.add(bnd);
bnd.setElement(jsonParser.parse(bnd.getErrors(), cs.getJsonObject("fhirBundle")));
}

View File

@ -76,7 +76,7 @@ public class SHLParser extends ParserBase {
byte[] content = TextFile.streamToBytes(inStream);
List<NamedElement> res = new ArrayList<>();
NamedElement shl = addNamedElement(res, "shl", content);
NamedElement shl = addNamedElement(res, "shl", "txt", content);
String src = TextFile.bytesToString(content);
if (src.startsWith("shlink:/")) {
@ -92,8 +92,8 @@ public class SHLParser extends ParserBase {
src = null;
}
if (src != null) {
NamedElement json = addNamedElement(res, "json", TextFile.stringToBytes(src, false));
byte[] cntin = Base64.getUrlDecoder().decode(src);
NamedElement json = addNamedElement(res, "json", "json", cntin);
JsonObject j = null;
try {
j = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(cntin);
@ -146,7 +146,7 @@ public class SHLParser extends ParserBase {
private void checkManifest(List<NamedElement> res, HTTPResult cnt) throws IOException {
NamedElement manifest = addNamedElement(res, "manifest", cnt.getContent());
NamedElement manifest = addNamedElement(res, "manifest", "json", cnt.getContent());
if (!cnt.getContentType().equals("application/json")) {
logError(manifest.getErrors(), "202-08-31", 1, 1, "manifest", IssueType.STRUCTURE, "The mime type should be application/json not "+cnt.getContentType(), IssueSeverity.ERROR);
@ -243,6 +243,7 @@ public class SHLParser extends ParserBase {
}
private void processContent(List<NamedElement> res, List<ValidationMessage> errors, String path, String name, String jose, String ct) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
NamedElement bin = addNamedElement(res, "encrypted", "jose", TextFile.stringToBytes(jose, false));
byte[] cnt = null;
JWEObject jwe;
try {
@ -250,10 +251,9 @@ public class SHLParser extends ParserBase {
jwe.decrypt(new DirectDecrypter(key));
cnt = jwe.getPayload().toBytes();
} catch (Exception e) {
logError(errors, "202-08-31", 1, 1, path, IssueType.STRUCTURE, "Decruption failed: "+e.getMessage(), IssueSeverity.ERROR);
logError(bin.getErrors(), "202-08-31", 1, 1, path, IssueType.STRUCTURE, "Decruption failed: "+e.getMessage(), IssueSeverity.ERROR);
}
if (cnt != null) {
NamedElement doc = addNamedElement(res, name, cnt);
switch (ct) {
case "application/smart-health-card":
//a JSON file with a .verifiableCredential array containing SMART Health Card JWS strings, as specified by https://spec.smarthealth.cards#via-file-download.
@ -261,23 +261,26 @@ public class SHLParser extends ParserBase {
res.addAll(shc.parse(new ByteArrayInputStream(cnt)));
break;
case "application/fhir+json":
NamedElement doc = addNamedElement(res, name, "json", cnt);
// a JSON file containing any FHIR resource (e.g., an individual resource or a Bundle of resources). Generally this format may not be tamper-proof.
logError(doc.getErrors(), "202-08-31", 1, 1, name, IssueType.STRUCTURE, "Processing content of type 'application/smart-api-access' is not done yet", IssueSeverity.INFORMATION);
break;
case "application/smart-api-access":
doc = addNamedElement(res, name, "api.json", cnt);
// a JSON file with a SMART Access Token Response (see SMART App Launch). Two additional properties are defined:
// aud Required string indicating the FHIR Server Base URL where this token can be used (e.g., "https://server.example.org/fhir")
// query: Optional array of strings acting as hints to the client, indicating queries it might want to make (e.g., ["Coverage?patient=123&_tag=family-insurance"])
logError(doc.getErrors(), "202-08-31", 1, 1, name, IssueType.STRUCTURE, "Processing content of type 'application/smart-api-access' is not done yet", IssueSeverity.INFORMATION);
break;
default:
doc = addNamedElement(res, name, "bin", cnt);
logError(doc.getErrors(), "202-08-31", 1, 1, name, IssueType.STRUCTURE, "The Content-Type '"+ct+"' is not known", IssueSeverity.INFORMATION);
}
}
}
private NamedElement addNamedElement(List<NamedElement> res, String name, byte[] content) {
NamedElement result = new NamedElement(name, content);
private NamedElement addNamedElement(List<NamedElement> res, String name, String type, byte[] content) {
NamedElement result = new NamedElement(name, type, content);
res.add(result);
return result;
}

View File

@ -83,7 +83,7 @@ public class TurtleParser extends ParserBase {
@Override
public List<NamedElement> parse(InputStream inStream) throws IOException, FHIRException {
byte[] content = TextFile.streamToBytes(inStream);
NamedElement ctxt = new NamedElement("ttl", content);
NamedElement ctxt = new NamedElement("focus", "ttl", content);
ByteArrayInputStream stream = new ByteArrayInputStream(content);
Turtle src = new Turtle();

View File

@ -465,7 +465,7 @@ public class VerticalBarParser extends ParserBase {
while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size()))
readSegment(message, reader);
List<NamedElement> res = new ArrayList<>();
res.add(new NamedElement(null, message, content));
res.add(new NamedElement("focus", "hl7", message, content));
return res;
}

View File

@ -114,7 +114,7 @@ public class XmlParser extends ParserBase {
public List<NamedElement> parse(InputStream inStream) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
byte[] content = TextFile.streamToBytes(inStream);
NamedElement context = new NamedElement("xml", content);
NamedElement context = new NamedElement("focus", "xml", content);
ByteArrayInputStream stream = new ByteArrayInputStream(content);
Document doc = null;

View File

@ -176,6 +176,7 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.SIDUtilities;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.UnicodeUtilities;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.Utilities.DecimalStatus;
@ -269,6 +270,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private static final String HTML_FRAGMENT_REGEX = "[a-zA-Z]\\w*(((\\s+)(\\S)*)*)";
private static final boolean STACK_TRACE = false;
private static final boolean DEBUG_ELEMENT = false;
private static final boolean SAVE_INTERMEDIARIES = false; // set this to true to get the intermediary formats while we are waiting for a UI around this z(SHC/SHL)
private static final HashSet<String> NO_TX_SYSTEM_EXEMPT = new HashSet<>(Arrays.asList("http://loinc.org", "http://unitsofmeasure.org", "http://hl7.org/fhir/sid/icd-9-cm", "http://snomed.info/sct", "http://www.nlm.nih.gov/research/umls/rxnorm"));
private static final HashSet<String> NO_HTTPS_LIST = new HashSet<>(Arrays.asList("https://loinc.org", "https://unitsofmeasure.org", "https://snomed.info/sct", "https://www.nlm.nih.gov/research/umls/rxnorm"));
@ -741,6 +743,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
timeTracker.load(t);
if (validatedContent != null && !validatedContent.isEmpty()) {
if (SAVE_INTERMEDIARIES) {
int index = 0;
for (NamedElement ne : validatedContent) {
index++;
saveValidatedContent(ne, index);
}
}
String url = parser.getImpliedProfile();
if (url != null) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
@ -760,6 +769,19 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return (validatedContent == null || validatedContent.isEmpty()) ? null : validatedContent.get(0).getElement(); // todo: this is broken, but fixing it really complicates things elsewhere, so we do this for now
}
private void saveValidatedContent(NamedElement ne, int index) {
String tgt = null;
try {
tgt = Utilities.path("[tmp]", "validator", "content");
Utilities.createDirectory(tgt);
tgt = Utilities.path(tgt, "content-"+index+"-"+ne.getFilename());
TextFile.bytesToFile(ne.getContent(), tgt);
} catch (Exception e) {
System.out.println("Error saving internal content to '"+tgt+"': "+e.getLocalizedMessage());
}
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource) throws FHIRException {
return validate(appContext, errors, resource, new ArrayList<>());
@ -4986,6 +5008,30 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
pct.done();
}
if (defn.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
for (Extension ext : defn.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
StructureDefinition sdi = context.fetchResource(StructureDefinition.class, ext.getValue().primitiveValue());
if (sdi == null) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED, ext.getValue().primitiveValue(), defn.getVersionedUrl());
} else {
if (crumbTrails) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_DEP, sdi.getUrl(), defn.getVersionedUrl()));
}
stack.resetIds();
if (pctOwned) {
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sdi.getUrl(), logProgress);
}
ok = startInner(hostContext, errors, resource, element, sdi, stack, false, pct, mode.withSource(ProfileSource.ProfileDependency)) && ok;
if (pctOwned) {
pct.done();
}
}
}
}
Element meta = element.getNamedChild(META);
if (meta != null) {
List<Element> profiles = new ArrayList<Element>();