Merge remote-tracking branch 'origin/master' into do-20240122-base-engine

This commit is contained in:
dotasek 2024-06-05 15:29:06 -04:00
commit c2d0207733
30 changed files with 748 additions and 249 deletions

View File

@ -0,0 +1,56 @@
package org.hl7.fhir.convertors.misc;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.DirectoryVisitor;
import org.hl7.fhir.utilities.filesystem.DirectoryVisitor.IDirectoryVisitorImplementation;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.npm.NpmPackage;
public class SpecMapUnpacker {
public static void main(String[] args) throws IOException {
new SpecMapUnpacker().unpack(args[0]);
}
public class SpecMapScanner implements IDirectoryVisitorImplementation {
@Override
public boolean enterDirectory(File directory) throws IOException {
return true;
}
@Override
public boolean visitFile(File file) throws IOException {
System.out.println("Look at "+file.getAbsolutePath());
try {
NpmPackage npm = NpmPackage.fromPackage(ManagedFileAccess.inStream(file));
if (npm.hasFile("other", "spec.internals")) {
byte[] cnt = TextFile.streamToBytes(npm.load("other", "spec.internals"));
TextFile.bytesToFile(cnt, Utilities.path(Utilities.getDirectoryForFile(file.getAbsolutePath()), "page-map.json"));
System.out.println(" ...extracted");
return true;
} else {
System.out.println(" ...not found");
return false;
}
} catch (Exception e) {
System.out.println(" ...error: "+e.getMessage());
return false;
}
}
}
private void unpack(String path) throws IOException {
System.out.println("Scanning "+path);
int count = DirectoryVisitor.visitDirectory(new SpecMapScanner(), path, "tgz");
System.out.println("Done. "+count+" files extracted");
}
}

View File

@ -700,9 +700,11 @@ public class ProfileUtilities {
throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, base.getBaseDefinition(), base.getUrl()));
checkNotGenerating(sdb, "an extension base");
generateSnapshot(sdb, base, base.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : webUrl, base.getName());
}
fixTypeOfResourceId(base);
if (base.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
checkTypeParameters(base, derived);
}
if (snapshotStack.contains(derived.getUrl())) {
throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
@ -976,6 +978,29 @@ public class ProfileUtilities {
}
private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) {
String bt = ToolingExtensions.readStringExtension(base, ToolingExtensions.EXT_TYPE_PARAMETER);
if (!derived.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl()));
}
String dt = ToolingExtensions.readStringExtension(derived, ToolingExtensions.EXT_TYPE_PARAMETER);
StructureDefinition bsd = context.fetchTypeDefinition(bt);
StructureDefinition dsd = context.fetchTypeDefinition(dt);
if (bsd == null) {
throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt));
}
if (dsd == null) {
throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt));
}
StructureDefinition t = dsd;
while (t != bsd && t != null) {
t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
}
if (t == null) {
throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt));
}
}
private XVerExtensionManager makeXVer() {
if (xver == null) {
xver = new XVerExtensionManager(context);

View File

@ -1483,7 +1483,7 @@ public class DataRenderer extends Renderer implements CodeResolver {
x.addText(displayContactPoint(contact));
break;
case PHONE:
if (contact.hasValue() && contact.getValue().startsWith("+")) {
if (contact.hasValue() && contact.getValue() != null && contact.getValue().startsWith("+")) {
x.ah("tel:"+contact.getValue().replace(" ", "")).tx(contact.getValue());
} else {
x.addText(displayContactPoint(contact));

View File

@ -105,7 +105,7 @@ public class ExampleScenarioRenderer extends TerminologyRenderer {
for (ExampleScenarioProcessStepComponent step: process.getStep()) {
plantUml += toPlantUml(step, stepPrefix(prefix, step, stepCount), scen, actorsActive, actorKeys);
if (step.getPause())
plantUml += context.formatPhrase(RenderingContext.EX_SCEN_TIME);
plantUml += context.formatPhrase(RenderingContext.EX_SCEN_TIME)+"\n";
stepCount++;
}

View File

@ -494,7 +494,7 @@ public abstract class ResourceRenderer extends DataRenderer {
if (url.startsWith("#") && res != null) {
for (ResourceWrapper r : res.getContained()) {
if (r.getId().equals(url.substring(1)))
return new ResourceWithReference(ResourceReferenceKind.CONTAINED, null, r);
return new ResourceWithReference(ResourceReferenceKind.CONTAINED, url, r);
}
return null;
}

View File

@ -79,7 +79,7 @@ import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.r5.utils.PublicationHacker;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.StandardsStatus;
import org.hl7.fhir.utilities.TextFile;
@ -1060,6 +1060,19 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
Cell left = gen.new Cell(null, ref, sName, hint, null);
row.getCells().add(left);
if (profile.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
Extension etp = profile.getExtensionByUrl(ToolingExtensions.EXT_TYPE_PARAMETER);
String name = etp.getExtensionString("name");
String type = etp.getExtensionString("type");
StructureDefinition t = context.getContext().fetchTypeDefinition(type);
if (t == null) {
left.addPiece(gen.new Piece(null, "<"+name+" : "+type+">", null));
} else if (t.getWebPath() == null) {
left.addPiece(gen.new Piece(type, "<"+name+" : "+t.present()+">", null));
} else {
left.addPiece(gen.new Piece(t.getWebPath(), "<"+name+" : "+t.present()+">", null));
}
}
return left;
}
@ -1964,6 +1977,28 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
} else {
c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
}
if (t.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
c.addPiece(checkForNoChange(t, gen.new Piece(null, "<", null)));
boolean pfirst = true;
List<Extension> exl = t.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER);
for (Extension ex : exl) {
if (pfirst) { pfirst = false; } else { c.addPiece(checkForNoChange(t, gen.new Piece(null, ";", null))); }
if (exl.size() > 1) {
c.addPiece(checkForNoChange(t, gen.new Piece(null, ex.getExtensionString("name")+": ", null)));
}
String type = ex.getExtensionString("type");
StructureDefinition psd = context.getContext().fetchTypeDefinition(type);
if (psd == null) {
c.addPiece(checkForNoChange(t, gen.new Piece(null, type, null)));
} else if (psd.getWebPath() == null) {
c.addPiece(checkForNoChange(t, gen.new Piece(type, psd.present(), null)));
} else {
c.addPiece(checkForNoChange(t, gen.new Piece(psd.getWebPath(), psd.present(), null)));
}
}
c.addPiece(checkForNoChange(t, gen.new Piece(null, ">", null)));
}
if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
c.addPiece(gen.new Piece(null, " ", null));
c.addStyledText((context.formatPhrase(RenderingContext.STRUC_DEF_TYPE_SUPP)), "S", "white", "red", null, false);
@ -3670,6 +3705,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
} else {
tableRow(tbl, context.formatPhrase(RenderingContext.GENERAL_TYPE), "datatypes.html", strikethrough, describeTypes(d.getType(), false, d, compare, mode, value, compareValue, sd));
}
if (root && sd.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
tableRow(tbl, context.formatPhrase(RenderingContext.STRUC_DEF_TYPE_PARAMETER), "http://hl7.org/fhir/tools/StructureDefinition-type-parameter.html", strikethrough, renderTypeParameter(sd.getExtensionByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)));
}
if (d.hasExtension(ToolingExtensions.EXT_DEF_TYPE)) {
tableRow(tbl, context.formatPhrase(RenderingContext.STRUC_DEF_DEFAULT_TYPE), "datatypes.html", strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DEF_TYPE));
}
@ -3830,6 +3868,20 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
tbl.tx("\r\n");
}
private XhtmlNode renderTypeParameter(Extension ext) {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
x.tx(ext.getExtensionString("name"));
x.tx(" : ");
String t = ext.getExtensionString("type");
StructureDefinition sd = context.getContext().fetchTypeDefinition(t);
if (sd == null) {
x.code().tx(t);
} else {
x.ah(sd.getWebPath(), t).tx(sd.present());
}
return x;
}
private XhtmlNode presentModifier(ElementDefinition d, int mode, ElementDefinition compare) throws FHIRException, IOException {
XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement(), null), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement(), null), null, mode, false, false);
if (x1 != null) {
@ -4318,7 +4370,28 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
} else {
ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode, false, false);
}
if (t.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
x.tx("<");
boolean first = true;
List<Extension> exl = t.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER);
for (Extension ex : exl) {
if (first) { first = false; } else { x.tx("; "); }
if (exl.size() > 1) {
x.tx(ex.getExtensionString("name"));
x.tx(":");
}
String type = ex.getExtensionString("type");
StructureDefinition psd = context.getContext().fetchTypeDefinition(type);
if (psd == null) {
x.code().tx(type);
} else if (psd.getWebPath() == null) {
x.ah(type).tx(type);
} else {
x.ah(psd.getWebPath()).tx(type);
}
}
x.tx(">");
}
if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) {
StatusList<ResolvedCanonical> profiles = analyseProfiles(t.getProfile(), compare == null ? null : compare.getProfile(), mustSupportOnly, mode);
if (profiles.size() > 0) {
@ -4647,6 +4720,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
break;
}
}
if (Utilities.noString(newMap) && compare == null) {
return null;
}
if (compare==null)
return new XhtmlNode(NodeType.Element, "div").tx(newMap);
String oldMap = null;
@ -4656,7 +4732,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
break;
}
}
if (Utilities.noString(newMap) && Utilities.noString(oldMap)) {
return null;
}
return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode, false, false);
}

View File

@ -162,7 +162,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
T output = (T) client.issueGetResourceRequest(resourceUri,
withVer(preferredResourceFormat.getHeader(), "5.0"),
generateHeaders(),
generateHeaders(false),
message,
timeoutNormal).getReference();
if (attemptedResourceFormat != preferredResourceFormat) {
@ -214,7 +214,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ResourceRequest<Resource> result = null;
try {
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass + "/" + id,
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
@ -233,7 +233,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(false),
"Read " + resourceClass.getName() + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
@ -251,7 +251,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndIdAndVersion(resourceClass, id, version),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(false),
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
@ -269,7 +269,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
result = client.issueGetResourceRequest(resourceAddress.resolveGetUriFromResourceClassAndCanonical(resourceClass, canonicalURL),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(false),
"Read " + resourceClass.getName() + "?url=" + canonicalURL,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
@ -293,7 +293,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resource.getClass(), resource.getId()),
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"Update " + resource.fhirType() + "/" + resource.getId(),
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
@ -321,7 +321,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePutRequest(resourceAddress.resolveGetUriFromResourceClassAndId(resourceClass, id),
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"Update " + resource.fhirType() + "/" + id,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
@ -357,10 +357,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
URI url = resourceAddress.resolveOperationURLFromClass(resourceClass, name, ps);
if (complex) {
byte[] body = ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true);
result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
result = client.issuePostRequest(url, body, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
"POST " + resourceClass.getName() + "/$" + name, timeoutLong);
} else {
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
@ -383,7 +383,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
Bundle transactionResult = null;
try {
transactionResult = client.postBatchRequest(resourceAddress.getBaseServiceUri(), ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat()), false), withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size()));
} catch (Exception e) {
handleException("An error occurred trying to process this transaction request", e);
@ -398,7 +398,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
result = client.issuePostRequest(resourceAddress.resolveValidateUri(resourceClass, id),
ByteUtils.resourceToByteArray(resource, false, isJson(getPreferredResourceFormat()), false),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
@ -458,7 +458,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ValueSet.class, "expand"),
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"ValueSet/$expand?url=" + source.getUrl(),
timeoutExpand);
} catch (IOException e) {
@ -476,7 +476,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
result = client.issueGetResourceRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup", params),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(false),
"CodeSystem/$lookup",
timeoutNormal);
} catch (IOException e) {
@ -495,7 +495,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(CodeSystem.class, "lookup"),
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"CodeSystem/$lookup",
timeoutNormal);
} catch (IOException e) {
@ -514,7 +514,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(ConceptMap.class, "translate"),
ByteUtils.resourceToByteArray(p, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"ConceptMap/$translate",
timeoutNormal);
} catch (IOException e) {
@ -539,7 +539,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"Closure?name=" + name,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
@ -561,7 +561,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issuePostRequest(resourceAddress.resolveOperationUri(null, "closure", new HashMap<String, String>()),
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"),
generateHeaders(),
generateHeaders(true),
"UpdateClosure?name=" + name,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
@ -617,7 +617,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
this.headers = headers;
}
private Headers generateHeaders() {
private Headers generateHeaders(boolean hasBody) {
Headers.Builder builder = new Headers.Builder();
// Add basic auth header if it exists
if (basicAuthHeaderExists()) {
@ -635,7 +635,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
builder.add("Accept-Language: "+acceptLang);
}
if (!Utilities.noString(contentLang)) {
if (hasBody && !Utilities.noString(contentLang)) {
builder.add("Content-Language: "+contentLang);
}
@ -689,7 +689,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
org.hl7.fhir.r5.utils.client.network.ResourceRequest<Resource> result = null;
try {
result = client.issueGetResourceRequest(resourceAddress.resolveGetResource(resourceClass, id),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), resourceClass.getName()+"/"+id, timeoutNormal);
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), resourceClass.getName()+"/"+id, timeoutNormal);
} catch (IOException e) {
throw new FHIRException(e);
}

View File

@ -13,6 +13,7 @@ import org.hl7.fhir.r5.utils.ResourceUtilities;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import org.hl7.fhir.r5.utils.client.ResourceFormat;
import org.hl7.fhir.utilities.MimeType;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.settings.FhirSettings;
import javax.annotation.Nonnull;
@ -91,7 +92,9 @@ public class FhirRequestBuilder {
*/
protected static void addResourceFormatHeaders(Request.Builder request, String format) {
request.addHeader("Accept", format);
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
if (Utilities.existsInList(request.getMethod$okhttp(), "POST", "PUT")) {
request.addHeader("Content-Type", format + ";charset=" + DEFAULT_CHARSET);
}
}
/**

View File

@ -0,0 +1,11 @@
package org.hl7.fhir.r5.utils.validation;
import java.util.List;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public interface IMessagingServices {
ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments);
}

View File

@ -31,6 +31,7 @@ package org.hl7.fhir.r5.utils.validation;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -53,6 +54,8 @@ import java.util.List;
*
*/
public interface IResourceValidator {
IWorkerContext getContext();
/**
* how much to check displays for coded elements

View File

@ -9,6 +9,7 @@ import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor.ElementValidationAction;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
@ -131,5 +132,42 @@ public interface IValidationPolicyAdvisor {
ValueSet valueSet,
List<String> systems);
/**
* This is called after a resource has been validated against the base structure,
* but before it's validated against any profiles specified in .meta.profile or in the parameters.
* This can be used to determine what additional profiles should be applied, for instance
* those derived from the http://hl7.org/fhir/tools/StructureDefinition/profile-mapping extension
*
* Note that the resource is an elementModel resource, not an IBaseResource. This is less convenient to
* read values from, but is the way the internals of the validator works (e.g. the version of the resource
* might be any version from R2-R6)
*
* The base implementation applies the mandatory vital signs to observations that have LOINC or SNOMED CT
* codes that indicate that they are vital signs. Note that these profiles are not optional; all vital sign resources
* are required to conform to them. For this reason, if you're providing your own policy advisor, you should
* keep a reference to the default one, or call BasePolicyAdvisorForFullValidation directly. You can choose not to,
* but if you do, you are allowing for resources that deviate from the FHIR specification (in a way that the
* community considers clinically unsafe, since it means that software (probably) will miss vital signs for
* patients).
*
* @param validator
* @param appContext What was originally provided from the app for it's context
* @param stackPath The current path for the stack. Note that the because of cross-references and FHIRPath conformsTo() statements, the stack can wind through the content unpredictably.
* @param definition the definition being validated against (might be useful: ElementDefinition.base.path, ElementDefinition.type, ElementDefinition.binding
* @param structure The structure definition that contains the element definition being validated against (may be from the base spec, may be from a profile)
* @param resource The actual resource (as an element model) so that the implementation can inspect the values in order to decide what profiles to apply
* @param valid true if the resource is so far considered valid
* @param messages all the validation messages. Implementations can inspect this, but the real purpose is to populate the messages with information messages explaining why profiles were (or weren't) applied
* @return
*/
List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
Element resource,
boolean valid,
IMessagingServices msgServices,
List<ValidationMessage> messages);
}

View File

@ -26,10 +26,27 @@ class FhirRequestBuilderTest {
}
@Test
@DisplayName("Test resource format headers are added correctly.")
void addResourceFormatHeaders() {
@DisplayName("Test resource format headers are added correctly (GET).")
void addResourceFormatHeadersGET() {
String testFormat = "yaml";
Request.Builder request = new Request.Builder().url("http://www.google.com");
request.setMethod$okhttp("GET");
FhirRequestBuilder.addResourceFormatHeaders(request, testFormat);
Map<String, List<String>> headersMap = request.build().headers().toMultimap();
Assertions.assertNotNull(headersMap.get("Accept"), "Accept header null.");
Assertions.assertEquals(testFormat, headersMap.get("Accept").get(0),
"Accept header not populated with expected value " + testFormat + ".");
Assertions.assertNull(headersMap.get("Content-Type"), "Content-Type header not null.");
}
@Test
@DisplayName("Test resource format headers are added correctly (POST).")
void addResourceFormatHeadersPOST() {
String testFormat = "yaml";
Request.Builder request = new Request.Builder().url("http://www.google.com");
request.setMethod$okhttp("POST");
FhirRequestBuilder.addResourceFormatHeaders(request, testFormat);
Map<String, List<String>> headersMap = request.build().headers().toMultimap();

View File

@ -0,0 +1,78 @@
package org.hl7.fhir.utilities.filesystem;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import org.hl7.fhir.utilities.Utilities;
public class DirectoryVisitor {
public interface IDirectoryVisitorImplementation {
boolean enterDirectory(File directory) throws IOException;
boolean visitFile(File file) throws IOException; // return true if count
}
private IDirectoryVisitorImplementation worker;
private Set<String> extensions;
public DirectoryVisitor(IDirectoryVisitorImplementation worker, Set<String> extensions) {
super();
this.worker = worker;
this.extensions = extensions;
if (this.extensions == null) {
extensions = new HashSet<>();
}
}
public DirectoryVisitor(IDirectoryVisitorImplementation worker, String... extensionList) {
super();
this.worker = worker;
extensions = new HashSet<>();
for (String s : extensionList) {
extensions.add(s);
}
}
public DirectoryVisitor(IDirectoryVisitorImplementation worker) {
super();
this.worker = worker;
extensions = new HashSet<>();
}
public static int visitDirectory(IDirectoryVisitorImplementation worker, String path) throws IOException {
return new DirectoryVisitor(worker).visit(path);
}
public static int visitDirectory(IDirectoryVisitorImplementation worker, String path, Set<String> extensions) throws IOException {
return new DirectoryVisitor(worker, extensions).visit(path);
}
public static int visitDirectory(IDirectoryVisitorImplementation worker, String path, String... extensionList) throws IOException {
return new DirectoryVisitor(worker, extensionList).visit(path);
}
public int visit(String path) throws IOException {
return visit(ManagedFileAccess.file(path));
}
private int visit(File file) throws IOException {
int count = 0;
if (file.isDirectory()) {
if (worker.enterDirectory(file)) {
for (File f : ManagedFileAccess.listFiles(file)) {
count += visit(f);
}
}
} else {
String ext = file.getName().substring(file.getName().lastIndexOf(".")+1);
if (extensions.isEmpty() || extensions.contains(ext)) {
if (worker.visitFile(file)) {
count++;
}
}
}
return count;
}
}

View File

@ -61,6 +61,7 @@ public class ManagedFileAccess {
FileInputStream inStream(String pathname);
FileOutputStream outStream(String pathname);
CSFile csfile(String pathname);
File[] listFiles(File file) throws IOException; // file would be returned from file() above
}
public enum FileAccessPolicy {
@ -222,4 +223,20 @@ public class ManagedFileAccess {
}
}
public static File[] listFiles(File f) throws IOException {
switch (accessPolicy) {
case DIRECT:
if (!inAllowedPaths(f.getAbsolutePath())) {
throw new IOException("The pathname '"+f.getAbsolutePath()+"' cannot be accessed by policy");
}
return f.listFiles();
case MANAGED:
return accessor.listFiles(f);
case PROHIBITED:
throw new IOException("Access to files is not allowed by local security policy");
default:
throw new IOException("Internal Error");
}
}
}

View File

@ -125,7 +125,7 @@ public abstract class I18nBase {
if (Objects.nonNull(theMessageArguments) && theMessageArguments.length > 0) {
message = MessageFormat.format(messages.getString(theMessage).trim(), theMessageArguments);
} else {
message = messages.getString(theMessage).trim();
message = MessageFormat.format(messages.getString(theMessage).trim(), null);
}
}
return message;

View File

@ -1079,4 +1079,5 @@ public class I18nConstants {
public static final String SD_TYPE_PARAMETER_INVALID = "SD_TYPE_PARAMETER_INVALID";
public static final String SD_TYPE_PARAMETER_INVALID_REF = "SD_TYPE_PARAMETER_INVALID_REF";
public static final String SD_TYPE_PARAM_NOT_SPECIFIED = "SD_TYPE_PARAM_NOT_SPECIFIED";
public static final String SD_TYPE_PARAMETER_ABSTRACT_WARNING = "SD_TYPE_PARAMETER_ABSTRACT_WARNING";
}

View File

@ -16,6 +16,8 @@ import java.util.Set;
import org.hl7.fhir.utilities.StringPair;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.filesystem.DirectoryVisitor;
import org.hl7.fhir.utilities.filesystem.DirectoryVisitor.IDirectoryVisitorImplementation;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
/**
@ -123,9 +125,9 @@ public class POGenerator {
}
cd.defined = found;
}
scanJavaSource(new File(core), consts, "RenderingI18nContext", "RenderingContext");
scanJavaSource(new File(igpub), consts, "RenderingI18nContext", "RenderingContext");
scanPascalSource(new File(pascal), props);
scanJavaSource(core, consts, "RenderingI18nContext", "RenderingContext");
scanJavaSource(igpub, consts, "RenderingI18nContext", "RenderingContext");
scanPascalSource(pascal, props);
Set<String> pns = new HashSet<>();
for (PropertyValue p : props) {
@ -170,9 +172,9 @@ public class POGenerator {
cd.defined = found;
}
scanJavaSource(new File(core), consts, "I18nConstants");
scanJavaSource(new File(igpub), consts, "I18nConstants");
scanPascalSource(new File(pascal), props);
scanJavaSource(core, consts, "I18nConstants");
scanJavaSource(igpub, consts, "I18nConstants");
scanPascalSource(pascal, props);
pns = new HashSet<>();
for (PropertyValue p : props) {
@ -212,73 +214,86 @@ public class POGenerator {
return ok;
}
private boolean scanJavaSource(File file, List<ConstantDefinition> consts, String... names) throws FileNotFoundException, IOException {
if (file.isDirectory()) {
boolean found = true;
for (File f : file.listFiles()) {
if (!Utilities.existsInList(f.getName(), "model", "formats")) {
found = scanJavaSource(f, consts, names) && found;
}
}
return false;
} else {
String ext = file.getName().substring(file.getName().lastIndexOf(".")+1);
if ("java".equals(ext)) {
String source = TextFile.fileToString(file);
for (ConstantDefinition cd : consts) {
if (!cd.used) {
boolean found = false;
for (String n : names) {
if (source.contains(n+"."+cd.name+",")) {
found = true;
}
if (source.contains(n+"."+cd.name+")")) {
found = true;
}
if (source.contains(n+"."+cd.name+" :")) {
found = true;
}
if (source.contains(n+"."+cd.name+";")) {
found = true;
}
}
if (found) {
cd.used = true;
}
}
}
return true;
} else {
return false;
}
private class JavaScanner implements IDirectoryVisitorImplementation {
List<ConstantDefinition> consts;
List<String> names;
@Override
public boolean enterDirectory(File f) throws IOException {
return !Utilities.existsInList(f.getName(), "model", "formats");
}
}
private void scanPascalSource(File file, List<PropertyValue> defs) throws FileNotFoundException, IOException {
if (file.isDirectory()) {
for (File f : file.listFiles()) {
scanPascalSource(f, defs);
}
} else {
String ext = file.getName().substring(file.getName().lastIndexOf(".")+1);
if ("pas".equals(ext)) {
String source = TextFile.fileToString(file);
for (PropertyValue pv : defs) {
if (!pv.used) {
boolean found = false;
String pn = pv.getBaseName();
if (source.contains("'"+pn+"'")) {
@Override
public boolean visitFile(File file) throws IOException {
String source = TextFile.fileToString(file);
for (ConstantDefinition cd : consts) {
if (!cd.used) {
boolean found = false;
for (String n : names) {
if (source.contains(n+"."+cd.name+",")) {
found = true;
}
if (found) {
pv.used = true;
}
if (source.contains(n+"."+cd.name+")")) {
found = true;
}
if (source.contains(n+"."+cd.name+" :")) {
found = true;
}
if (source.contains(n+"."+cd.name+";")) {
found = true;
}
}
if (found) {
cd.used = true;
}
}
}
return true;
}
}
private void scanJavaSource(String path, List<ConstantDefinition> consts, String... names) throws FileNotFoundException, IOException {
JavaScanner scanner = new JavaScanner();
scanner.consts = consts;
scanner.names = new ArrayList<String>();
for (String s : names) {
scanner.names.add(s);
}
DirectoryVisitor.visitDirectory(scanner, path, "java");
}
private class PascalScanner implements IDirectoryVisitorImplementation {
private List<PropertyValue> defs;
@Override
public boolean enterDirectory(File directory) throws IOException {
return true;
}
@Override
public boolean visitFile(File file) throws IOException {
String source = TextFile.fileToString(file);
for (PropertyValue pv : defs) {
if (!pv.used) {
boolean found = false;
String pn = pv.getBaseName();
if (source.contains("'"+pn+"'")) {
found = true;
}
if (found) {
pv.used = true;
}
}
}
return true;
}
}
private void scanPascalSource(String path, List<PropertyValue> defs) throws FileNotFoundException, IOException {
PascalScanner scanner = new PascalScanner();
scanner.defs = defs;
DirectoryVisitor.visitDirectory(scanner, path, "pas");
}
private List<ConstantDefinition> loadConstants(String path) throws FileNotFoundException, IOException {
@ -438,7 +453,7 @@ public class POGenerator {
}
}
} else {
// we don't care; nothing to do
o.oldMsgId = null;
}
} else if (mode == 1) {
if (!value.equals(o.msgid)) {
@ -451,7 +466,7 @@ public class POGenerator {
o.msgstr.set(0, "!!"+o.msgstr.get(0));
}
} else {
// we don't care; nothing to do
o.oldMsgId = null;
}
} else if (mode == 2) {
if (!value.equals(o.msgidPlural)) {
@ -464,7 +479,7 @@ public class POGenerator {
o.msgstr.set(1, "!!"+o.msgstr.get(1));
}
} else {
// we don't care; nothing to do
o.oldMsgId = null;
}
}
}

View File

@ -20,7 +20,7 @@ BUNDLE_BUNDLE_ENTRY_FOUND_MULTIPLE_FRAGMENT = Found {0} matches for fragment {2}
BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED = Except for transactions and batches, each entry in a Bundle must have a fullUrl which is the identity of the resource in the entry
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES = The {1} resource matched more than one of the allowed profiles ({3})
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH = The {1} resource did not match any of the allowed profiles (Type {2}: {3})
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON = The {1} resource did not math the profile {2} because: {3}
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON = The {1} resource did not match the profile {2} because: {3}
BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT_one = Can''t find ''{1}'' in the bundle ({2}). Note that there is a resource in the bundle with the same type and id, but it does not match because of the fullUrl based rules around matching relative references (must be ``{3}``)
BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT_other = Can''t find ''{1}'' in the bundle ({2}). Note that there are {0} resources in the bundle with the same type and id, but they do not match because of the fullUrl based rules around matching relative references (one of ``{3}``)
BUNDLE_BUNDLE_ENTRY_NOTFOUND_FRAGMENT = Can''t find ''{0}'' in the bundle ({1})
@ -1107,3 +1107,4 @@ SD_TYPE_PARAMETER_UNKNOWN = The type definition ''{0}'' has the type parameter '
SD_TYPE_PARAMETER_INVALID = The type definition ''{2}'' has a type parameter ''{3}'', which is not consistent with it''s ancestor type definition ''{0}'' which has the type parameter ''{1}''
SD_TYPE_PARAMETER_INVALID_REF = The type ''{0}'' is a reference to ''{1}'' which has a type parameter ''{2}'' with a base type of {3} but the type parameter provided is ''{4}'' which is not the right type
SD_TYPE_PARAM_NOT_SPECIFIED = The type ''{0}'' at {3} is a reference to ''{1}'' which needs a type parameter ''{2}'' but a type parameter is not provided for ''{2}''
SD_TYPE_PARAMETER_ABSTRACT_WARNING = The type ''{0}'' at {3} refers to the abstract type ''{1}'' but the context is not an abstract type - this is usually an error

View File

@ -132,7 +132,7 @@ CAPABILITY_TYPS = Types
CAPABILITY_TYP_PRES = ype are only present if at least one of the resources has support for them.
CAPABILITY_UPDATE_INT = PUT a new resource version (update interaction)
CAPABILITY_VREAD_INT = GET past versions of resources (vread interaction)
CAPABILTY_ALLOW_CAP = Any FHIR capability may be 'allowed' by the system unless explicitly marked as 'SHALL NOT'. A few items are marked as MAY in the Implementation Guide to highlight their potential relevance to the use case.
CAPABILTY_ALLOW_CAP = Any FHIR capability may be ''allowed'' by the system unless explicitly marked as ''SHALL NOT''. A few items are marked as MAY in the Implementation Guide to highlight their potential relevance to the use case.
CAPABILTY_SHALL_SUPP = SHALL Support the Following Implementation Guides
CODESYSTEM_CONCEPTS = Concepts
CODESYSTEM_CONTENT_COMPLETE = This <param name="cased"/> code system <param name="cs"/> defines the following code<if test="code-count != 1">s</if><param name="h"/>:
@ -537,10 +537,10 @@ GENERAL_MODIFIERS = Modifiers
SEARCH_PAR_MULTIPLES = Multiples
SEARCH_PAR_MULTIPLE_AND_APPEAR = multipleAnd: The parameter may only appear once
SEARCH_PAR_MULTIPLE_AND_REPEAT = multipleAnd: The parameter may repeat in order to specify multiple values that must all be true
SEARCH_PAR_MULTIPLE_AND_SERVER = multipleAnd: It's up to the server whether the parameter may repeat in order to specify multiple values that must all be true
SEARCH_PAR_MULTIPLE_AND_SERVER = multipleAnd: It''s up to the server whether the parameter may repeat in order to specify multiple values that must all be true
SEARCH_PAR_MULTIPLE_OR_MULTIPLE = multipleOr: The parameter may have multiple values (separated by comma) where at least one must be true
SEARCH_PAR_MULTIPLE_OR_ONE = multipleOr: The parameter may only have one value (no comma separators)
SEARCH_PAR_MULTIPLE_OR_SERVER = multipleOr: It's up to the server whether the parameter can have multiple values (separated by comma) where at least one must be true
SEARCH_PAR_MULTIPLE_OR_SERVER = multipleOr: It''s up to the server whether the parameter can have multiple values (separated by comma) where at least one must be true
SEARCH_PAR_NONE = (none)
SEARCH_PAR_PROC = Processing Mode
SEARCH_PAR_REND_TARGET = Target Resources

View File

@ -109,6 +109,20 @@ class I18nBaseTest {
assertEquals("Documents oder Messages müssen mindestens einen Eintrag enthalten", result);
}
@Test
@DisplayName("Test double single quotes behaviour.")
void testDoubleSingleQuotes() {
I18nTestClass testClass = new I18nTestClass();
testClass.setLocale(Locale.US);
String actualMessageA = testClass.formatMessage(I18nConstants.VALUESET_EXAMPLE_SYSTEM_HINT, "Mazooma");
assertEquals("Example System 'Mazooma' specified, so Concepts and Filters can't be checked", actualMessageA);
String actualMessageB = testClass.formatMessage(I18nConstants.VALUESET_NO_SYSTEM_WARNING);
System.out.println(actualMessageB);
assertEquals("No System specified, so Concepts and Filters can't be checked", actualMessageB);
}
@Test
@DisplayName("Test German localization file contains no umlauts.")
void testThatNoOneHasMessedWithTheGermans() {

View File

@ -67,6 +67,7 @@ import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
import org.hl7.fhir.r5.utils.validation.IMessagingServices;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader;
@ -87,7 +88,7 @@ import org.hl7.fhir.validation.cli.utils.ValidationLevel;
import org.hl7.fhir.validation.instance.utils.IndexedElement;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class BaseValidator implements IValidationContextResourceLoader {
public class BaseValidator implements IValidationContextResourceLoader, IMessagingServices {
public static class BooleanHolder {
private boolean value = true;
@ -411,7 +412,7 @@ public class BaseValidator implements IValidationContextResourceLoader {
return thePass;
}
protected ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments) {
public ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments) {
String message = context.formatMessage(theMessage, theMessageArguments);
return addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage).setSignpost(true);
}

View File

@ -67,6 +67,7 @@ import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
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;
@ -104,6 +105,7 @@ import org.hl7.fhir.validation.cli.utils.ProfileLoader;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.cli.utils.SchemaValidator;
import org.hl7.fhir.validation.cli.utils.ValidationLevel;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
import org.hl7.fhir.utilities.ByteProvider;
@ -1253,4 +1255,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP
return res;
}
@Override
public List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid,
IMessagingServices msgServices, List<ValidationMessage> messages) {
return new BasePolicyAdvisorForFullValidation().getImpliedProfilesForResource(validator, appContext, stackPath,
definition, structure, resource, valid, msgServices, messages);
}
}

View File

@ -42,11 +42,12 @@ import org.hl7.fhir.utilities.json.parser.JsonParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.validation.cli.utils.Common;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForFullValidation;
import javax.annotation.Nonnull;
public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator {
public class StandAloneValidatorFetcher extends BasePolicyAdvisorForFullValidation implements IValidatorResourceFetcher, IValidationPolicyAdvisor, IWorkerContextManager.ICanonicalResourceLocator {
List<String> mappingsUris = new ArrayList<>();
private FilesystemPackageCacheManager pcm;
@ -75,31 +76,6 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
String url) {
return ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
}
@Override
public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator,
Object appContext,
StructureDefinition structure,
ElementDefinition element,
String containerType,
String containerId,
Element.SpecialElement containingResourceType,
String path,
String url) {
return ContainedReferenceValidationPolicy.CHECK_VALID;
}
@Override
public EnumSet<ResourceValidationAction> policyForResource(IResourceValidator validator, Object appContext,
StructureDefinition type, String path) {
return EnumSet.allOf(ResourceValidationAction.class);
}
@Override
public EnumSet<ElementValidationAction> policyForElement(IResourceValidator validator, Object appContext,
StructureDefinition structure, ElementDefinition element, String path) {
return EnumSet.allOf(ElementValidationAction.class);
}
@Override
public boolean resolveURL(IResourceValidator validator, Object appContext, String path, String url, String type, boolean canonical) throws IOException, FHIRException {
@ -316,19 +292,6 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV
}
}
@Override
public EnumSet<CodedContentValidationAction> policyForCodedContent(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
BindingKind kind,
AdditionalBindingPurpose purpose,
ValueSet valueSet,
List<String> systems) {
return EnumSet.allOf(CodedContentValidationAction.class);
}
@Override
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();

View File

@ -0,0 +1,166 @@
package org.hl7.fhir.validation.instance;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
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.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.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public class BasePolicyAdvisorForFullValidation implements IValidationPolicyAdvisor {
@Override
public ReferenceValidationPolicy policyForReference(IResourceValidator validator, Object appContext, String path, String url) {
return ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS;
}
@Override
public ContainedReferenceValidationPolicy policyForContained(IResourceValidator validator, Object appContext,
StructureDefinition structure, ElementDefinition element, String containerType, String containerId,
SpecialElement containingResourceType, String path, String url) {
return ContainedReferenceValidationPolicy.CHECK_VALID;
}
@Override
public EnumSet<ResourceValidationAction> policyForResource(IResourceValidator validator, Object appContext,
StructureDefinition type, String path) {
return EnumSet.allOf(ResourceValidationAction.class);
}
@Override
public EnumSet<ElementValidationAction> policyForElement(IResourceValidator validator, Object appContext,
StructureDefinition structure, ElementDefinition element, String path) {
return EnumSet.allOf(ElementValidationAction.class);
}
@Override
public EnumSet<CodedContentValidationAction> policyForCodedContent(IResourceValidator validator,
Object appContext,
String stackPath,
ElementDefinition definition,
StructureDefinition structure,
BindingKind kind,
AdditionalBindingPurpose purpose,
ValueSet valueSet,
List<String> systems) {
return EnumSet.allOf(CodedContentValidationAction.class);
}
@Override
public List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid,
IMessagingServices msgServices, List<ValidationMessage> messages) {
List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
if ("Observation".equals(resource.fhirType()) && VersionUtilities.isR4Plus(validator.getContext().getVersion())) {
getImpliedProfilesForObservation(profiles, msgServices, messages, validator.getContext(), stackPath, resource);
}
return profiles;
}
private void getImpliedProfilesForObservation(List<StructureDefinition> profiles, IMessagingServices msgServices, List<ValidationMessage> messages, IWorkerContext context, String stackPath, Element resource) {
Element code = resource.getNamedChild("code", false);
List<String> codes = new ArrayList<>();
if (hasLoincCode(code, codes, "85353-1")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "LOINC", codes);
} else if (hasLoincCode(code, codes, "9279-1", "76170-0", "76172-6", "76171-8", "19840-8", "33438-3", "76270-8", "11291-2")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/resprate", "Respiratory Rate", "LOINC", codes);
} else if (hasLoincCode(code, codes, "60978-4", "73795-7", "73799-9", "76476-1", "76477-9", "8867-4", "8889-8", "8890-6", "8891-4", "8892-2", "8893-0", "40443-4", "55425-3", "68999-2", "11328-2", "69000-8", "69000-8", "60978-4", "60978-4", "8890-6", "8886-4", "68999-2", "68999-2")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "LOINC", codes);
} else if (hasLoincCode(code, codes, "2708-6", "19224-5", "20564-1", "2709-4", "2710-2", "2713-6", "51733-4", "59408-5", "59417-6", "89276-0", "97549-0")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "LOINC", codes);
} else if (hasLoincCode(code, codes, "8310-5", "60834-9", "60835-6", "60836-4", "60838-0", "60955-2", "61009-7", "75539-7", "75987-8", "76010-8", "76011-6", "76278-1", "8309-7", "8310-5", "8328-7", "8329-5", "8330-3", "8331-1", "8332-9", "8333-7", "8334-5", "91371-5", "98657-0", "98663-8")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "LOINC", codes);
} else if (hasLoincCode(code, codes, "8302-2", "3137-7", "3138-5", "8302-2", "8306-3", "8308-9")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "LOINC", codes);
} else if (hasLoincCode(code, codes, "9843-4", "8287-5", "9843-4")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "LOINC", codes);
} else if (hasLoincCode(code, codes, "29463-7", "29463-7", "3141-9", "3142-7", "75292-3", "79348-9", "8350-1", "8351-9")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "LOINC", codes);
} else if (hasLoincCode(code, codes, "39156-5", "39156-5", "59574-4", "89270-3")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "LOINC", codes);
} else if (hasLoincCode(code, codes, "85354-9", "35094-2", "8459-0", "85354-9", "76534-7", "55284-4", "8480-6")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "LOINC", codes);
} else if (hasSctCode(code, codes, "46680005")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "86290005", "271625008", "271306003")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "271306003", "249043002", "444981005", "399017001", "251670001", "429525003", "429614003")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "103228002", "103228002", "442349007", "442476006", "442440005", "431314004", "442734002", "713194001")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "386725007", "276885007", "300076005", "1222808002", "364246006", "307047009", "708499008", "708499008", "431598003", "698831002", "698832009", "415882003", "415974002", "415929009", "415945006")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "1153637007", "1162419008", "50373000", "1162418000", "1230278008", "1162392001", "1162417005")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "363812007", "169876006", "1269262007", "363811000")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "363811000", "60621009", "735395000", "425024002", "424927000", "784399000", "1162416001", "1162415002")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "60621009")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "SNOMED CT", codes);
} else if (hasSctCode(code, codes, "75367002", "251076008", "163033001", "163035008", "386534000", "386536003", "271649006", "271649006", "271650006", "407556006", "407554009", "716579001", "399304008")) {
addProfile(profiles, msgServices, messages, context, stackPath, resource, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes);
}
}
private void addProfile(List<StructureDefinition> profiles, IMessagingServices msgServices, List<ValidationMessage> messages, IWorkerContext context, String stackPath, Element resource, String url, String name, String systemName, List<String> codes) {
resource.addMessage(msgServices.signpost(messages, null, IssueType.INFORMATIONAL, resource.line(), resource.col(), stackPath, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_OBS, url, name, systemName, codes.get(0)));
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd != null) {
profiles.add(sd);
} else {
// complain?
}
}
protected boolean hasLoincCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://loinc.org".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
protected boolean hasSctCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://snomed.info/sct".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
}

View File

@ -173,6 +173,7 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
import org.hl7.fhir.r5.utils.sql.Validator;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
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.IValidationProfileUsageTracker;
@ -595,7 +596,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean noBindingMsgSuppressed;
private Map<String, Element> fetchCache = new HashMap<>();
private HashMap<Element, ResourceValidationTracker> resourceTracker = new HashMap<>();
private IValidationPolicyAdvisor policyAdvisor;
private IValidationPolicyAdvisor policyAdvisor = new BasePolicyAdvisorForFullValidation();
long time = 0;
long start = 0;
long lastlog = 0;
@ -691,6 +692,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
@Override
public IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor) {
if (advisor == null) {
throw new Error("Cannot set advisor to null");
}
this.policyAdvisor = advisor;
return this;
}
@ -3111,7 +3115,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
if (!found) {
if (type.equals("canonical")) {
ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, valContext, path, url);
ReferenceValidationPolicy rp = policyAdvisor.policyForReference(this, valContext, path, url);
if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url) && ok;
} else {
@ -3128,7 +3132,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
} else {
if (type.equals("canonical")) {
ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, valContext, path, url);
ReferenceValidationPolicy rp = policyAdvisor.policyForReference(this, valContext, path, url);
if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) {
try {
Resource r = null;
@ -3581,8 +3585,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = false;
}
} else {
EnumSet<CodedContentValidationAction> validationPolicy = getPolicyAdvisor() == null ?
EnumSet.allOf(CodedContentValidationAction.class) : getPolicyAdvisor().policyForCodedContent(this, valContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, null, vs, new ArrayList<>());
EnumSet<CodedContentValidationAction> validationPolicy = policyAdvisor.policyForCodedContent(this, valContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, null, vs, new ArrayList<>());
if (!validationPolicy.isEmpty()) {
long t = System.nanoTime();
@ -3918,11 +3921,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (refType.equals("contained") || refType.equals("bundled")) {
pol = ReferenceValidationPolicy.CHECK_VALID;
} else {
if (policyAdvisor == null) {
pol = ReferenceValidationPolicy.IGNORE;
} else {
pol = policyAdvisor.policyForReference(this, valContext.getAppContext(), path, ref);
}
pol = policyAdvisor.policyForReference(this, valContext.getAppContext(), path, ref);
}
if (conditional) {
@ -5705,15 +5704,22 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = false;
}
if (checkSpecials) {
ok = checkSpecials(valContext, errors, element, stack, checkSpecials, pct, mode, fromContained) && ok;
ok = checkSpecials(valContext, errors, element, stack, checkSpecials, pct, mode, fromContained, ok) && ok;
ok = validateResourceRules(errors, element, stack) && ok;
}
return ok;
}
public boolean checkSpecials(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean contained) {
public boolean checkSpecials(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean contained, boolean isOk) {
boolean ok = true;
// first, does the policy advisor have profiles it wants us to check?
List<StructureDefinition> profiles = policyAdvisor.getImpliedProfilesForResource(this, valContext.getAppContext(), stack.getLiteralPath(),
element.getProperty().getDefinition(), element.getProperty().getStructure(), element, isOk, this, errors);
for (StructureDefinition sd : profiles) {
ok = startInner(valContext, errors, element, element, sd, stack, false, pct, mode, false) && ok;
}
long t = System.nanoTime();
try {
if (VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(element.getType())) {
@ -5966,8 +5972,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
SpecialElement special = element.getSpecial();
ContainedReferenceValidationPolicy containedValidationPolicy = getPolicyAdvisor() == null ?
ContainedReferenceValidationPolicy.CHECK_VALID : getPolicyAdvisor().policyForContained(this,
ContainedReferenceValidationPolicy containedValidationPolicy = policyAdvisor.policyForContained(this,
valContext, parentProfile, child, context.fhirType(), context.getId(), special, path, parentProfile.getUrl());
if (containedValidationPolicy.ignore()) {
@ -6021,7 +6026,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
checkSpecials(valContext, errors, element, stack, ok, pct, mode, true);
checkSpecials(valContext, errors, element, stack, ok, pct, mode, true, ok);
if (typeForResource.getProfile().size() == 1) {
long t = System.nanoTime();
@ -6427,8 +6432,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (debug) {
System.out.println(" check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getVersionedUrl()+time());
}
EnumSet<ElementValidationAction> actionSet = policyAdvisor == null ? EnumSet.allOf(ElementValidationAction.class) :
policyAdvisor.policyForElement(this, valContext.getAppContext(), profile, ei.getDefinition(), localStack.getLiteralPath());
EnumSet<ElementValidationAction> actionSet = policyAdvisor.policyForElement(this, valContext.getAppContext(), profile, ei.getDefinition(), localStack.getLiteralPath());
String localStackLiteralPath = localStack.getLiteralPath();
String eiPath = ei.getPath();

View File

@ -181,7 +181,7 @@ public class BundleValidator extends BaseValidator {
}
}
// also, while we're here, check the specials, since this doesn't happen anywhere else
((InstanceValidator) parent).checkSpecials(hostContext, errors, res, rstack, true, pct, mode, true);
((InstanceValidator) parent).checkSpecials(hostContext, errors, res, rstack, true, pct, mode, true, ok);
}
// todo: check specials

View File

@ -38,92 +38,8 @@ public class ObservationValidator extends BaseValidator {
element.getNamedChild("effectiveTiming", false) != null || element.getNamedChild("effectiveInstant", false) != null,
I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_AN_EFFECTIVEDATETIME_OR_AN_EFFECTIVEPERIOD, element.getProperty().typeSummary()) && ok;
// hook in the vital signs
if (VersionUtilities.isR4Plus(context.getVersion())) {
Element code = element.getNamedChild("code", false);
List<String> codes = new ArrayList<>();
if (hasLoincCode(code, codes, "85353-1")) {
ok = checkObservationAgainstProfile(valContext, errors, element, stack, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "9279-1", "76170-0", "76172-6", "76171-8", "19840-8", "33438-3", "76270-8", "11291-2")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/resprate", "Respiratory Rate", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "60978-4", "73795-7", "73799-9", "76476-1", "76477-9", "8867-4", "8889-8", "8890-6", "8891-4", "8892-2", "8893-0", "40443-4", "55425-3", "68999-2", "11328-2", "69000-8", "69000-8", "60978-4", "60978-4", "8890-6", "8886-4", "68999-2", "68999-2")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "2708-6", "19224-5", "20564-1", "2709-4", "2710-2", "2713-6", "51733-4", "59408-5", "59417-6", "89276-0", "97549-0")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "8310-5", "60834-9", "60835-6", "60836-4", "60838-0", "60955-2", "61009-7", "75539-7", "75987-8", "76010-8", "76011-6", "76278-1", "8309-7", "8310-5", "8328-7", "8329-5", "8330-3", "8331-1", "8332-9", "8333-7", "8334-5", "91371-5", "98657-0", "98663-8")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "8302-2", "3137-7", "3138-5", "8302-2", "8306-3", "8308-9")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "9843-4", "8287-5", "9843-4")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "29463-7", "29463-7", "3141-9", "3142-7", "75292-3", "79348-9", "8350-1", "8351-9")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "39156-5", "39156-5", "59574-4", "89270-3")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "85354-9", "35094-2", "8459-0", "85354-9", "76534-7", "55284-4", "8480-6")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "LOINC", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "46680005")) {
ok = checkObservationAgainstProfile(valContext, errors, element, stack, "http://hl7.org/fhir/StructureDefinition/vitalspanel", "Vital Signs Panel", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "86290005", "271625008", "271306003")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "Blood pressure systolic and diastolic", "Respiratory Rate", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "271306003", "249043002", "444981005", "399017001", "251670001", "429525003", "429614003")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/heartrate", "Heart rate", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "103228002", "103228002", "442349007", "442476006", "442440005", "431314004", "442734002", "713194001")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/oxygensat", "Oxygen saturation", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "386725007", "276885007", "300076005", "1222808002", "364246006", "307047009", "708499008", "708499008", "431598003", "698831002", "698832009", "415882003", "415974002", "415929009", "415945006")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodytemp", "Body temperature", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "1153637007", "1162419008", "50373000", "1162418000", "1230278008", "1162392001", "1162417005")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyheight", "Body height", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "363812007", "169876006", "1269262007", "363811000")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/headcircum", "Head circumference", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "363811000", "60621009", "735395000", "425024002", "424927000", "784399000", "1162416001", "1162415002")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "60621009")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "75367002", "251076008", "163033001", "163035008", "386534000", "386536003", "271649006", "271649006", "271650006", "407556006", "407554009", "716579001", "399304008")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes, pct, mode) && ok;
}
}
// Looking for the vital signs code? It's moved to BasePolicyAdvisorForFullValidation.getImpliedProfilesForObservation
return ok;
}
private boolean checkObservationAgainstProfile(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack, String url, String name, String sys, List<String> loinc, PercentageTracker pct, ValidationMode mode) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_OBS, url, name, sys, loinc.get(0)));
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
return false;
} else {
return ((InstanceValidator) parent).startInner(valContext, errors, element, element, sd, stack, false, pct, mode, false);
}
}
private boolean hasLoincCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://loinc.org".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
private boolean hasSctCode(Element code, List<String> codes, String... values) {
if (code != null) {
List<Element> codings = code.getChildren("coding");
for (Element coding : codings) {
if ("http://snomed.info/sct".equals(coding.getNamedChildValue("system", false)) && Utilities.existsInList(coding.getNamedChildValue("code", false), values)) {
codes.add(coding.getNamedChildValue("code", false));
return true;
}
}
}
return false;
}
}

View File

@ -14,6 +14,7 @@ import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.elementmodel.Element;
@ -87,6 +88,7 @@ public class StructureDefinitionValidator extends BaseValidator {
StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
if (warning(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), base != null, I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), "StructureDefinition, so can't check the differential")) {
if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, stack.getLiteralPath(), sd.hasDerivation(), I18nConstants.SD_MUST_HAVE_DERIVATION, sd.getUrl())) {
checkTypeParameters(errors, stack, base, sd);
boolean bok = base.getAbstract() || sd.hasKind() && sd.getKind() == base.getKind();
ok = rule(errors, "2022-11-02", IssueType.NOTFOUND, stack.getLiteralPath(), bok, I18nConstants.SD_CONSTRAINED_KIND_NO_MATCH, sd.getKind().toCode(), base.getKind().toCode(), base.getType(), base.getUrl()) && ok;
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
@ -193,6 +195,27 @@ public class StructureDefinitionValidator extends BaseValidator {
return ok;
}
private boolean checkTypeParameters(List<ValidationMessage> errors, NodeStack stack, StructureDefinition base, StructureDefinition derived) {
String bt = ToolingExtensions.readStringExtension(base, ToolingExtensions.EXT_TYPE_PARAMETER);
if (bt == null) {
return true;
} else {
if (rule(errors, "2024-05-29", IssueType.INVALID, stack.getLiteralPath(), derived.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER), I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl())) {
String dt = ToolingExtensions.readStringExtension(derived, ToolingExtensions.EXT_TYPE_PARAMETER);
StructureDefinition bsd = context.fetchTypeDefinition(bt);
StructureDefinition dsd = context.fetchTypeDefinition(dt);
if (rule(errors, "2024-05-29", IssueType.INVALID, stack.getLiteralPath(), bsd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt) &&
rule(errors, "2024-05-29", IssueType.INVALID, stack.getLiteralPath(), dsd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt)) {
StructureDefinition t = dsd;
while (t != bsd && t != null) {
t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
}
return rule(errors, "2024-05-29", IssueType.INVALID, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt);
}
}
return false;
}
}
private String getFixedValue(Element src) {
Element diff = src.getNamedChild("differential", false);
@ -465,7 +488,8 @@ public class StructureDefinitionValidator extends BaseValidator {
typeCodes.add(tc);
Set<String> tcharacteristics = new HashSet<>();
StructureDefinition tsd = context.fetchTypeDefinition(tc);
if (tsd != null) {
if (tsd != null) {
checkTypeParameters(errors, stack, type, tc, tsd, path, sd);
characteristicsValid = true;
if (tsd.hasExtension(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
for (Extension ext : tsd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_CHARACTERISTICS)) {
@ -566,6 +590,54 @@ public class StructureDefinitionValidator extends BaseValidator {
return ok;
}
private boolean checkTypeParameters(List<ValidationMessage> errors, NodeStack stack, Element typeE, String tc, StructureDefinition tsd, String path, StructureDefinition sd) {
boolean ok = true;
if (tsd.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
List<Element> extensions = typeE.getChildrenByName("extension");
for (Extension ext : tsd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)) {
String name = ext.getExtensionString("name");
String type = ext.getExtensionString("type");
StructureDefinition psd = context.fetchTypeDefinition(type);
if (psd != null && name != null) {
boolean found = false;
for (Element e : extensions) {
if (ToolingExtensions.EXT_TYPE_PARAMETER.equals(e.getNamedChildValue("url"))) {
String ename = e.getExtensionValue("name").primitiveValue();
if (name.equals(ename)) {
found = true;
String etype = e.getExtensionValue("type").primitiveValue();
for (Extension ex : sd.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_PARAMETER)) {
String tn = ex.getExtensionString("name");
if (tn != null && tn.equals(etype)) {
etype = ex.getExtensionString("type");
break;
}
}
StructureDefinition esd = context.fetchTypeDefinition(etype);
if (rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), esd != null, I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, tc, etype)) {
StructureDefinition t = esd;
while (t != null && t != psd) {
t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
}
ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_INVALID_REF, tc, etype, tsd.getVersionedUrl(), name, type) & ok;
if (t != null) {
if (!sd.getAbstract() && esd.getAbstract()) {
warning(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), t != null, I18nConstants.SD_TYPE_PARAMETER_ABSTRACT_WARNING, tc, etype, tsd.getVersionedUrl(), name, type);
}
}
} else {
ok = false;
}
}
}
}
ok = rule(errors, "2024-05-29", IssueType.BUSINESSRULE, stack.getLiteralPath(), found, I18nConstants.SD_TYPE_PARAM_NOT_SPECIFIED, tc, tsd.getVersionedUrl(), name, path) && ok;
}
}
}
return ok;
}
private ElementDefinition getDefinitionFromBase(StructureDefinition base, String id, String path) {
ElementDefinition ed = null;
if (id != null) {

View File

@ -59,6 +59,7 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.OperationOutcomeUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
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;
@ -90,6 +91,7 @@ import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidatorUtils;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher;
import org.hl7.fhir.validation.instance.BasePolicyAdvisorForFullValidation;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.AfterClass;
@ -469,7 +471,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
private ValidationEngine buildVersionEngine(String ver, String txLog) throws Exception {
String server = FhirSettings.getTxFhirLocal();
String server = FhirSettings.getTxFhirDevelopment();
switch (ver) {
case "1.0": return TestUtilities.getValidationEngine("hl7.fhir.r2.core#1.0.2", server, txLog, FhirPublication.DSTU2, true, "1.0.2");
case "1.4": return TestUtilities.getValidationEngine("hl7.fhir.r2b.core#1.4.0", server, txLog, FhirPublication.DSTU2016May, true, "1.4.0");
@ -887,4 +889,12 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
public Set<String> fetchCanonicalResourceVersions(IResourceValidator validator, Object appContext, String url) {
return new HashSet<>();
}
@Override
public List<StructureDefinition> getImpliedProfilesForResource(IResourceValidator validator, Object appContext,
String stackPath, ElementDefinition definition, StructureDefinition structure, Element resource, boolean valid,
IMessagingServices msgServices, List<ValidationMessage> messages) {
return new BasePolicyAdvisorForFullValidation().getImpliedProfilesForResource(validator, appContext, stackPath,
definition, structure, resource, valid, msgServices, messages);
}
}

View File

@ -21,7 +21,7 @@
<commons_compress_version>1.26.0</commons_compress_version>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.5.10</validator_test_case_version>
<validator_test_case_version>1.5.11-SNAPSHOT</validator_test_case_version>
<jackson_version>2.17.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>