
org.hl7.fhir.validation.instance.InstanceValidator Maven / Gradle / Ivy
package org.hl7.fhir.validation.instance;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.fhir.ucum.Decimal;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions;
import org.hl7.fhir.r5.context.ContextUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.OIDSummary;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.elementmodel.JsonParser;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.elementmodel.ParserBase;
import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy;
import org.hl7.fhir.r5.elementmodel.ResourceParser;
import org.hl7.fhir.r5.elementmodel.ValidatedFragment;
import org.hl7.fhir.r5.elementmodel.XmlParser;
import org.hl7.fhir.r5.fhirpath.ExpressionNode;
import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.fhirpath.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.FunctionDetails;
import org.hl7.fhir.r5.fhirpath.FHIRPathUtilityClasses.TypedElementDefinition;
import org.hl7.fhir.r5.fhirpath.TypeDetails;
import org.hl7.fhir.r5.formats.FormatUtilities;
import org.hl7.fhir.r5.model.Address;
import org.hl7.fhir.r5.model.Attachment;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Base.ProfileSource;
import org.hl7.fhir.r5.model.Base.ValidationInfo;
import org.hl7.fhir.r5.model.Base.ValidationMode;
import org.hl7.fhir.r5.model.Base.ValidationReason;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.DataType;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.DateType;
import org.hl7.fhir.r5.model.DecimalType;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.AdditionalBindingPurposeVS;
import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
import org.hl7.fhir.r5.model.ElementDefinition.ConstraintSeverity;
import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Enumeration;
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.HumanName;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.ImplementationGuide.ImplementationGuideGlobalComponent;
import org.hl7.fhir.r5.model.InstantType;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Period;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.Quantity;
import org.hl7.fhir.r5.model.Range;
import org.hl7.fhir.r5.model.Ratio;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.SampledData;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r5.model.TimeType;
import org.hl7.fhir.r5.model.Timing;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.UrlType;
import org.hl7.fhir.r5.model.UsageContext;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.r5.utils.BuildExtensions;
import org.hl7.fhir.r5.utils.ResourceUtilities;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.UserDataNames;
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.sql.Validator.TrueFalseOrUnknown;
import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
import org.hl7.fhir.r5.utils.validation.IResourceValidator;
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.IValidationProfileUsageTracker;
import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.validation.ValidatorSession;
import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
import org.hl7.fhir.r5.utils.validation.constants.CheckDisplayOption;
import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
import org.hl7.fhir.r5.utils.validation.constants.IdStatus;
import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.HL7WorkGroups;
import org.hl7.fhir.utilities.HL7WorkGroups.HL7WorkGroup;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.SIDUtilities;
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;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
import org.hl7.fhir.utilities.filesystem.ManagedFileAccess;
import org.hl7.fhir.utilities.http.HTTPResultException;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.validation.IDigitalSignatureServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.ai.CodeAndTextValidationRequest;
import org.hl7.fhir.validation.ai.CodeAndTextValidationResult;
import org.hl7.fhir.validation.ai.CodeAndTextValidator;
import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.codesystem.CodingsObserver;
import org.hl7.fhir.validation.instance.type.BundleValidator;
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.ConceptMapValidator;
import org.hl7.fhir.validation.instance.type.ImplementationGuideValidator;
import org.hl7.fhir.validation.instance.type.MeasureValidator;
import org.hl7.fhir.validation.instance.type.ObservationValidator;
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator;
import org.hl7.fhir.validation.instance.type.StructureMapValidator;
import org.hl7.fhir.validation.instance.type.StructureMapValidator.VariableDefn;
import org.hl7.fhir.validation.instance.type.StructureMapValidator.VariableSet;
import org.hl7.fhir.validation.instance.type.ValueSetValidator;
import org.hl7.fhir.validation.instance.type.ViewDefinitionValidator;
import org.hl7.fhir.validation.instance.utils.Base64Util;
import org.hl7.fhir.validation.instance.utils.CanonicalResourceLookupResult;
import org.hl7.fhir.validation.instance.utils.CanonicalTypeSorter;
import org.hl7.fhir.validation.instance.utils.ChildIterator;
import org.hl7.fhir.validation.instance.utils.ElementInfo;
import org.hl7.fhir.validation.instance.utils.EnableWhenEvaluator;
import org.hl7.fhir.validation.instance.utils.FHIRPathExpressionFixer;
import org.hl7.fhir.validation.instance.utils.IndexedElement;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ResolvedReference;
import org.hl7.fhir.validation.instance.utils.ResourceValidationTracker;
import org.hl7.fhir.validation.instance.utils.StructureDefinitionSorterByUrl;
import org.hl7.fhir.validation.instance.utils.UrlUtil;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
import org.w3c.dom.Document;
/**
* Thinking of using this in a java program? Don't!
* You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine
*
* Validation todo:
* - support @default slices
*
* @author Grahame Grieve
*/
/*
* todo:
* check urn's don't start oid: or uuid:
* check MetadataResource.url is absolute
*/
public class InstanceValidator extends BaseValidator implements IResourceValidator {
public enum BindingContext {
BASE, MAXVS, ADDITIONAL
}
private static final String EXECUTED_CONSTRAINT_LIST = "validator.executed.invariant.list";
private static final String EXECUTION_ID = "validator.execution.id";
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 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 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"));
private static final HashSet EXTENSION_CONTEXT_LIST = new HashSet<>(Arrays.asList("ElementDefinition.example.value", "ElementDefinition.pattern", "ElementDefinition.fixed"));
private static final HashSet ID_EXEMPT_LIST = new HashSet<>(Arrays.asList("id", "base64Binary", "markdown"));
private static final HashSet HTML_ELEMENTS = new HashSet<>(Arrays.asList(
"p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
"small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
"ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
"code", "samp", "img", "map", "area"));
private static final HashSet HTML_ATTRIBUTES = new HashSet<>(Arrays.asList(
"title", "style", "class", "id", "idref", "lang", "xml:lang", "dir", "accesskey", "tabindex",
// tables
"span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan"));
private static final HashSet HTML_COMBO_LIST = new HashSet<>(Arrays.asList(
"a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
"a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
"img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
"area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
"table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"));
private static final HashSet HTML_BLOCK_LIST = new HashSet<>(Arrays.asList("div", "blockquote", "table", "ol", "ul", "p"));
private static final HashSet RESOURCE_X_POINTS = new HashSet<>(Arrays.asList("Bundle.entry.resource", "Bundle.entry.response.outcome", "DomainResource.contained", "Parameters.parameter.resource", "Parameters.parameter.part.resource"));
private class ValidatorHostServices implements IEvaluationContext {
@Override
public List resolveConstant(FHIRPathEngine engine, Object appContext, String name, boolean beforeContext, boolean explicitConstant) throws PathEngineException {
ValidationContext c = (ValidationContext) appContext;
if ("profile".equals(name) && explicitConstant) {
List b = new ArrayList<>();
if (c.getProfile() != null) {
b.add(c.getProfile());
}
return b;
}
if (externalHostServices != null)
return externalHostServices.resolveConstant(engine, c.getAppContext(), name, beforeContext, explicitConstant);
else
return new ArrayList ();
}
@Override
public TypeDetails resolveConstantType(FHIRPathEngine engine, Object appContext, String name, boolean explicitConstant) throws PathEngineException {
if (appContext instanceof VariableSet) {
VariableSet vars = (VariableSet) appContext;
VariableDefn v = vars.getVariable(name.substring(1));
if (v != null && v.hasTypeInfo()) {
return new TypeDetails(CollectionStatus.SINGLETON, v.getWorkingType());
} else {
return null;
}
}
ValidationContext c = (ValidationContext) appContext;
if (externalHostServices != null)
return externalHostServices.resolveConstantType(engine, c.getAppContext(), name, explicitConstant);
else
return null;
}
@Override
public boolean log(String argument, List focus) {
if (externalHostServices != null)
return externalHostServices.log(argument, focus);
else
return false;
}
@Override
public FunctionDetails resolveFunction(FHIRPathEngine engine, String functionName) {
switch (functionName) {
case "slice": return new FunctionDetails("Returns the given slice as defined in the given structure definition. If in an invariant, First parameter can be %profile - current profile", 2, 2);
case "getResourceKey" : return new FunctionDetails("Unique Key for resource", 0, 0);
case "getReferenceKey" : return new FunctionDetails("Unique Key for resource that is the target of the reference", 0, 1);
default:
if (externalHostServices != null) {
return externalHostServices.resolveFunction(engine, functionName);
} else {
return null;
}
}
}
@Override
public TypeDetails checkFunction(FHIRPathEngine engine, Object appContext, String functionName, TypeDetails focus, List parameters) throws PathEngineException {
switch (functionName) {
case "slice":
// todo: check parameters
return focus;
case "getResourceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string");
case "getReferenceKey" : return new TypeDetails(CollectionStatus.SINGLETON, "string");
default:
if (externalHostServices != null) {
return externalHostServices.checkFunction(engine, appContext, functionName, focus, parameters);
} else {
throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCHECKFUNCTION));
}
}
}
@Override
public List executeFunction(FHIRPathEngine engine, Object appContext, List focus, String functionName, List> parameters) {
switch (functionName) {
case "slice": return executeSlice(engine, appContext, focus, parameters);case "getResourceKey" : return executeResourceKey(focus);
case "getReferenceKey" : return executeReferenceKey(null, focus, parameters);
default:
if (externalHostServices != null) {
return externalHostServices.executeFunction(engine, appContext, focus, functionName, parameters);
} else {
throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESEXECUTEFUNCTION));
}
}
}
private List executeResourceKey(List focus) {
List base = new ArrayList ();
if (focus.size() == 1) {
Base res = focus.get(0);
base.add(new StringType(res.fhirType()+"/"+res.getIdBase()));
}
return base;
}
private List executeReferenceKey(Base rootResource, List focus, List> parameters) {
List base = new ArrayList ();
if (focus.size() == 1) {
Base res = focus.get(0);
String ref = null;
if (res.fhirType().equals("Reference")) {
ref = getRef(res);
} else if (res.isPrimitive()) {
ref = res.primitiveValue();
} else {
throw new FHIRException("Unable to generate a reference key based on a "+res.fhirType());
}
base.add(new StringType(ref));
}
return base;
}
private String getRef(Base res) {
Property prop = res.getChildByName("reference");
if (prop != null && prop.getValues().size() == 1) {
return prop.getValues().get(0).primitiveValue();
}
return null;
}
private List executeSlice(FHIRPathEngine engine, Object appContext, List focus, List> parameters) {
ValidationContext c = (ValidationContext) appContext;
List res = new ArrayList<>();
if (parameters.size() != 2 && !(appContext instanceof ValidationContext)) {
return res;
}
StructureDefinition sd = null;
// if present, first parameter must be a singleton that points to the current profile
if (parameters.get(0).size() > 1) {
return res;
} else if (parameters.get(0).size() == 1) { // if it's there, we have to check it
Base b = parameters.get(0).get(0);
if (b.isPrimitive()) {
sd = context.fetchResource(StructureDefinition.class, b.primitiveValue());
} else if (b instanceof StructureDefinition) {
sd = (StructureDefinition) b;
}
} else {
sd = c.getProfile();
}
// second parameter must be present
if (parameters.get(1).size() != 1) {
return res;
}
String name = parameters.get(1).get(0).primitiveValue();
if (!Utilities.noString(name)) {
for (Base b : focus) {
if (b instanceof Element) {
Element e = (Element) b;
if (e.hasSlice(sd, name)) {
res.add(e);
}
}
}
}
return res;
}
@Override
public Base resolveReference(FHIRPathEngine engine, Object appContext, String url, Base refContext) throws FHIRException {
ValidationContext c = (ValidationContext) appContext;
if (refContext != null && refContext.hasUserData(UserDataNames.validator_bundle_resolution)) {
return (Base) refContext.getUserData(UserDataNames.validator_bundle_resolution);
}
if (c.getAppContext() instanceof Element) {
Element element = (Element) c.getAppContext();
while (element != null) {
Base res = resolveInBundle(url, element);
if (res != null) {
return res;
}
element = element.getParentForValidator();
}
}
Base res = resolveInBundle(url, c.getResource());
if (res != null) {
return res;
}
Element element = c.getRootResource();
while (element != null) {
res = resolveInBundle(url, element);
if (res != null) {
return res;
}
element = element.getParentForValidator();
}
if (externalHostServices != null) {
return setParentsBase(externalHostServices.resolveReference(engine, c.getAppContext(), url, refContext));
} else if (fetcher != null) {
try {
return setParents(fetcher.fetch(InstanceValidator.this, c.getAppContext(), url));
} catch (IOException e) {
throw new FHIRException(e);
}
} else {
throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET__RESOLVE__LOCALLY_2, url));
}
}
@Override
public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base item, String url) throws FHIRException {
ValidationContext ctxt = (ValidationContext) appContext;
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_, url));
}
InstanceValidator self = InstanceValidator.this;
List valerrors = new ArrayList();
ValidationMode mode = new ValidationMode(ValidationReason.Expression, ProfileSource.FromExpression);
if (item instanceof Resource) {
try {
Element e = new ObjectConverter(context).convert((Resource) item);
setParents(e);
self.validateResource(new ValidationContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null,
mode, false, false);
} catch (IOException e1) {
throw new FHIRException(e1);
}
} else if (item instanceof Element) {
Element e = (Element) item;
if (e.getSpecial() == SpecialElement.CONTAINED) {
self.validateResource(new ValidationContext(ctxt.getAppContext(), e, ctxt.getRootResource(), ctxt.getGroupingResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode, false, false);
} else if (e.getSpecial() != null) {
self.validateResource(new ValidationContext(ctxt.getAppContext(), e, e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode, false, false);
} else {
self.validateResource(new ValidationContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode, false, false);
}
} else
throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
boolean ok = true;
List record = new ArrayList<>();
for (ValidationMessage v : valerrors) {
ok = ok && !v.getLevel().isError();
if (v.getLevel().isError() || v.isSlicingHint()) {
record.add(v);
}
}
if (!ok && !record.isEmpty()) {
ctxt.sliceNotes(url, record);
}
return ok;
}
@Override
public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) {
ValidationContext c = (ValidationContext) appContext;
if (c.getProfile() != null && url.startsWith("#")) {
for (Resource r : c.getProfile().getContained()) {
if (r.getId().equals(url.substring(1))) {
if (r instanceof ValueSet)
return (ValueSet) r;
else
throw new FHIRException(context.formatMessage(I18nConstants.REFERENCE__REFERS_TO_A__NOT_A_VALUESET, url, r.fhirType()));
}
}
return null;
}
return context.findTxResource(ValueSet.class, cu.pinValueSet(url));
}
@Override
public boolean paramIsType(String name, int index) {
return false;
}
}
private FHIRPathEngine fpe;
public FHIRPathEngine getFHIRPathEngine() {
return fpe;
}
// configuration items
private CheckDisplayOption checkDisplay;
private boolean anyExtensionsAllowed;
private boolean errorForUnknownProfiles;
private boolean noInvariantChecks;
private boolean wantInvariantInMessage;
private boolean hintAboutNonMustSupport;
private boolean showMessagesFromReferences;
private String validationLanguage;
private boolean baseOnly;
private boolean noCheckAggregation;
private boolean wantCheckSnapshotUnchanged;
private boolean noUnicodeBiDiControlChars;
private HtmlInMarkdownCheck htmlInMarkdownCheck;
private boolean allowComments;
private boolean allowDoubleQuotesInFHIRPath;
private List igs = new ArrayList<>();
private List extensionDomains = new ArrayList();
private IdStatus resourceIdRule;
private boolean allowXsiLocation;
// used during the build process to keep the overall volume of messages down
private boolean suppressLoincSnomedMessages;
// time tracking
private boolean noBindingMsgSuppressed;
private Map fetchCache = new HashMap<>();
private HashMap resourceTracker = new HashMap<>();
long time = 0;
long start = 0;
long lastlog = 0;
private IEvaluationContext externalHostServices;
private boolean noExtensibleWarnings;
private String serverBase;
private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
private String executionId;
private IValidationProfileUsageTracker tracker;
private ValidatorHostServices validatorServices;
private boolean assumeValidRestReferences;
private boolean securityChecks;
private ProfileUtilities profileUtilities;
private boolean crumbTrails;
private List bundleValidationRules = new ArrayList<>();
private boolean validateValueSetCodesOnTxServer = true;
private QuestionnaireMode questionnaireMode;
private Map crLookups = new HashMap<>();
private boolean logProgress;
private CodingsObserver codingObserver;
public List validatedContent;
public boolean testMode;
private boolean example ;
private IDigitalSignatureServices signatureServices;
private boolean unknownCodeSystemsCauseErrors;
private boolean noExperimentalContent;
private List textsToCheck = new ArrayList<>();
private String aiService;
private Set textsToCheckKeys = new HashSet<>();
private String cacheFolder;
public InstanceValidator(@Nonnull IWorkerContext theContext, @Nonnull IEvaluationContext hostServices, @Nonnull XVerExtensionManager xverManager, ValidatorSession session) {
super(theContext, xverManager, false, session);
start = System.currentTimeMillis();
this.externalHostServices = hostServices;
this.profileUtilities = new ProfileUtilities(theContext, null, null);
fpe = new FHIRPathEngine(context);
validatorServices = new ValidatorHostServices();
fpe.setHostServices(validatorServices);
if (theContext.getVersion().startsWith("3.0") || theContext.getVersion().startsWith("1.0"))
fpe.setLegacyMode(true);
source = Source.InstanceValidator;
fpe.setDoNotEnforceAsSingletonRule(!VersionUtilities.isR5VerOrLater(theContext.getVersion()));
fpe.setAllowDoubleQuotes(allowDoubleQuotesInFHIRPath);
codingObserver = new CodingsObserver(theContext, xverManager, debug, session);
}
@Override
public boolean isNoExtensibleWarnings() {
return noExtensibleWarnings;
}
@Override
public IResourceValidator setNoExtensibleWarnings(boolean noExtensibleWarnings) {
this.noExtensibleWarnings = noExtensibleWarnings;
return this;
}
@Override
public boolean isShowMessagesFromReferences() {
return showMessagesFromReferences;
}
@Override
public void setShowMessagesFromReferences(boolean showMessagesFromReferences) {
this.showMessagesFromReferences = showMessagesFromReferences;
}
@Override
public boolean isNoInvariantChecks() {
return noInvariantChecks;
}
@Override
public IResourceValidator setNoInvariantChecks(boolean value) {
this.noInvariantChecks = value;
return this;
}
@Override
public boolean isWantInvariantInMessage() {
return wantInvariantInMessage;
}
@Override
public IResourceValidator setWantInvariantInMessage(boolean wantInvariantInMessage) {
this.wantInvariantInMessage = wantInvariantInMessage;
return this;
}
public IValidationProfileUsageTracker getTracker() {
return this.tracker;
}
public IResourceValidator setTracker(IValidationProfileUsageTracker value) {
this.tracker = value;
return this;
}
public boolean isHintAboutNonMustSupport() {
return hintAboutNonMustSupport;
}
public void setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) {
this.hintAboutNonMustSupport = hintAboutNonMustSupport;
}
public boolean isAssumeValidRestReferences() {
return this.assumeValidRestReferences;
}
public void setAssumeValidRestReferences(boolean value) {
this.assumeValidRestReferences = value;
}
public boolean isAllowComments() {
return allowComments;
}
public void setAllowComments(boolean allowComments) {
this.allowComments = allowComments;
}
public boolean isCrumbTrails() {
return crumbTrails;
}
public void setCrumbTrails(boolean crumbTrails) {
this.crumbTrails = crumbTrails;
}
public boolean isDoImplicitFHIRPathStringConversion() {
return fpe.isDoImplicitStringConversion();
}
public void setDoImplicitFHIRPathStringConversion(boolean doImplicitFHIRPathStringConversion) {
fpe.setDoImplicitStringConversion(doImplicitFHIRPathStringConversion);
}
private boolean allowUnknownExtension(String url) {
if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression"))
// Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
return true;
for (String s : extensionDomains)
if (url.startsWith(s))
return true;
return anyExtensionsAllowed;
}
private boolean isKnownExtension(String url) {
// Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with
if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") ||
url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") ||
url.equals("http://hl7.org/fhir/StructureDefinition/codesystem-properties-mode"))
return true;
for (String s : extensionDomains)
if (url.startsWith(s))
return true;
return false;
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format) throws FHIRException {
return validate(appContext, errors, stream, format, new ArrayList<>());
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format, String profile) throws FHIRException {
ArrayList profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
return validate(appContext, errors, stream, format, profiles);
}
private StructureDefinition getSpecifiedProfile(String profile) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile);
if (sd == null) {
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_LOCATE_THE_PROFILE__IN_ORDER_TO_VALIDATE_AGAINST_IT, profile));
}
return sd;
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format, List profiles) throws FHIRException {
ParserBase parser = Manager.makeParser(context, format);
List logicals = new ArrayList<>();
for (StructureDefinition sd : profiles) {
if (sd.getKind() == StructureDefinitionKind.LOGICAL) {
logicals.add(sd);
}
}
if (logicals.size() > 0) {
if (rulePlural(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, "Configuration", logicals.size() == 1, logicals.size(), I18nConstants.MULTIPLE_LOGICAL_MODELS, ResourceUtilities.listUrls(logicals))) {
parser.setLogical(logicals.get(0));
}
}
if (parser instanceof XmlParser) {
((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
}
parser.setupValidation(ValidationPolicy.EVERYTHING);
if (parser instanceof XmlParser) {
((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
}
if (parser instanceof JsonParser) {
((JsonParser) parser).setAllowComments(allowComments);
}
parser.setSignatureServices(signatureServices);
long t = System.nanoTime();
validatedContent = null;
try {
validatedContent = parser.parse(stream);
} catch (IOException e1) {
throw new FHIRException(e1);
}
timeTracker.load(t);
if (validatedContent != null && !validatedContent.isEmpty()) {
if (SAVE_INTERMEDIARIES) {
int index = 0;
for (ValidatedFragment ne : validatedContent) {
index++;
saveValidatedContent(ne, index);
}
}
String url = parser.getImpliedProfile();
if (url != null) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, "Payload", false, "Implied profile "+url+" not known to validator");
} else {
profiles.add(sd);
}
}
for (ValidatedFragment ne : validatedContent) {
if (ne.getElement() != null) {
validate(appContext, ne.getErrors(), validatedContent.size() > 1 ? ne.path() : null, ne.getElement(), profiles);
}
errors.addAll(ne.getErrors());
}
}
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(ValidatedFragment 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 errors, Resource resource) throws FHIRException {
return validate(appContext, errors, resource, new ArrayList<>());
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource, String profile) throws FHIRException {
ArrayList profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
return validate(appContext, errors, resource, profiles);
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource, List profiles) throws FHIRException {
long t = System.nanoTime();
Element e = new ResourceParser(context).parse(resource);
timeTracker.load(t);
validate(appContext, errors, null, e, profiles);
return e;
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element) throws FHIRException {
return validate(appContext, errors, element, new ArrayList<>());
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element, String profile) throws FHIRException {
ArrayList profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
return validate(appContext, errors, element, profiles);
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element, List profiles) throws FHIRException {
XmlParser parser = new XmlParser(context);
parser.setupValidation(ValidationPolicy.EVERYTHING);
long t = System.nanoTime();
Element e;
try {
e = parser.parse(errors, element);
} catch (IOException e1) {
throw new FHIRException(e1);
}
timeTracker.load(t);
if (e != null) {
validate(appContext, errors, null, e, profiles);
}
return e;
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document) throws FHIRException {
return validate(appContext, errors, document, new ArrayList<>());
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document, String profile) throws FHIRException {
ArrayList profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
return validate(appContext, errors, document, profiles);
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document, List profiles) throws FHIRException {
XmlParser parser = new XmlParser(context);
parser.setupValidation(ValidationPolicy.EVERYTHING);
long t = System.nanoTime();
Element e;
try {
e = parser.parse(errors, document);
} catch (IOException e1) {
throw new FHIRException(e1);
}
timeTracker.load(t);
if (e != null)
validate(appContext, errors, null, e, profiles);
return e;
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object) throws FHIRException {
return validate(appContext, errors, object, new ArrayList<>());
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object, String profile) throws FHIRException {
ArrayList profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
return validate(appContext, errors, object, profiles);
}
@Override
public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object, List profiles) throws FHIRException {
JsonParser parser = new JsonParser(context, new ProfileUtilities(context, null, null, fpe));
parser.setupValidation(ValidationPolicy.EVERYTHING);
long t = System.nanoTime();
Element e = parser.parse(errors, object);
timeTracker.load(t);
if (e != null)
validate(appContext, errors, null, e, profiles);
return e;
}
@Override
public void validate(Object appContext, List errors, String initialPath, Element element) throws FHIRException {
validate(appContext, errors, initialPath, element, new ArrayList<>());
}
@Override
public void validate(Object appContext, List errors, String initialPath, Element element, String profile) throws FHIRException {
ArrayList profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
validate(appContext, errors, initialPath, element, profiles);
}
@Override
public void validate(Object appContext, List errors, String path, Element element, List profiles) throws FHIRException {
// this is the main entry point; all the other public entry points end up here coming here...
// so the first thing to do is to clear the internal state
fetchCache.clear();
fetchCache.put(element.fhirType() + "/" + element.getIdBase(), element);
resourceTracker.clear();
trackedMessages.clear();
messagesToRemove.clear();
executionId = UUID.randomUUID().toString();
baseOnly = profiles.isEmpty();
setParents(element);
long t = System.nanoTime();
NodeStack stack = new NodeStack(context, null, element, validationLanguage);
if (profiles == null || profiles.isEmpty()) {
validateResource(new ValidationContext(appContext, element), errors, element, element, null, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.BaseDefinition), false, false);
} else {
int i = 0;
while (i < profiles.size()) {
StructureDefinition sd = profiles.get(i);
if (sd.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
StructureDefinition dep = context.fetchResource( StructureDefinition.class, ext.getValue().primitiveValue(), sd);
if (dep == 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(), sd.getVersionedUrl());
} else if (!profiles.contains(dep)) {
profiles.add(dep);
}
}
}
i++;
}
for (StructureDefinition defn : profiles) {
validateResource(new ValidationContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.ConfigProfile), false, false);
}
}
if (hintAboutNonMustSupport && !profiles.isEmpty()) {
checkElementUsage(errors, element, stack);
}
codingObserver.finish(errors, stack);
errors.removeAll(messagesToRemove);
timeTracker.overall(t);
if (aiService != null && !textsToCheck.isEmpty()) {
t = System.nanoTime();
CodeAndTextValidator ctv = new CodeAndTextValidator(cacheFolder, aiService);
List results = null;
try {
results = ctv.validateCodings(textsToCheck);
} catch (Exception e) {
if (e.getCause() != null && e.getCause() instanceof HTTPResultException) {
warning(errors, "2025-01-14", IssueType.EXCEPTION, stack, false,
I18nConstants.VALIDATION_AI_FAILED_LOG, e.getMessage(), ((HTTPResultException)e.getCause()).logPath);
} else {
warning(errors, "2025-01-14", IssueType.EXCEPTION, stack, false,
I18nConstants.VALIDATION_AI_FAILED, e.getMessage());
}
}
if (results != null) {
for (CodeAndTextValidationResult vr : results) {
if (!vr.isValid()) {
warning(errors, "2025-01-14", IssueType.BUSINESSRULE, vr.getRequest().getLocation().line(), vr.getRequest().getLocation().col(), vr.getRequest().getLocation().getLiteralPath(), false,
I18nConstants.VALIDATION_AI_TEXT_CODE, vr.getRequest().getCode(), vr.getRequest().getText(), vr.getConfidence(), vr.getExplanation());
}
}
}
timeTracker.ai(t);
}
if (DEBUG_ELEMENT) {
element.printToOutput();
}
}
private void checkElementUsage(List errors, Element element, NodeStack stack) {
if (element.getPath()==null
|| (element.getName().equals("id") && !element.getPath().substring(0, element.getPath().length()-3).contains("."))
|| (element.getName().equals("text") && !element.getPath().substring(0, element.getPath().length()-5).contains(".")))
return;
String hasFixed = element.getUserString(UserDataNames.keyview_hasFixed);
if (element.getPath().contains(".") && (hasFixed== null || !hasFixed.equals("Y"))) {
String elementUsage = element.getUserString(UserDataNames.keyview_elementSupported);
hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage != null && (elementUsage.equals("Y") || elementUsage.equals("NA")), I18nConstants.MUSTSUPPORT_VAL_MUSTSUPPORT, element.getName(), element.getProperty().getStructure().getVersionedUrl());
if (elementUsage==null || !elementUsage.equals("Y"))
return;
}
if (element.hasChildren() && (hasFixed== null || !hasFixed.equals("Y"))) {
String prevName = "";
int elementCount = 0;
for (Element ce : element.getChildren()) {
if (ce.getName().equals(prevName))
elementCount++;
else {
elementCount = 1;
prevName = ce.getName();
}
checkElementUsage(errors, ce, stack.push(ce, elementCount, null, null));
}
}
}
private boolean check(String v1, String v2) {
boolean res = v1 == null ? Utilities.noString(v1) : v1.equals(v2);
return res;
}
private boolean checkAddress(List errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use", false), fixed.getUseElement(), fixedSource, "use", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".text", focus.getNamedChild("text", false), fixed.getTextElement(), fixedSource, "text", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".city", focus.getNamedChild("city", false), fixed.getCityElement(), fixedSource, "city", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".state", focus.getNamedChild("state", false), fixed.getStateElement(), fixedSource, "state", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".country", focus.getNamedChild("country", false), fixed.getCountryElement(), fixedSource, "country", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip", false), fixed.getPostalCodeElement(), fixedSource, "postalCode", focus, pattern, context) && ok;
List lines = new ArrayList();
focus.getNamedChildren("line", lines);
boolean lineSizeCheck;
if (pattern) {
lineSizeCheck = lines.size() >= fixed.getLine().size();
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, lineSizeCheck, I18nConstants.FIXED_TYPE_CHECKS_DT_ADDRESS_LINE, Integer.toString(fixed.getLine().size()),
Integer.toString(lines.size()))) {
for (int i = 0; i < fixed.getLine().size(); i++) {
StringType fixedLine = fixed.getLine().get(i);
boolean found = false;
List allErrorsFixed = new ArrayList<>();
List errorsFixed = null;
for (int j = 0; j < lines.size() && !found; ++j) {
errorsFixed = new ArrayList<>();
checkFixedValue(errorsFixed, path + ".line", lines.get(j), fixedLine, fixedSource, "line", focus, pattern, context);
if (!hasErrors(errorsFixed)) {
found = true;
} else {
errorsFixed.stream().filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal()).forEach(t -> allErrorsFixed.add(t));
}
}
if (!found) {
ok = rule(errorsFixed, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, false, I18nConstants.PATTERN_CHECK_STRING, fixedLine.getValue(), fixedSource, allErrorsFixed) && ok;
}
}
} else {
ok = false;
}
} else if (!pattern) {
lineSizeCheck = lines.size() == fixed.getLine().size();
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, lineSizeCheck, I18nConstants.FIXED_TYPE_CHECKS_DT_ADDRESS_LINE,
Integer.toString(fixed.getLine().size()), Integer.toString(lines.size()))) {
for (int i = 0; i < lines.size(); i++) {
ok = checkFixedValue(errors, path + ".line", lines.get(i), fixed.getLine().get(i), fixedSource, "line", focus, pattern, context) && ok;
}
} else {
ok = false;
}
}
return ok;
}
private boolean checkAttachment(List errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType", false), fixed.getContentTypeElement(), fixedSource, "contentType", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".language", focus.getNamedChild("language", false), fixed.getLanguageElement(), fixedSource, "language", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".data", focus.getNamedChild("data", false), fixed.getDataElement(), fixedSource, "data", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".url", focus.getNamedChild("url", false), fixed.getUrlElement(), fixedSource, "url", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".size", focus.getNamedChild("size", false), fixed.getSizeElement(), fixedSource, "size", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash", false), fixed.getHashElement(), fixedSource, "hash", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".title", focus.getNamedChild("title", false), fixed.getTitleElement(), fixedSource, "title", focus, pattern, context) && ok;
return ok;
}
// public API
private boolean checkCode(List errors, Element element, String path, String code, String system, String version, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException {
boolean ok = true;
long t = System.nanoTime();
boolean ss = context.supportsSystem(system, baseOptions.getFhirVersion());
timeTracker.tx(t, "ss "+system);
if (ss) {
t = System.nanoTime();
ValidationResult s = checkCodeOnServer(stack, code, system, version, display, checkDisplay);
timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'");
if (s == null)
return true;
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, s, element, path, false, null, null) & ok;
if (s.isOk()) {
if (s.getMessage() != null && !s.messageIsInIssues()) {
txWarning(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, I18nConstants.TERMINOLOGY_PASSTHROUGH_TX_MESSAGE, s.getMessage(), system, code);
}
return ok;
}
if (!s.messageIsInIssues()) {
if (s.getErrorClass() != null && s.getErrorClass().isInfrastructure())
txWarning(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
else if (s.getSeverity() == IssueSeverity.INFORMATION)
txHint(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
else if (s.getSeverity() == IssueSeverity.WARNING)
txWarning(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
else
return txRule(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, I18nConstants.TERMINOLOGY_PASSTHROUGH_TX_MESSAGE, s.getMessage(), system, code) && ok;
}
return ok;
} else if (system.startsWith("http://build.fhir.org") || system.startsWith("https://build.fhir.org")) {
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_WRONG_BUILD, system, suggestSystemForBuild(system));
return false;
} else if (system.startsWith("http://hl7.org/fhir") || system.startsWith("https://hl7.org/fhir") || system.startsWith("http://www.hl7.org/fhir") || system.startsWith("https://www.hl7.org/fhir")) {
if (SIDUtilities.isknownCodeSystem(system)) {
return ok; // else don't check these (for now)
} else if (system.startsWith("http://hl7.org/fhir/test")) {
return ok; // we don't validate these
} else if (system.endsWith(".html")) {
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_WRONG_HTML, system, suggestSystemForPage(system));
return false;
} else {
CodeSystem cs = getCodeSystem(system);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, I18nConstants.TERMINOLOGY_TX_SYSTEM_UNKNOWN, system)) {
ConceptDefinitionComponent def = getCodeDefinition(cs, code);
if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, I18nConstants.UNKNOWN_CODESYSTEM, system, code))
return warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), I18nConstants.TERMINOLOGY_TX_DISPLAY_WRONG, def.getDisplay()) && ok;
}
return false;
}
} else if (context.isNoTerminologyServer() && NO_TX_SYSTEM_EXEMPT.contains(system)) {
txWarning(errors, NO_RULE_DATE, null, IssueType.BUSINESSRULE, element.line(), element.col(), path, false, I18nConstants.ERROR_VALIDATING_CODE_RUNNING_WITHOUT_TERMINOLOGY_SERVICES, code, system);
return ok; // no checks in this case
} else if (startsWithButIsNot(system, "http://snomed.info/sct", "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_INVALID, system);
return false;
} else {
try {
if (context.fetchResourceWithException(ValueSet.class, system, element.getProperty().getStructure()) != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET, system) && ok;
// Lloyd: This error used to prohibit checking for downstream issues, but there are some cases where that checking needs to occur. Please talk to me before changing the code back.
}
boolean done = false;
if (system.startsWith("https:") && system.length() > 7) {
String ns = "http:"+system.substring(6);
CodeSystem cs = getCodeSystem(ns);
if (cs != null || NO_HTTPS_LIST.contains(system)) {
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_HTTPS, system);
done = true;
}
}
if (!isAllowExamples() || !Utilities.startsWithInList(system, "http://example.org", "https://example.org")) {
CodeSystem cs = context.fetchCodeSystem(system);
if (cs == null) {
hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.UNKNOWN_CODESYSTEM, system);
} else {
if (hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, cs.getContent() != CodeSystemContentMode.NOTPRESENT, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOT_USABLE, system)) {
ok = rule(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, false, "Error - this should not happen? (Consult GG)") && ok;
}
}
}
return ok;
} catch (Exception e) {
return ok;
}
}
}
private Object suggestSystemForPage(String system) {
if (system.contains("/codesystem-")) {
String s = system.substring(system.indexOf("/codesystem-")+12);
String url = "http://hl7.org/fhir/"+s.replace(".html", "");
if (context.fetchCodeSystem(url) != null) {
return url;
} else {
return "{unable to determine intended url}";
}
}
if (system.contains("/valueset-")) {
String s = system.substring(system.indexOf("/valueset-")+8);
String url = "http://hl7.org/fhir/"+s.replace(".html", "");
if (context.fetchCodeSystem(url) != null) {
return url;
} else {
return "{unable to determine intended url}";
}
}
return "{unable to determine intended url}";
}
private Object suggestSystemForBuild(String system) {
if (system.contains("/codesystem-")) {
String s = system.substring(system.indexOf("/codesystem-")+12);
String url = "http://hl7.org/fhir/"+s.replace(".html", "");
if (context.fetchCodeSystem(url) != null) {
return url;
} else {
return "{unable to determine intended url}";
}
}
if (system.contains("/valueset-")) {
String s = system.substring(system.indexOf("/valueset-")+8);
String url = "http://hl7.org/fhir/"+s.replace(".html", "");
if (context.fetchCodeSystem(url) != null) {
return url;
} else {
return "{unable to determine intended url}";
}
}
system = system.replace("https://", "http://");
if (system.length() < 22) {
return "{unable to determine intended url}";
}
system = "http://hl7.org/fhir/"+system.substring(22).replace(".html", "");
if (context.fetchCodeSystem(system) != null) {
return system;
} else {
return "{unable to determine intended url}";
}
}
private boolean startsWithButIsNot(String system, String... uri) {
for (String s : uri)
if (!system.equals(s) && system.startsWith(s))
return true;
return false;
}
private boolean hasErrors(List errors) {
if (errors != null) {
for (ValidationMessage vm : errors) {
if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) {
return true;
}
}
}
return false;
}
private boolean checkCodeableConcept(List errors, String path, Element focus, CodeableConcept fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".text", focus.getNamedChild("text", false), fixed.getTextElement(), fixedSource, "text", focus, pattern, context) && ok;
List codings = new ArrayList();
focus.getNamedChildren("coding", codings);
if (pattern) {
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() >= fixed.getCoding().size(), I18nConstants.TERMINOLOGY_TX_CODING_COUNT, Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) {
for (int i = 0; i < fixed.getCoding().size(); i++) {
Coding fixedCoding = fixed.getCoding().get(i);
boolean found = false;
List allErrorsFixed = new ArrayList<>();
List errorsFixed;
for (int j = 0; j < codings.size() && !found; ++j) {
errorsFixed = new ArrayList<>();
checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern, context);
if (!hasErrors(errorsFixed)) {
found = true;
} else {
errorsFixed
.stream()
.filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal())
.forEach(t -> allErrorsFixed.add(t));
}
}
if (!found) {
// The argonaut DSTU2 labs profile requires userSelected=false on the category.coding and this
// needs to produce an understandable error message
// String message = "Expected CodeableConcept " + (pattern ? "pattern" : "fixed value") + " not found for" +
// " system: " + fixedCoding.getSystemElement().asStringValue() +
// " code: " + fixedCoding.getCodeElement().asStringValue() +
// " display: " + fixedCoding.getDisplayElement().asStringValue();
// if (fixedCoding.hasUserSelected()) {
// message += " userSelected: " + fixedCoding.getUserSelected();
// }
// message += " - Issues: " + allErrorsFixed;
// TYPE_CHECKS_PATTERN_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4}
// TYPE_CHECKS_PATTERN_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4}
// TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4}
// TYPE_CHECKS_FIXED_CC_US = The pattern [system {0}, code {1}, display "{2}" and userSelected {5}] defined in the profile {3} not found. Issues: {4}
if (fixedCoding.hasUserSelected()) {
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, false, pattern ? I18nConstants.TYPE_CHECKS_PATTERN_CC_US : I18nConstants.TYPE_CHECKS_FIXED_CC_US,
fixedCoding.getSystemElement().asStringValue(), fixedCoding.getCodeElement().asStringValue(), fixedCoding.getDisplayElement().asStringValue(),
fixedSource, allErrorsFixed, fixedCoding.getUserSelected()) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, false, pattern ? I18nConstants.TYPE_CHECKS_PATTERN_CC : I18nConstants.TYPE_CHECKS_FIXED_CC,
fixedCoding.getSystemElement().asStringValue(), fixedCoding.getCodeElement().asStringValue(), fixedCoding.getDisplayElement().asStringValue(),
fixedSource, allErrorsFixed) && ok;
}
}
}
} else {
ok = false;
}
} else {
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), I18nConstants.TERMINOLOGY_TX_CODING_COUNT, Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) {
for (int i = 0; i < codings.size(); i++)
ok = checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus, false, context) && ok;
} else {
ok = false;
}
}
return ok;
}
private boolean checkCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, BooleanHolder bh) {
boolean checkDisp = true;
BooleanHolder checked = new BooleanHolder(false);
if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) {
try {
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
if (cc.hasText() && cc.hasCoding()) {
for (Coding c : cc.getCoding()) {
recordCodeTextCombo(stack, theElementCntext.getBase().getPath(), c, cc.getText());
}
}
if (binding.hasValueSet()) {
String vsRef = binding.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = binding.getStrength();
Extension maxVS = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET);
checkDisp = validateBindingCodeableConcept(errors, path, element, profile, stack, bh, checkDisp, checked, cc, vsRef, valueset, strength, maxVS, true, null);
// } else if (binding.hasValueSet()) {
// hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
} else if (!noBindingMsgSuppressed) {
hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
}
for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) {
StringBuilder b = new StringBuilder();
if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) {
String vsRef = ab.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = convertPurposeToStrength(ab.getPurpose());
checkDisp = validateBindingCodeableConcept(errors, path, element, profile, stack, bh, checkDisp, checked, cc, vsRef, valueset, strength, null, false, b.toString()) && checkDisp;
}
}
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage());
}
}
}
if (!noTerminologyChecks && theElementCntext != null && !checked.ok()) { // no binding check, so we just check the CodeableConcept generally
try {
CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
if (cc.hasText() && cc.hasCoding()) {
for (Coding c : cc.getCoding()) {
recordCodeTextCombo(stack, theElementCntext.getBase().getPath(), c, cc.getText());
}
}
if (cc.hasCoding()) {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, null, cc);
bh.see(calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, null, null));
timeTracker.tx(t, "vc " + cc.toString());
}
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage());
}
}
return checkDisp;
}
private void recordCodeTextCombo(NodeStack node, String path, Coding c, String text) {
if (!c.hasDisplay() || !c.getDisplay().equals(text)) {
ValidationResult vr = context.validateCode(baseOptions.setDisplayWarningMode(false)
.setLanguages(node.getWorkingLang()), c.getSystem(), c.getVersion(), c.getCode(), text);
if (!vr.isOk()) {
int key = (c.getSystem()+"||"+c.getCode()+"||"+text).hashCode();
if (!textsToCheckKeys.contains(key)) {
textsToCheckKeys.add(key);
textsToCheck.add(new CodeAndTextValidationRequest(node, path, node.getWorkingLang() == null ? context.getLocale().toLanguageTag() : node.getWorkingLang(), c.getSystem(), c.getCode(), vr.getDisplay(), text));
}
}
}
}
private boolean isInScope(ElementDefinitionBindingAdditionalComponent ab, StructureDefinition profile, Element resource, StringBuilder b) {
if (ab.getUsage().isEmpty()) {
return true;
}
boolean ok = true;
for (UsageContext usage : ab.getUsage()) {
if (!isInScope(usage, profile, resource, b)) {
ok = false;
}
}
return ok;
}
private boolean isInScope(UsageContext usage, StructureDefinition profile, Element resource, StringBuilder b) {
if (isKnownUsage(usage)) {
return true;
}
if (usage.getCode().hasSystem() && (usage.getCode().getSystem().equals(profile.getUrl()) || usage.getCode().getSystem().equals(profile.getVersionedUrl()))) {
// if it's not a defined usage from external sources, it might match something in the data content
List items = findDataValue(resource, usage.getCode().getCode());
if (matchesUsage(items, usage.getValue())) {
b.append(context.formatMessage(I18nConstants.BINDING_ADDITIONAL_USAGE, displayCoding(usage.getCode()), display(usage.getValue())));
return true;
}
}
return false;
}
private String displayCoding(Coding value) {
return value.getCode();
}
private String displayCodeableConcept(CodeableConcept value) {
for (Coding c : value.getCoding()) {
String s = displayCoding(c);
if (s != null) {
return s;
}
}
return value.getText();
}
private String display(DataType value) {
switch (value.fhirType()) {
case "Coding" : return displayCoding((Coding) value);
case "CodeableConcept" : return displayCodeableConcept((CodeableConcept) value);
}
return value.fhirType();
}
private boolean matchesUsage(List items, DataType value) {
for (Element item : items) {
if (matchesUsage(item, value)) {
return true;
}
}
return false;
}
private String display(List items) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (Element item : items) {
display(b, item);
}
return b.toString();
}
private void display(CommaSeparatedStringBuilder b, Element item) {
if (item.isPrimitive()) {
b.append(item.primitiveValue());
} else if (item.fhirType().equals("CodeableConcept")) {
for (Element c : item.getChildren("coding")) {
b.append(c.getNamedChildValue("code"));
}
} else {
b.append(item.toString());
}
}
private boolean matchesUsage(Element item, DataType value) {
switch (value.fhirType()) {
case "CodeableConcept": return matchesUsageCodeableConcept(item, (CodeableConcept) value);
case "Quantity": return false;
case "Range": return false;
case "Reference": return false;
default: return false;
}
}
private boolean matchesUsageCodeableConcept(Element item, CodeableConcept value) {
switch (item.fhirType()) {
case "CodeableConcept": return matchesUsageCodeableConceptCodeableConcept(item, value);
case "Coding": return matchesUsageCodeableConceptCoding(item, value);
default: return false;
}
}
private boolean matchesUsageCodeableConceptCoding(Element item, CodeableConcept value) {
String system = item.getNamedChildValue("system");
String version = item.getNamedChildValue("version");
String code = item.getNamedChildValue("code");
for (Coding c : value.getCoding()) {
if (system == null || !system.equals(c.getSystem())) {
return false;
}
if (code == null || !code.equals(c.getCode())) {
return false;
}
if (c.hasVersion()) {
if (version == null || !version.equals(c.getVersion())) {
return false;
}
}
return true;
}
return false;
}
private boolean matchesUsageCodeableConceptCodeableConcept(Element item, CodeableConcept value) {
for (Element code : item.getChildren("coding")) {
if (matchesUsageCodeableConceptCoding(code, value)) {
return true;
}
}
return false;
}
private List findDataValue(Element resource, String code) {
List items = new ArrayList();
if (resource != null) {
findDataValues(items, resource, code);
}
return items;
}
private void findDataValues(List items, Element element, String path) {
if (element.getPath() == null) {
return;
}
if (pathMatches(element.getPath(), path)) {
items.add(element);
} else if (element.hasChildren() && path.startsWith(element.getPath())) {
for (Element child : element.getChildren()) {
findDataValues(items, child, path);
}
}
}
private boolean pathMatches(String actualPath, String pathSpec) {
String[] ap = actualPath.split("\\.");
String[] ps = pathSpec.split("\\.");
if (ap.length != ps.length) {
return false;
}
for (int i = 0; i < ap.length; i++) {
if (!pathSegmentMatches(ap[i], ps[i])) {
return false;
}
}
return true;
}
private boolean pathSegmentMatches(String ap, String ps) {
if (ps.contains("[")) {
return ap.equals(ps);
} else {
if (ap.contains("[")) {
ap = ap.substring(0, ap.indexOf("["));
}
return ap.equals(ps);
}
}
private BindingStrength convertPurposeToStrength(AdditionalBindingPurposeVS purpose) {
switch (purpose) {
case MAXIMUM: return BindingStrength.REQUIRED;
case EXTENSIBLE: return BindingStrength.EXTENSIBLE;
case PREFERRED: return BindingStrength.PREFERRED;
case REQUIRED: return BindingStrength.REQUIRED;
default: return null;
}
}
private boolean isTestableBinding(ElementDefinitionBindingAdditionalComponent ab) {
return ab.hasValueSet() && convertPurposeToStrength(ab.getPurpose()) != null;
}
private boolean validateBindingCodeableConcept(List errors, String path, Element element, StructureDefinition profile, NodeStack stack, BooleanHolder bh, boolean checkDisp, BooleanHolder checked,
CodeableConcept cc, String vsRef, ValueSet valueset, BindingStrength strength, Extension maxVS, boolean base, String usageNote) throws CheckCodeOnServerException {
if (valueset == null) {
CodeSystem cs = context.fetchCodeSystem(vsRef);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(vsRef))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(vsRef));
} else {
bh.fail();
}
} else {
BindingContext bc = base ? BindingContext.BASE : BindingContext.ADDITIONAL;
if (!cc.hasCoding()) {
if (strength == BindingStrength.REQUIRED)
bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeReference(vsRef, valueset, bc, usageNote)));
else if (strength == BindingStrength.EXTENSIBLE) {
if (maxVS != null)
bh.see(rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringFromExtension(maxVS)), valueset.getVersionedUrl()));
else if (!noExtensibleWarnings) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(vsRef, valueset, bc, usageNote));
}
}
} else {
long t = System.nanoTime();
// Check whether the codes are appropriate for the type of binding we have
boolean bindingsOk = true;
if (strength != BindingStrength.EXAMPLE) {
if (strength == BindingStrength.REQUIRED) {
removeTrackedMessagesForLocation(errors, element, path);
}
boolean atLeastOneSystemIsSupported = false;
for (Coding nextCoding : cc.getCoding()) {
String nextSystem = nextCoding.getSystem();
if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem, baseOptions.getFhirVersion())) {
atLeastOneSystemIsSupported = true;
break;
}
}
if (!atLeastOneSystemIsSupported && strength == BindingStrength.EXAMPLE) {
// ignore this since we can't validate but it doesn't matter..
} else {
checked.set(true);
ValidationResult vr = checkCodeOnServer(stack, valueset, cc);
bh.see(calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, vsRef, strength));
if (!vr.isOk()) {
bindingsOk = false;
if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) {
if (strength == BindingStrength.REQUIRED || (strength == BindingStrength.EXTENSIBLE && maxVS != null)) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOSVC_BOUND_REQ, describeReference(vsRef));
} else if (strength == BindingStrength.EXTENSIBLE) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOSVC_BOUND_EXT, describeReference(vsRef));
}
} else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
if (strength == BindingStrength.REQUIRED)
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(vsRef), vr.getErrorClass().toString());
else if (strength == BindingStrength.EXTENSIBLE) {
if (maxVS != null)
bh.see(checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(maxVS), cc, stack));
else if (!noExtensibleWarnings)
txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(vsRef), vr.getErrorClass().toString());
} else if (strength == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_3_CC, describeReference(vsRef), vr.getErrorClass().toString());
}
}
} else if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
// we've already handled the warnings / errors about this, and set the status correctly. We don't need to do anything more?
} else if (vr.getErrorClass() != TerminologyServiceErrorClass.SERVER_ERROR) { // (should have?) already handled server error
if (strength == BindingStrength.REQUIRED) {
bh.see(txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(vsRef, valueset, bc, usageNote), ccSummary(cc)));
} else if (strength == BindingStrength.EXTENSIBLE) {
if (maxVS != null)
bh.see(checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(maxVS), cc, stack));
if (!noExtensibleWarnings)
txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(vsRef, valueset, bc, usageNote), ccSummary(cc));
} else if (strength == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeReference(vsRef, valueset, bc, usageNote), ccSummary(cc));
}
}
}
} else if (vr.getMessage() != null) {
if (vr.getTrimmedMessage() != null) {
if (vr.getSeverity() == IssueSeverity.INFORMATION) {
txHint(errors, "2023-07-03", vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
} else {
checkDisp = false;
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getTrimmedMessage());
}
}
} else {
if (strength == BindingStrength.EXTENSIBLE) {
removeTrackedMessagesForLocation(errors, element, path);
}
checkDisp = false;
}
}
// Then, for any codes that are in code systems we are able
// to validate, we'll validate that the codes actually exist
if (bindingsOk && false) {
for (Coding nextCoding : cc.getCoding()) {
bh.see(checkBindings(errors, path, element, stack, valueset, nextCoding));
}
}
timeTracker.tx(t, "vc "+cc.toString());
}
}
}
return checkDisp;
}
// private String notFoundSeverityNoteForBinding(BindingStrength strength, Set systems) {
// if (strength == BindingStrength.REQUIRED &&
// (Utilities.listValueStartsWith("http://hl7.org/fhir", systems) || Utilities.listValueStartsWith("http://terminology.hl7.org", systems))) {
// return "error because this is a required binding to an HL7 code system";
// } else {
// return null;
// }
// }
//
// /**
// * The terminology server will report an error for an unknown code system or version, or a dependent valueset
// *
// * but we only care for validation if the binding strength is strong enough.
// * @param binding
// * @return
// */
// private org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity notFoundSeverityForBinding(BindingStrength strength, String systems) {
// if (strength == BindingStrength.REQUIRED &&
// (Utilities.listValueStartsWith("http://hl7.org/fhir", systems) || Utilities.listValueStartsWith("http://terminology.hl7.org", systems))) {
// return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
// } else if (strength == BindingStrength.REQUIRED || strength == BindingStrength.EXTENSIBLE) {
// return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
// } else {
// return org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.INFORMATION;
// }
// }
/**
* For each issue in the validationResult, determine first if it should be ignored, and if not, calculate its severity
* and add it to the errors list.
*
* @param errors a list of validation messages that are errors
* @param validationResult the validation result containing terminology issues to be evaluated
* @param element the element being validated
* @param path the path of the element being validated
* @param ignoreCantInfer if true, the element will be ignored
* @param vsurl the value set url
* @param bindingStrength the binding strength of the code/codeable concept
* @return true if no errors were added to the errors list, false otherwise
*/
private boolean calculateSeverityForTxIssuesAndUpdateErrors(List errors, ValidationResult validationResult, Element element, String path, boolean ignoreCantInfer, String vsurl, BindingStrength bindingStrength) {
if (validationResult == null) {
return true;
}
boolean noErrorsFound = true;
for (OperationOutcomeIssueComponent issue : validationResult.getIssues()) {
if (!isIgnoredTxIssueType(issue, ignoreCantInfer)) {
OperationOutcomeIssueComponent issueWithCalculatedSeverity = getTxIssueWithCalculatedSeverity(issue, bindingStrength);
var validationMessage = buildValidationMessage(validationResult.getTxLink(), element.line(), element.col(), path, issueWithCalculatedSeverity);
if (!isSuppressedValidationMessage(path, validationMessage.getMessageId())) {
errors.add(validationMessage);
if (validationMessage.isError()) {
noErrorsFound = false;
}
}
}
}
return noErrorsFound;
}
/**
* Check if the issueComponent should not be included when evaluating if terminology issues contain errors.
*
* The following tx-issue-types are ignored:
*
* - not-in-vs
* - this-code-not-in-vs
* - cannot-infer
*
* Note that cannot-infer is only ignored if ignoreCantInfer is true.
*
* @param issueComponent the issue
* @param ignoreCantInfer if true, ignore
* @return true if the issue should be ignored when evaluating if terminology issues contain errors, false otherwise
*/
private boolean isIgnoredTxIssueType(OperationOutcomeIssueComponent issueComponent, boolean ignoreCantInfer) {
if (issueComponent.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-in-vs")) {
return true;
}
if (issueComponent.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "this-code-not-in-vs")) {
return true;
}
if (issueComponent.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "cannot-infer") || ignoreCantInfer) {
return true;
}
return false;
}
/**
* Derive a new issue from the passed issueComponent with appropriate severity.
*
* If the issueComponent has a tx-issue-type of "not-found", the returned value will have an appropriate severity
* based on bindingStrength. The issueComponent text will also be updated to include the reason for error level
* severity.
*
* If the issueComponent has a tx-issue-type of "invalid-display" the returned value will have an appropriate severity
* based on displayWarningMode.
*
* All other issues will be returned unchanged.
* @param issueComponent the issue component
* @param bindingStrength the binding strength of the code/codeable concept
* @return the new issue component with appropriate severity
*/
private OperationOutcomeIssueComponent getTxIssueWithCalculatedSeverity(OperationOutcomeIssueComponent issueComponent, BindingStrength bindingStrength) {
OperationOutcomeIssueComponent newIssueComponent = issueComponent.copy();
if (newIssueComponent.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "not-found")) {
String text = issueComponent.getDetails().getText();
boolean isHL7 = text == null ? false : text.contains("http://hl7.org/fhir") || text.contains("http://terminology.hl7.org");
org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity notFoundLevel = null;
String notFoundNote = null;
if (bindingStrength == null) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding=null";
} else if (bindingStrength == BindingStrength.REQUIRED && isHL7) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
notFoundNote = "error because this is a required binding to an HL7 code system";
} else if (bindingStrength == BindingStrength.REQUIRED && unknownCodeSystemsCauseErrors) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR;
notFoundNote = "error because this is a required binding";
} else if (bindingStrength == BindingStrength.REQUIRED) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding=required";
} else if (bindingStrength == BindingStrength.EXTENSIBLE) {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding=extensible";
} else {
notFoundLevel = org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING;
notFoundNote = null; // "binding="+bs.toCode();
}
if (notFoundLevel != null && newIssueComponent.getSeverity().isHigherThan(notFoundLevel)) { // && (vsurl != null && i.getDetails().getText().contains(vsurl))) {
newIssueComponent.setSeverity(notFoundLevel);
if (notFoundNote != null) {
newIssueComponent.getDetails().setText(newIssueComponent.getDetails().getText() + " (" + notFoundNote + ")");
}
}
}
if (newIssueComponent.getDetails().hasCoding("http://hl7.org/fhir/tools/CodeSystem/tx-issue-type", "invalid-display") && baseOptions.isDisplayWarningMode() && newIssueComponent.getSeverity() == org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.ERROR) {
newIssueComponent.setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.WARNING);
}
return newIssueComponent;
}
public boolean checkBindings(List errors, String path, Element element, NodeStack stack, ValueSet valueset, Coding nextCoding) {
boolean ok = true;
if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem(), baseOptions.getFhirVersion())) {
ValidationResult vr = checkCodeOnServer(stack, valueset, nextCoding);
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, null, null) && ok;
if (vr.getSeverity() != null && !vr.messageIsInIssues()) {
if (vr.getSeverity() == IssueSeverity.INFORMATION) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
} else if (vr.getSeverity() == IssueSeverity.WARNING) {
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
} else {
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()) && ok;
}
}
}
return ok;
}
private boolean checkCDACodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, StructureDefinition logical) {
BooleanHolder ok = new BooleanHolder(true);
if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
try {
CodeableConcept cc = new CodeableConcept();
ok.see(convertCDACodeToCodeableConcept(errors, path, element, logical, cc));
if (cc.hasText() && cc.hasCoding()) {
for (Coding c : cc.getCoding()) {
recordCodeTextCombo(stack, theElementCntext.getBase().getPath(), c, cc.getText());
}
}
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) {
if (binding.hasValueSet()) {
String vsRef = binding.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = binding.getStrength();
Extension vsMax = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET);
validateBindingCodeableConcept(errors, path, element, profile, stack, ok, false, new BooleanHolder(), cc, vsRef, valueset, strength, vsMax, true, null);
// special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding.
if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) {
ok.see(checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical));
}
// } else if (binding.hasValueSet()) {
// hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
} else if (!noBindingMsgSuppressed) {
hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
}
for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) {
StringBuilder b = new StringBuilder();
if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) {
String vsRef = ab.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = convertPurposeToStrength(ab.getPurpose());
validateBindingCodeableConcept(errors, path, element, profile, stack, ok, false, new BooleanHolder(), cc, vsRef, valueset, strength, null, false, b.toString());
}
}
}
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getCause().getMessage());
}
}
return ok.ok();
}
private boolean checkTerminologyCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) {
boolean ok = false;
Coding c = convertToCoding(element, logical);
String code = c.getCode();
String system = c.getSystem();
String display = c.getDisplay();
String version = c.getVersion();
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isCodeSystemReferenceValid(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE) && ok;
if (system != null && code != null && !noTerminologyChecks) {
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system) && ok;
try {
if (checkCode(errors, element, path, code, system, version, display, checkDisplay, stack)) {
if (theElementCntext != null && theElementCntext.hasBinding()) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) {
if (binding.hasValueSet()) {
String vsRef = binding.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = binding.getStrength();
Extension vsMax = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET);
ok = validateBindingTerminologyCoding(errors, path, element, profile, stack, ok, c, code, system, display, vsRef, valueset, strength, vsMax, true, null);
} else if (binding.hasValueSet()) {
hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
} else if (!inCodeableConcept && !noBindingMsgSuppressed) {
hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
}
for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) {
StringBuilder b = new StringBuilder();
if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) {
String vsRef = ab.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = convertPurposeToStrength(ab.getPurpose());
ok = validateBindingTerminologyCoding(errors, path, element, profile, stack, ok, c, code, system, display, vsRef, valueset, strength, null, true, b.toString()) && ok;
}
}
}
}
} else {
ok = false;
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING2, e.getMessage(), e.toString());
ok = false;
}
}
return ok;
}
private boolean validateBindingTerminologyCoding(List errors, String path, Element element,
StructureDefinition profile, NodeStack stack, boolean ok, Coding c, String code, String system, String display,
String vsRef, ValueSet valueset, BindingStrength strength, Extension vsMax, boolean base, String usageNote) {
if (valueset == null) {
CodeSystem cs = context.fetchCodeSystem(vsRef);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(vsRef))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(vsRef));
} else {
ok = false;
}
} else {
BindingContext bc = base ? BindingContext.BASE : BindingContext.ADDITIONAL;
try {
long t = System.nanoTime();
ValidationResult vr = null;
if (strength != BindingStrength.EXAMPLE) {
vr = checkCodeOnServer(stack, valueset, c);
}
if (strength == BindingStrength.REQUIRED) {
removeTrackedMessagesForLocation(errors, element, path);
}
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, vsRef, strength) && ok;
timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'");
if (vr != null && !vr.isOk()) {
if (vr.IsNoService())
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER, system+"#"+code);
else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
if (strength == BindingStrength.REQUIRED)
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(vsRef, valueset, bc, usageNote), vr.getMessage(), system+"#"+code);
else if (strength == BindingStrength.EXTENSIBLE) {
if (vsMax != null)
checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack);
else if (!noExtensibleWarnings)
txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(vsRef, valueset, bc, usageNote));
} else if (strength == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(vsRef, valueset, bc, usageNote));
}
}
} else if (strength == BindingStrength.REQUIRED)
ok= txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_4, describeReference(vsRef, valueset, bc, usageNote), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code) && ok;
else if (strength == BindingStrength.EXTENSIBLE) {
if (vsMax != null)
ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack) && ok;
else
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_5, describeReference(vsRef, valueset, bc, usageNote), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code);
} else if (strength == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_6, describeReference(vsRef, valueset, bc, usageNote), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code);
}
}
} else if (vr != null && vr.getMessage() != null){
if (vr.getSeverity() == IssueSeverity.INFORMATION) {
txHint(errors, "2023-07-04", vr.getTxLink(), IssueType.INFORMATIONAL, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_HINT, code, vr.getMessage());
} else {
txWarning(errors, "2023-07-04", vr.getTxLink(), IssueType.INFORMATIONAL, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_WARNING, code, vr.getMessage());
}
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING1, e.getMessage());
}
}
return ok;
}
private boolean convertCDACodeToCodeableConcept(List errors, String path, Element element, StructureDefinition logical, CodeableConcept cc) {
boolean ok = true;
cc.setText(element.getNamedChildValue("originalText", false));
if (element.hasChild("nullFlavor", false)) {
cc.addExtension("http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new CodeType(element.getNamedChildValue("nullFlavor", false)));
}
if (element.hasChild("code", false) || element.hasChild("codeSystem", false)) {
Coding c = cc.addCoding();
String oid = element.getNamedChildValue("codeSystem", false);
if (oid != null) {
c.setSystem("urn:oid:"+oid);
OIDSummary urls = context.urlsForOid(oid, "CodeSystem");
if (urls.urlCount() == 1) {
c.setSystem(urls.getUrl());
} else if (urls.urlCount() == 0) {
warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_UNKNOWN_OID, oid);
} else {
String prefUrl = urls.chooseBestUrl();
if (prefUrl == null) {
rule(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES, oid, urls.describe());
ok = false;
} else {
c.setSystem(prefUrl);
warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES_CHOSEN, oid, prefUrl, urls.describe());
}
}
} else {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
}
c.setCode(element.getNamedChildValue("code", false));
c.setVersion(element.getNamedChildValue("codeSystemVersion", false));
c.setDisplay(element.getNamedChildValue("displayName", false));
}
// todo: translations
return ok;
}
private Coding convertToCoding(Element element, StructureDefinition logical) {
Coding res = new Coding();
for (ElementDefinition ed : logical.getSnapshot().getElement()) {
if (Utilities.charCount(ed.getPath(), '.') == 1) {
List maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
for (String m : maps) {
String name = tail(ed.getPath());
List list = new ArrayList<>();
element.getNamedChildren(name, list);
if (!list.isEmpty()) {
if ("Coding.code".equals(m)) {
res.setCode(list.get(0).primitiveValue());
} else if ("Coding.system[fmt:OID]".equals(m)) {
String oid = list.get(0).primitiveValue();
String url = new ContextUtilities(context).oid2Uri(oid);
if (url != null) {
res.setSystem(url);
} else {
res.setSystem("urn:oid:" + oid);
}
} else if ("Coding.version".equals(m)) {
res.setVersion(list.get(0).primitiveValue());
} else if ("Coding.display".equals(m)) {
res.setDisplay(list.get(0).primitiveValue());
}
}
}
}
}
return res;
}
private boolean checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) {
boolean ok = true;
ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile);
if (valueset == null) {
CodeSystem cs = context.fetchCodeSystem(maxVSUrl);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl));
} else {
ok = false;
}
} else {
try {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, valueset, cc);
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, maxVSUrl, null) && ok;
timeTracker.tx(t, "vc "+cc.toString());
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_7, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), vr.getMessage());
else
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), ccSummary(cc)) && ok;
}
} catch (CheckCodeOnServerException e) {
if (STACK_TRACE) e.getCause().printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getCause().getMessage());
}
}
return ok;
}
// private String describeValueSet(String url) {
// ValueSet vs = context.findTxResource(ValueSet.class, url);
// if (vs != null) {
// return "'"+vs.present()+"' ("+url+")";
// } else {
// return "("+url+")";
// }
// }
private boolean checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, Coding c, NodeStack stack) {
boolean ok = true;
ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile);
if (valueset == null) {
CodeSystem cs = context.fetchCodeSystem(maxVSUrl);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl));
} else {
ok = false;
}
} else {
try {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, valueset, c);
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, maxVSUrl, null) && ok;
timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), vr.getMessage(), c.getSystem()+"#"+c.getCode());
else
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_10, describeReference(maxVSUrl, valueset, BindingContext.MAXVS, null), c.getSystem(), c.getCode()) && ok;
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage());
}
}
return ok;
}
private boolean checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) {
boolean ok = true;
ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile);
if (valueset == null) {
CodeSystem cs = context.fetchCodeSystem(maxVSUrl);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl));
} else {
ok = false;
}
} else {
try {
long t = System.nanoTime();
ValidationResult vr = checkCodeOnServer(stack, valueset, value, baseOptions);
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, false, maxVSUrl, null) && ok;
timeTracker.tx(t, "vc "+value);
if (!vr.isOk()) {
if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset, BindingContext.BASE, null), vr.getMessage(), value);
else {
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_11, describeReference(maxVSUrl, valueset, BindingContext.BASE, null), vr.getMessage()) && ok;
}
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage());
}
}
return ok;
}
private String ccSummary(CodeableConcept cc) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (Coding c : cc.getCoding())
b.append(c.getSystem() + "#" + c.getCode());
return b.toString();
}
private boolean checkCoding(List errors, String path, Element focus, Coding fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system", false), fixed.getSystemElement(), fixedSource, "system", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".version", focus.getNamedChild("version", false), fixed.getVersionElement(), fixedSource, "version", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".code", focus.getNamedChild("code", false), fixed.getCodeElement(), fixedSource, "code", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display", false), fixed.getDisplayElement(), fixedSource, "display", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected", false), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern, context) && ok;
return ok;
}
private boolean checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) {
if (!inCodeableConcept || theElementCntext.hasBinding()) {
String code = element.getNamedChildValue("code", false);
String system = element.getNamedChildValue("system", false);
if (code != null && system == null) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
}
String version = element.getNamedChildValue("version", false);
String display = element.getNamedChildValue("display", false);
return checkCodedElement(errors, path, element, profile, theElementCntext, inCodeableConcept, checkDisplay, stack, code, system, version, display);
} else {
return true;
}
}
private boolean checkCodedElement(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack,
String theCode, String theSystem, String theVersion, String theDisplay) {
boolean ok = true;
BooleanHolder checked = new BooleanHolder(false);
if (theSystem != null && theCode != null && !noTerminologyChecks) {
try {
if (theElementCntext != null && theElementCntext.hasBinding()) {
ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) {
try {
Coding c = ObjectConverter.readAsCoding(element);
if (binding.hasValueSet()) {
String vsRef = binding.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = binding.getStrength();
Extension vsMax = binding.getExtensionByUrl(ToolingExtensions.EXT_MAX_VALUESET);
ok = validateBindingCodedElement(errors, path, element, profile, stack, theCode, theSystem, ok, checked, c, vsRef, valueset, strength, vsMax, true, null);
// } else if (binding.hasValueSet()) {
// hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
} else if (!inCodeableConcept && !noBindingMsgSuppressed) {
hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
}
for (ElementDefinitionBindingAdditionalComponent ab : binding.getAdditional()) {
StringBuilder b = new StringBuilder();
if (isTestableBinding(ab) && isInScope(ab, profile, getResource(stack), b)) {
String vsRef = ab.getValueSet();
ValueSet valueset = resolveBindingReference(profile, vsRef, profile.getUrl(), profile);
BindingStrength strength = convertPurposeToStrength(ab.getPurpose());
ok = validateBindingCodedElement(errors, path, element, profile, stack, theCode, theSystem, ok, checked, c, vsRef, valueset, strength, null, false, b.toString()) && ok;
}
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING1, e.getMessage());
}
}
}
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING2, e.getMessage(), e.toString());
ok = false;
}
if (!checked.ok()) {
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, theSystem == null || isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, theSystem) && ok;
if (ok) {
ok = checkCode(errors, element, path, theCode, theSystem, theVersion, theDisplay, checkDisplay, stack);
}
}
}
return ok;
}
private Element getResource(NodeStack stack) {
if (stack.getElement().isResource()) {
return stack.getElement();
}
if (stack.getParent() == null) {
return null;
}
return getResource(stack.getParent());
}
private boolean validateBindingCodedElement(List errors, String path, Element element,
StructureDefinition profile, NodeStack stack, String theCode, String theSystem, boolean ok, BooleanHolder checked,
Coding c, String vsRef, ValueSet valueset, BindingStrength strength, Extension vsMax, boolean base, String usageNote) {
if (valueset == null) {
CodeSystem cs = context.fetchCodeSystem(vsRef);
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(vsRef))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(vsRef));
} else {
ok = false;
}
} else {
BindingContext bc = base ? BindingContext.BASE : BindingContext.ADDITIONAL;
long t = System.nanoTime();
ValidationResult vr = null;
if (strength != BindingStrength.EXAMPLE) {
checked.set(true);
vr = checkCodeOnServer(stack, valueset, c);
}
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, strength == BindingStrength.EXTENSIBLE, vsRef, strength) && ok;
timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
if (strength == BindingStrength.REQUIRED) {
removeTrackedMessagesForLocation(errors, element, path);
}
if (vr != null && !vr.isOk()) {
if (vr.IsNoService())
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER, theSystem+"#"+theCode);
else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
if (strength == BindingStrength.REQUIRED)
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(vsRef, valueset, bc, usageNote), vr.getMessage(), theSystem+"#"+theCode) && ok;
else if (strength == BindingStrength.EXTENSIBLE) {
if (vsMax != null)
checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack);
else if (!noExtensibleWarnings)
txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(vsRef, valueset, bc, usageNote), theSystem+"#"+theCode);
} else if (strength == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(vsRef, valueset, bc, usageNote), theSystem+"#"+theCode);
}
}
} else if (strength == BindingStrength.REQUIRED)
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_12, describeReference(vsRef, valueset, bc, usageNote), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode) && ok;
else if (strength == BindingStrength.EXTENSIBLE) {
if (vsMax != null)
ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringFromExtension(vsMax), c, stack) && ok;
else if (!noExtensibleWarnings) {
txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_13, describeReference(vsRef, valueset, bc, usageNote), getErrorMessage(vr.getMessage()), c.getSystem()+"#"+c.getCode());
}
} else if (strength == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(vsRef, valueset, bc, usageNote), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode);
}
}
} else if (vr != null && vr.getMessage() != null) {
if (vr.getSeverity() == IssueSeverity.INFORMATION) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
} else {
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
}
}
}
return ok;
}
private boolean isValueSet(String url) {
try {
ValueSet vs = context.fetchResourceWithException(ValueSet.class, url);
return vs != null;
} catch (Exception e) {
return false;
}
}
private boolean checkContactPoint(List errors, String path, Element focus, ContactPoint fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system", false), fixed.getSystemElement(), fixedSource, "system", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value", false), fixed.getValueElement(), fixedSource, "value", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use", false), fixed.getUseElement(), fixedSource, "use", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period", false), fixed.getPeriod(), fixedSource, "period", focus, pattern, context) && ok;
return ok;
}
private boolean checkExtension(ValidationContext valContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException {
boolean ok = true;
String url = element.getNamedChildValue("url", false);
String u = url.contains("|") ? url.substring(0, url.indexOf("|")) : url;
boolean isModifier = element.getName().equals("modifierExtension");
assert def.getIsModifier() == isModifier;
long t = System.nanoTime();
StructureDefinition ex = Utilities.isAbsoluteUrl(u) ? context.fetchResource(StructureDefinition.class, u) : null;
if (ex == null) {
ex = getXverExt(errors, path, element, url);
}
if (url.contains("|")) {
if (ex == null) {
ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
if (ex == null) {
warning(errors, "2022-12-17", IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_NO_MATCH);
} else {
ok = rule(errors, "2022-12-17", IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_IGNORE) && ok;
}
} else {
if (url.equals(ex.getUrl())) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_MISLEADING);
} else if (url.equals(ex.getVersionedUrl())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_NOT_ALLOWED) && ok;
} else { //
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXT_VER_URL_REVERSION, ex.getVersion()) && ok;
}
}
}
timeTracker.sd(t);
if (ex == null) {
if (extensionUrl != null && !isAbsolute(url)) {
if (extensionUrl.equals(profile.getUrl())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), I18nConstants.EXTENSION_EXT_SUBEXTENSION_INVALID, url, profile.getVersionedUrl()) && ok;
}
} else if (SpecialExtensions.isKnownExtension(url)) {
ex = SpecialExtensions.getDefinition(url);
} else if (BuildExtensions.allConsts().contains(url)) {
// nothing
} else if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN_NOTHERE, url)) {
hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN, url);
} else {
ok = false;
}
}
if (ex != null) {
trackUsage(ex, valContext, element);
// check internal definitions are coherent
if (isModifier) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN) && ok;
}
// 1. can this extension be used here?
ok = checkExtensionContext(valContext.getAppContext(), errors, resource, container, ex, containerStack, valContext, isModifier) && ok;
ok = checkDefinitionStatus(errors, element, path, ex, profile, context.formatMessage(I18nConstants.MSG_DEPENDS_ON_EXTENSION)) && ok;
if (isModifier)
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_Y, url) && ok;
else
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_N, url) && ok;
// check the type of the extension:
Set allowedTypes = listExtensionTypes(ex);
String actualType = getExtensionType(element);
if (actualType != null)
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), I18nConstants.EXTENSION_EXT_TYPE, url, allowedTypes.toString(), actualType) && ok;
else if (element.hasChildren("extension"))
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_WRONG, url) && ok;
else
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_ABSENT, url) && ok;
// 3. is the content of the extension valid?
ok = validateElement(valContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url, pct, mode) && ok;
}
return ok;
}
private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) {
for (ElementDefinition ed : profile.getSnapshot().getElement()) {
if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) {
return true;
}
}
return false;
}
private String getExtensionType(Element element) {
for (Element e : element.getChildren()) {
if (e.getName().startsWith("value")) {
String tn = e.getName().substring(5);
String ltn = Utilities.uncapitalize(tn);
if (isPrimitiveType(ltn))
return ltn;
else
return tn;
}
}
return null;
}
private Set listExtensionTypes(StructureDefinition ex) {
ElementDefinition vd = null;
for (ElementDefinition ed : ex.getSnapshot().getElement()) {
if (ed.getPath().startsWith("Extension.value")) {
vd = ed;
break;
}
}
Set res = new HashSet();
if (vd != null && !"0".equals(vd.getMax())) {
for (TypeRefComponent tr : vd.getType()) {
res.add(tr.getWorkingCode());
}
}
// special hacks
if (ex.getUrl().equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type")) {
res.add("uri");
res.add("url");
}
return res;
}
private boolean checkExtensionContext(Object appContext, List errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidationContext valContext, boolean modifier) {
String extUrl = definition.getUrl();
boolean ok = false;
CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder();
List plist = new ArrayList<>();
plist.add(stripIndexes(stripRefs(stack.getLiteralPath())));
for (String s : stack.getLogicalPaths()) {
String p = stripIndexes(s);
// all extensions are always allowed in ElementDefinition.example.value, and in fixed and pattern values. TODO: determine the logical paths from the path stated in the element definition....
if (EXTENSION_CONTEXT_LIST.contains(p)) {
return true;
}
plist.add(p);
}
Collections.sort(plist); // logical paths are a set, but we want a predictable order for error messages
if (definition.hasExtension(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE)) {
Extension ext = definition.getExtensionByUrl(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE);
if (ext.hasExtension(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_START)) {
String v = ToolingExtensions.readStringExtension(ext, ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_START);
ok = rule(errors, "2025-01-07", IssueType.BUSINESSRULE, container.line(), container.col(), stack.getLiteralPath(),
VersionUtilities.compareVersions(VersionUtilities.getMajMin(context.getVersion()), v) >= 0,
I18nConstants.EXTENSION_FHIR_VERSION_EARLIEST, extUrl, VersionUtilities.getNameForVersion(v), v, VersionUtilities.getNameForVersion(context.getVersion()), context.getVersion()) && ok;
}
if (ext.hasExtension(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_END)) {
String v = ToolingExtensions.readStringExtension(ext, ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_END);
ok = rule(errors, "2025-01-07", IssueType.BUSINESSRULE, container.line(), container.col(), stack.getLiteralPath(),
VersionUtilities.compareVersions(VersionUtilities.getMajMin(context.getVersion()), v) <= 0,
I18nConstants.EXTENSION_FHIR_VERSION_LATEST, extUrl, VersionUtilities.getNameForVersion(v), v, VersionUtilities.getNameForVersion(context.getVersion()), context.getVersion()) && ok;
}
}
boolean vv = false;
for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) {
if (ok) {
break;
}
boolean cok = true;
if (ctxt.hasExtension(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE)) {
Extension ext = ctxt.getExtensionByUrl(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE);
vv = true;
if (ext.hasExtension(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_START)) {
String v = ToolingExtensions.readStringExtension(ext, ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_START);
cok = VersionUtilities.compareVersions(VersionUtilities.getMajMin(context.getVersion()), v) >= 0 && cok;
}
if (ext.hasExtension(ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_END)) {
String v = ToolingExtensions.readStringExtension(ext, ToolingExtensions.EXT_FHIRVERSION_SPECIFIC_USE_END);
cok = VersionUtilities.compareVersions(VersionUtilities.getMajMin(context.getVersion()), v) <= 0 && cok;
}
}
if (cok) {
if (ctxt.getType() == ExtensionContextType.ELEMENT) {
String en = ctxt.getExpression();
String pu = null;
if (en == null) {
// nothing? It's an error in the extension definition, but that's properly reported elsewhere
} else {
contexts.append("e:" + en);
if (en.contains("#")) {
pu = en.substring(0, en.indexOf("#"));
en = en.substring(en.indexOf("#")+1);
} else {
//pu = en;
}
if (Utilities.existsInList(en, "Element", "Any")) {
ok = true;
} else if (en.equals("Resource") && container.isResource()) {
ok = true;
} else if (en.equals("CanonicalResource") && containsAny(VersionUtilities.getExtendedCanonicalResourceNames(context.getVersion()), plist)) {
ok = true;
} else if (hasElementName(plist, en) && pu == null) {
ok = true;
}
}
if (!ok) {
if (checkConformsToProfile(appContext, errors, resource, container, stack, extUrl, ctxt.getExpression(), pu)) {
for (String p : plist) {
if (ok) {
break;
}
if (p.equals(en)) {
ok = true;
} else {
String pn = p;
String pt = "";
if (p.contains(".")) {
pn = p.substring(0, p.indexOf("."));
pt = p.substring(p.indexOf("."));
}
StructureDefinition sd = context.fetchTypeDefinition(pn);
while (sd != null) {
if ((sd.getType() + pt).equals(en)) {
ok = true;
break;
}
if (sd.getBaseDefinition() != null) {
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
} else {
sd = null;
}
}
}
}
}
}
} else if (ctxt.getType() == ExtensionContextType.EXTENSION) {
contexts.append("x:" + ctxt.getExpression());
String ext = null;
if (stack.getElement().getName().startsWith("value")) {
NodeStack estack = stack.getParent();
if (estack != null && estack.getElement().fhirType().equals("Extension")) {
ext = estack.getElement().getNamedChildValue("url", false);
}
} else {
ext = stack.getElement().getNamedChildValue("url", false);
}
if (ctxt.getExpression().equals(ext)) {
ok = true;
} else if (ext != null) {
plist.add(ext);
}
} else if (ctxt.getType() == ExtensionContextType.FHIRPATH) {
contexts.append("p:" + ctxt.getExpression());
// The context is all elements that match the FHIRPath query found in the expression.
List res = fpe.evaluate(valContext, resource, valContext.getRootResource(), resource, fpe.parse(ctxt.getExpression()));
if (res.contains(container)) {
ok = true;
}
} else {
throw new Error(context.formatMessage(I18nConstants.UNRECOGNISED_EXTENSION_CONTEXT_, ctxt.getTypeElement().asStringValue()));
}
}
}
if (!ok) {
if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_XVER : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString(), definition.getUserString(XVerExtensionManager.XVER_VER_MARKER));
} else if (vv) {
rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_VER : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_VER, extUrl, contexts.toString(), plist.toString(), definition.getUserString(XVerExtensionManager.XVER_VER_MARKER), context.getVersion());
} else {
rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString(), definition.getUserString(XVerExtensionManager.XVER_VER_MARKER));
}
return false;
} else {
if (definition.hasContextInvariant()) {
for (StringType s : definition.getContextInvariant()) {
if (!fpe.evaluateToBoolean(valContext, resource, valContext.getRootResource(), container, fpe.parse(s.getValue()))) {
if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.PROFILE_EXT_NOT_HERE, extUrl, s.getValue());
return true;
} else {
rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.PROFILE_EXT_NOT_HERE, extUrl, s.getValue());
return false;
}
}
}
}
return true;
}
}
private String stripRefs(String literalPath) {
if (literalPath.contains(".resolve().ofType(")) {
String s = literalPath.substring(literalPath.lastIndexOf(".resolve().")+18);
int i = s.indexOf(")");
s = s.substring(0, i)+s.substring(i+1);
return s;
} else {
return literalPath;
}
}
private boolean containsAny(Set set, List list) {
for (String p : list) {
if (set.contains(p)) {
return true;
}
}
return false;
}
private boolean hasElementName(List plist, String en) {
String[] ep = en.split("\\.");
for (String s : plist) {
if (s.equals(en)) {
return true;
}
String[] sp = s.split("\\.");
int si = 0;
int ei = 0;
boolean mismatch = false;
while (si < sp.length && ei < ep.length) {
var ps = sp[si];
var pe = ep[ei];
if (!ps.equals(pe)) {
if (pe.endsWith("[x]")) {
if (ps.equals(pe.substring(0, pe.length()-3)) && si < sp.length - 1 && sp[si+1].startsWith("ofType(")) {
si++;
} else {
mismatch = true;
}
} else {
mismatch = true;
}
}
si++;
ei++;
}
if (!mismatch && si == sp.length && ei == ep.length) {
return true;
}
}
return false;
}
private boolean checkConformsToProfile(Object appContext, List errors, Element resource, Element container, NodeStack stack, String extUrl, String expression, String pu) {
if (pu == null) {
return true;
}
if (pu.equals("http://hl7.org/fhir/StructureDefinition/"+resource.fhirType())) {
return true;
}
StructureDefinition sd = context.fetchResource(StructureDefinition.class, pu);
if (!rule(errors, "2023-07-03", IssueType.UNKNOWN, container.line(), container.col(), stack.getLiteralPath(), sd != null,I18nConstants.EXTENSION_CONTEXT_UNABLE_TO_FIND_PROFILE, extUrl, expression)) {
return false;
} else if (sd.getType().equals(resource.fhirType())) {
List valerrors = new ArrayList();
ValidationMode mode = new ValidationMode(ValidationReason.Expression, ProfileSource.FromExpression);
validateResource(new ValidationContext(appContext, resource), valerrors, resource, resource, sd, IdStatus.OPTIONAL, new NodeStack(context, null, resource, validationLanguage), null, mode, false, false);
boolean ok = true;
List record = new ArrayList<>();
for (ValidationMessage v : valerrors) {
ok = ok && !v.getLevel().isError();
if (v.getLevel().isError() || v.isSlicingHint()) {
record.add(v);
}
}
return ok;
} else {
warning(errors, "2023-07-03", IssueType.UNKNOWN, container.line(), container.col(), stack.getLiteralPath(), false,
I18nConstants.EXTENSION_CONTEXT_UNABLE_TO_CHECK_PROFILE, extUrl, expression, pu);
return true;
}
}
private List fixContexts(String extUrl, List list) {
List res = new ArrayList<>();
for (StructureDefinitionContextComponent ctxt : list) {
res.add(ctxt.copy());
}
if (ToolingExtensions.EXT_FHIR_TYPE.equals(extUrl)) {
list.get(0).setExpression("ElementDefinition.type");
}
// the history of this is a mess - see https://jira.hl7.org/browse/FHIR-13328
// we in practice we will support it in either place, but the specification says on ElementDefinition, not on ElementDefinition.type
// but this creates validation errors people can't fix all over the place if we don't do this.
if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) {
StructureDefinitionContextComponent e = new StructureDefinitionContextComponent();
e.setExpression("ElementDefinition.type");
e.setType(ExtensionContextType.ELEMENT);
list.add(e);
}
if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version".equals(extUrl)) {
list.get(0).setExpression("Element"); // well, it can't be used anywhere but the list of places it can be used is quite long
}
if (!VersionUtilities.isThisOrLater("4.6", context.getVersion())) {
if (Utilities.existsInList(extUrl, "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", "http://hl7.org/fhir/StructureDefinition/capabilitystatement-prohibited")) {
list.get(0).setExpression("Element"); // well, they can't be used anywhere but the list of places they can be used is quite long
}
}
return list;
}
private String stripIndexes(String path) {
boolean skip = false;
StringBuilder b = new StringBuilder();
for (char c : path.toCharArray()) {
if (skip) {
if (c == ']') {
skip = false;
}
} else if (c == '[') {
skip = true;
} else {
b.append(c);
}
}
return b.toString();
}
@SuppressWarnings("rawtypes")
public boolean checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent, boolean pattern, String context) {
boolean ok = true;
if ((fixed == null || fixed.isEmpty()) && focus == null) {
; // this is all good
} else if ((fixed == null || fixed.isEmpty()) && focus != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, pattern, I18nConstants.PROFILE_VAL_NOTALLOWED, focus.getName(), (pattern ? "pattern" : "fixed value"));
} else if (fixed != null && !fixed.isEmpty() && focus == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, I18nConstants.PROFILE_VAL_MISSINGELEMENT, propName, fixedSource);
} else {
String value = focus.primitiveValue();
if (fixed instanceof org.hl7.fhir.r5.model.BooleanType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.InstantType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.InstantType) fixed).getValue().toString(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.InstantType) fixed).asStringValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.CodeType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.CodeType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.CodeType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.StringType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.StringType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.StringType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.UriType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UriType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.UriType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.DateType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateType) fixed).getValue().toString(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.DateType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue().toString(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.OidType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.OidType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.OidType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.UuidType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UuidType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.UuidType) fixed).getValue(), context);
else if (fixed instanceof org.hl7.fhir.r5.model.IdType)
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IdType) fixed).getValue(), value), I18nConstants._DT_FIXED_WRONG, value, ((org.hl7.fhir.r5.model.IdType) fixed).getValue(), context);
else if (fixed instanceof Quantity)
ok = checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern, context) && ok;
else if (fixed instanceof Address)
ok = checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern, context);
else if (fixed instanceof ContactPoint)
ok = checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern, context);
else if (fixed instanceof Attachment)
ok = checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern, context);
else if (fixed instanceof Identifier)
ok = checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern, context);
else if (fixed instanceof Coding)
ok = checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern, context);
else if (fixed instanceof HumanName)
ok = checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern, context);
else if (fixed instanceof CodeableConcept)
ok = checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern, context);
else if (fixed instanceof Timing)
ok = checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern, context);
else if (fixed instanceof Period)
ok = checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern, context);
else if (fixed instanceof Range)
ok = checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern, context);
else if (fixed instanceof Ratio)
ok = checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern, context);
else if (fixed instanceof SampledData)
ok = checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern, context);
else if (fixed instanceof Reference)
ok = checkReference(errors, path, focus, (Reference) fixed, fixedSource, pattern, context);
else
ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, I18nConstants.INTERNAL_INT_BAD_TYPE, fixed.fhirType());
List extensions = new ArrayList();
focus.getNamedChildren("extension", extensions);
if (fixed.getExtension().size() == 0) {
ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0 || pattern == true, I18nConstants.EXTENSION_EXT_FIXED_BANNED) && ok;
} else if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(), I18nConstants.EXTENSION_EXT_COUNT_MISMATCH, Integer.toString(fixed.getExtension().size()), Integer.toString(extensions.size()))) {
for (Extension e : fixed.getExtension()) {
Element ex = getExtensionByUrl(extensions, e.getUrl());
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) {
ok = checkFixedValue(errors, path, ex.getNamedChild("extension", false).getNamedChild("value", false), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension", false), false, context) && ok;
} else {
ok = false;
}
}
} else {
ok = false;
}
}
return ok;
}
private boolean checkHumanName(List errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use", false), fixed.getUseElement(), fixedSource, "use", focus, pattern, context);
ok = checkFixedValue(errors, path + ".text", focus.getNamedChild("text", false), fixed.getTextElement(), fixedSource, "text", focus, pattern, context);
ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period", false), fixed.getPeriod(), fixedSource, "period", focus, pattern, context);
List parts = new ArrayList();
if (!pattern || fixed.hasFamily()) {
focus.getNamedChildren("family", parts);
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_FAMILY, (fixed.hasFamily() ? "1" : "0"), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
ok = checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern, context) && ok;
} else {
ok = false;
}
}
if (!pattern || fixed.hasGiven()) {
focus.getNamedChildren("given", parts);
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_GIVEN, Integer.toString(fixed.getGiven().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
ok = checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern, context) && ok;
} else {
ok = false;
}
}
if (!pattern || fixed.hasPrefix()) {
focus.getNamedChildren("prefix", parts);
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_PREFIX, Integer.toString(fixed.getPrefix().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
ok = checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern, context) && ok;
} else {
ok = false;
}
}
if (!pattern || fixed.hasSuffix()) {
focus.getNamedChildren("suffix", parts);
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_SUFFIX, Integer.toString(fixed.getSuffix().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
ok = checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern, context) && ok;
} else {
ok = false;
}
}
return ok;
}
private boolean checkIdentifier(List errors, String path, Element element, ElementDefinition context) {
boolean ok = true;
String system = element.getNamedChildValue("system", false);
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM) && ok;
if ("urn:ietf:rfc:3986".equals(system)) {
String value = element.getNamedChildValue("value", false);
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, value == null || isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE, value) && ok;
}
return ok;
}
private boolean checkIdentifier(List errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use", false), fixed.getUseElement(), fixedSource, "use", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".type", focus.getNamedChild(TYPE, false), fixed.getType(), fixedSource, TYPE, focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system", false), fixed.getSystemElement(), fixedSource, "system", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value", false), fixed.getValueElement(), fixedSource, "value", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period", false), fixed.getPeriod(), fixedSource, "period", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner", false), fixed.getAssigner(), fixedSource, "assigner", focus, pattern, context) && ok;
return ok;
}
private boolean checkPeriod(List errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".start", focus.getNamedChild("start", false), fixed.getStartElement(), fixedSource, "start", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".end", focus.getNamedChild("end", false), fixed.getEndElement(), fixedSource, "end", focus, pattern, context) && ok;
return ok;
}
private boolean checkPrimitive(ValidationContext valContext, List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node, NodeStack parentNode, Element resource) throws FHIRException {
boolean ok = true;
// sanity check. The only children allowed are id and extension, but value might slip through in some circumstances.
for (Element child : e.getChildren()) {
ok = rule(errors, "2024-02-28", IssueType.INVALID, child.line(), child.col(), path, !"value".equals(child.getName()), I18nConstants.ILLEGAL_PROPERTY, "value") && ok;
}
if (isBlank(e.primitiveValue())) {
if (e.primitiveValue() == null)
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT) && ok;
else if (e.primitiveValue().length() == 0)
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY) && ok;
else if (Utilities.isAllWhitespace(e.primitiveValue()))
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS);
if (context.hasBinding()) {
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, e.line(), e.col(), path, context.getBinding().getStrength() != BindingStrength.REQUIRED, I18nConstants.Terminology_TX_Code_ValueSet_MISSING) && ok;
}
ok = rule(errors, "2023-06-18", IssueType.INVALID, e.line(), e.col(), path, !context.getMustHaveValue(), I18nConstants.PRIMITIVE_MUSTHAVEVALUE_MESSAGE, context.getId(), profile.getVersionedUrl()) && ok;
if (context.hasValueAlternatives()) {
boolean found = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (CanonicalType ct : context.getValueAlternatives()) {
found = found || e.hasExtension(ct.getValue());
b.append(ct.getValue());
}
ok = rulePlural(errors, "2023-06-18", IssueType.INVALID, e.line(), e.col(), path, found, context.getValueAlternatives().size(), I18nConstants.PRIMITIVE_VALUE_ALTERNATIVES_MESSAGE, context.getId(), profile.getVersionedUrl(), b.toString()) && ok;
}
return ok;
} else {
boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue());
if (hasBiDiControls) {
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, e.line(), e.col(), path, !noUnicodeBiDiControlChars, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_DISALLOWED, UnicodeUtilities.replaceBiDiChars(e.primitiveValue()))) {
String msg = UnicodeUtilities.checkUnicodeWellFormed(e.primitiveValue());
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_MATCH, msg);
} else {
ok = false;
}
}
Set badChars = new HashSet<>();
for (char ch : e.primitiveValue().toCharArray()) {
if (ch < 32 && !(ch == '\r' || ch == '\n' || ch == '\t')) {
// can't get to here with xml - the parser fails if you try
badChars.add(Integer.toHexString(ch));
}
}
warningPlural(errors, "2023-07-26", IssueType.INVALID, e.line(), e.col(), path, badChars.isEmpty(), badChars.size(), I18nConstants.UNICODE_XML_BAD_CHARS, badChars.toString());
}
if (context.hasExtension(ToolingExtensions.EXT_MIN_LENGTH) && e.hasPrimitiveValue()) {
int length = e.primitiveValue().length();
int spec = ToolingExtensions.readIntegerExtension(context, ToolingExtensions.EXT_MIN_LENGTH, 0);
ok = rule(errors, "2024-11-02", IssueType.INVALID, e.line(), e.col(), path, length >= spec, I18nConstants.PRIMITIVE_TOO_SHORT, length, spec) && ok;
}
String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
// there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons
//( see task 13328) it might also be found on one the types
if (regex != null) {
for (TypeRefComponent tr : context.getType()) {
if (tr.hasExtension(ToolingExtensions.EXT_REGEX)) {
regex = tr.getExtensionString(ToolingExtensions.EXT_REGEX);
break;
}
}
}
if (regex != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex) && ok;
}
if (!"xhtml".equals(type)) {
if (securityChecks) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR) && ok;
} else if (!"markdown".equals(type)){
hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING);
}
}
if (type.equals("boolean")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE) && ok;
}
if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
String url = e.primitiveValue();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.equals(Utilities.trimWS(url).replace(" ", ""))
// work around an old invalid example in a core package
|| "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_WS, url) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
if (type.equals("oid")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START) && ok;
}
if (type.equals("uuid")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT) && ok;
}
if (type.equals("canonical")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;
}
if (url != null && url.startsWith("urn:uuid:")) {
String s = url.substring(9);
if (s.contains("#")) {
s = s.substring(0, s.indexOf("#"));
}
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(s), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID, s) && ok;
}
if (url != null && url.startsWith("urn:oid:")) {
String cc = url.substring(8);
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
// OIDs roots shorter than 4 chars are almost never valid for namespaces except for 1.3.x
Utilities.isOid(cc) && ((cc.lastIndexOf('.') >= 4 || cc.startsWith("1.3"))),
I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID, cc) && ok;
}
if (isCanonicalURLElement(e, node)) {
// we get to here if this is a defining canonical URL (e.g. CodeSystem.url)
// the URL must be an IRI if present
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url),
node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;
} else if (!e.getProperty().getDefinition().getPath().equals("Bundle.entry.fullUrl")) { // we don't check fullUrl here; it's not a reference, it's a definition. It'll get checked as part of checking the bundle
ok = validateReference(valContext, errors, path, type, context, e, url) && ok;
}
}
if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) {
// work around an old issue with ElementDefinition.id
if (!context.getPath().equals("ElementDefinition.id")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()) && ok;
}
}
if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY)) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || !Utilities.isAllWhitespace(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS_ALL, prepWSPresentation(e.primitiveValue()))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || Utilities.trimWS(e.primitiveValue()).equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS, prepWSPresentation(e.primitiveValue()));
}
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
} else {
ok = false;
}
} else {
ok = false;
}
}
if (type.equals("dateTime")) {
boolean dok = ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for dateTime") && ok;
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("tz-for-time"))) {
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && dok;
}
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok;
if (dok) {
try {
DateTimeType dt = new DateTimeType(e.primitiveValue());
if (isCoreDefinition(profile) || !context.hasExtension(ToolingExtensions.EXT_DATE_RULES) || ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid")) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("time")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID) && ok;
try {
TimeType dt = new TimeType(e.primitiveValue());
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage());
ok = false;
}
}
if (type.equals("date")) {
boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for date");
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && dok;
if (dok) {
try {
DateType dt = new DateType(e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("base64Binary")) {
String encoded = e.primitiveValue();
if (isNotBlank(encoded)) {
boolean bok = Base64Util.isValidBase64(encoded);
if (!bok) {
String value = encoded.length() < 100 ? encoded : "(snip)";
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value) && ok;
} else {
boolean wsok = !Base64Util.base64HasWhitespace(encoded);
if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR) && ok;
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING);
}
}
if (bok && context.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
int size = Base64Util.countBase64DecodedBytes(encoded);
long def = Long.parseLong(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_SIZE));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def) && ok;
}
}
}
if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_VALID, e.primitiveValue())) {
Integer v = Integer.valueOf(e.getValue());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")) && ok;
if (type.equals("unsignedInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
ok = false;
}
}
if (type.equals("integer64")) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER64_VALID, e.primitiveValue())) {
Long v = Long.valueOf(e.getValue());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueInteger64Type() || !context.getMaxValueInteger64Type().hasValue() || (context.getMaxValueInteger64Type().getValue() >= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_GT, (context.hasMaxValueInteger64Type() ? context.getMaxValueInteger64Type() : "")) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueInteger64Type() || !context.getMinValueInteger64Type().hasValue() || (context.getMinValueInteger64Type().getValue() <= v), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT, (context.hasMinValueInteger64Type() ? context.getMinValueInteger64Type() : "")) && ok;
if (type.equals("unsignedInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
ok = false;
}
}
if (type.equals("decimal")) {
if (e.primitiveValue() != null) {
DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) {
warning(errors, NO_RULE_DATE, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue());
try {
Decimal v = new Decimal(e.getValue());
if (context.hasMaxValueDecimalType() && context.getMaxValueDecimalType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueDecimalType()) && ok;
} else if (context.hasMaxValueIntegerType() && context.getMaxValueIntegerType().hasValue()) {
// users can also provide a max integer type. It's not clear whether that's actually valid, but we'll check for it anyway
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMaxValue(v, new BigDecimal(context.getMaxValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, context.getMaxValueIntegerType()) && ok;
}
if (context.hasMinValueDecimalType() && context.getMinValueDecimalType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, context.getMinValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMinValueDecimalType()) && ok;
} else if (context.hasMinValueIntegerType() && context.getMinValueIntegerType().hasValue()) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, new BigDecimal(context.getMinValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMinValueIntegerType()) && ok;
}
} catch (Exception ex) {
// should never happen?
}
} else {
ok = false;
}
}
if (context.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0;
int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_DECIMALS));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def) && ok;
}
}
if (type.equals("instant")) {
boolean dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX, "'"+e.primitiveValue()+"' doesn't meet format requirements for instant)");
if (dok) {
try {
InstantType dt = new InstantType(e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage());
dok = false;
}
}
ok = ok && dok;
}
if (type.equals("code") && e.primitiveValue() != null) {
// Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace
// other than single spaces in the contents
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CODE_WS, e.primitiveValue()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
}
if (context.hasBinding() && e.primitiveValue() != null) {
// special cases
if ("StructureDefinition.type".equals(context.getPath()) && "http://hl7.org/fhir/StructureDefinition/StructureDefinition".equals(profile.getUrl())) {
ok = checkTypeValue(errors, path, e, parentNode.getElement());
} else {
ok = checkPrimitiveBinding(valContext, errors, path, type, context, e, profile, node) && ok;
}
}
if (type.equals("markdown") && htmlInMarkdownCheck != HtmlInMarkdownCheck.NONE) {
String raw = e.primitiveValue();
String processed = MarkDownProcessor.preProcess(raw);
if (!raw.equals(processed)) {
int i = 0;
while (i < raw.length() && raw.charAt(1) == processed.charAt(i)) {
i++;
}
if (i < raw.length()-1 ) {
if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw.subSequence(i, i+2))) {
ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok;
}
} else {
if (!warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw)) {
ok = (htmlInMarkdownCheck != HtmlInMarkdownCheck.ERROR) && ok;
}
}
}
}
if (type.equals("xhtml")) {
XhtmlNode xhtml = e.getXhtml();
if (xhtml != null) { // if it is null, this is an error already noted in the parsers
// check that the namespace is there and correct.
String ns = xhtml.getNsDecl();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS) && ok;
// check that inner namespaces are all correct
ok = checkInnerNS(errors, e, path, xhtml.getChildNodes()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, xhtml.getName()) && ok;
// check that no illegal elements and attributes have been used
ok = checkInnerNames(errors, e, path, xhtml.getChildNodes(), false) && ok;
ok = checkUrls(errors, e, path, xhtml.getChildNodes()) && ok;
ok = checkIdRefs(errors, e, path, xhtml, resource, node) && ok;
if (true) {
ok = checkReferences(valContext, errors, e, path, "div", xhtml, resource) && ok;
}
if (true) {
ok = checkImageSources(valContext, errors, e, path, "div", xhtml, resource) && ok;
}
}
}
if (context.hasFixed()) {
ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false, profile.getVersionedUrl()+"#"+context.getId()) && ok;
}
if (context.hasPattern()) {
ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true, profile.getVersionedUrl()+"#"+context.getId()) && ok;
}
if (ok && !ID_EXEMPT_LIST.contains(e.fhirType())) { // ids get checked elsewhere
String regext = FHIRPathExpressionFixer.fixRegex(getRegexFromType(e.fhirType()));
if (regext != null) {
try {
String pt = e.primitiveValue();
String ptFmt = null;
if (e.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
ptFmt = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(e.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), pt);
}
boolean matches = pt.matches(regext) || (ptFmt != null && ptFmt.matches(regext));
if (!matches) {
if (ptFmt == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, pt, e.fhirType(), regext) && ok;
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE_ALT, pt, ptFmt, e.fhirType(), regext) && ok;
}
}
} catch (Throwable ex) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION, regext, e.fhirType(), ex.getMessage()) && ok;
}
}
}
// for nothing to check
return ok;
}
private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException {
if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) {
DateTimeType d = new DateTimeType(av);
return d.getAsV3();
} else
throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATE_FORMAT_, fmt));
}
private boolean isCoreDefinition(StructureDefinition profile) {
return profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && profile.getKind() != StructureDefinitionKind.LOGICAL;
}
private String getRegexFromType(String fhirType) {
StructureDefinition sd = context.fetchTypeDefinition(fhirType);
if (sd != null) {
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().endsWith(".value")) {
String regex = ed.getExtensionString(ToolingExtensions.EXT_REGEX);
if (regex != null) {
return regex;
}
for (TypeRefComponent td : ed.getType()) {
regex = td.getExtensionString(ToolingExtensions.EXT_REGEX);
if (regex != null) {
return regex;
}
}
}
}
}
return null;
}
private boolean checkTypeValue(List errors, String path, Element e, Element sd) {
String v = e.primitiveValue();
if (v == null) {
return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.SD_TYPE_MISSING);
}
String url = sd.getChildValue("url");
String d = sd.getChildValue("derivation");
String k = sd.getChildValue("kind");
if (Utilities.isAbsoluteUrl(v)) {
warning(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, d.equals("constraint") || ns(v).equals(ns(url)) || ns(v).equals(ns(url).replace("StructureDefinition/", "")), I18nConstants.SD_TYPE_NOT_MATCH_NS, v, url);
return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, "logical".equals(k), I18nConstants.SD_TYPE_NOT_LOGICAL, v, k);
} else {
boolean tok = false;
for (StructureDefinition t : context.fetchResourcesByType(StructureDefinition.class)) {
if (t.hasSourcePackage() && t.getSourcePackage().getId().startsWith("hl7.fhir.r") && v.equals(t.getType())) {
tok = true;
}
}
if (tok) {
if (!(("http://hl7.org/fhir/StructureDefinition/"+v).equals(url))) {
return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, "constraint".equals(d), I18nConstants.SD_TYPE_NOT_DERIVED, v);
} else {
return true;
}
} else {
return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, tok, I18nConstants.SD_TYPE_NOT_LOCAL, v);
}
}
}
private String ns(String url) {
return url.contains("/") ? url.substring(0, url.lastIndexOf("/")) : url;
}
private Object prepWSPresentation(String s) {
if (Utilities.noString(s)) {
return "";
}
return Utilities.escapeJson(s);
}
public boolean validateReference(ValidationContext valContext, List errors, String path, String type, ElementDefinition context, Element e, String url) {
boolean ok = true;
if (url.startsWith("#")) {
valContext.getInternalRefs().add(url.substring(1));
}
// now, do we check the URI target?
if (fetcher != null && !type.equals("uuid")) {
boolean found;
try {
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) /* || (url.startsWith("http://hl7.org/fhir/tools")) */ ||
SpecialExtensions.isKnownExtension(url) || isXverUrl(url);
if (!found) {
found = fetcher.resolveURL(this, valContext, path, url, type, type.equals("canonical"));
}
} catch (IOException e1) {
found = false;
}
if (!found) {
if (type.equals("canonical")) {
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 {
hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url);
}
} else {
if (url.contains("hl7.org") || url.contains("fhir.org")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url) && ok;;
} else if (url.contains("example.org") || url.contains("acme.com")) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url) && ok;;
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url);
}
}
} else {
if (type.equals("canonical")) {
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;
if (url.startsWith("#")) {
r = loadContainedResource(errors, path, valContext.getRootResource(), url.substring(1), Resource.class);
}
if (r == null) {
r = fetcher.fetchCanonicalResource(this, valContext.getAppContext(), url);
}
if (r == null) {
r = this.context.fetchResource(Resource.class, url);
}
if (r == null) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, rp != ReferenceValidationPolicy.CHECK_VALID, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC, url);
} else if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, isCorrectCanonicalType(r, context), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE, url, r.fhirType(), listExpectedCanonicalTypes(context))) {
if (rp == ReferenceValidationPolicy.CHECK_VALID) {
// todo....
}
// we resolved one, but if there's no version, check if the reference is potentially ambiguous
if (!url.contains("|") && r instanceof CanonicalResource) {
if (!Utilities.existsInList(context.getBase().getPath(), "ImplementationGuide.dependsOn.uri", "ConceptMap.group.source", "ConceptMap.group.target")) {
// ImplementationGuide.dependsOn.version is mandatory, and ConceptMap is checked in the ConceptMap validator
Set possibleVersions = fetcher.fetchCanonicalResourceVersions(this, valContext.getAppContext(), url);
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, possibleVersions.size() <= 1, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS,
url, ((CanonicalResource) r).getVersion(), CommaSeparatedStringBuilder.join(", ", Utilities.sorted(possibleVersions)));
}
}
} else {
ok = false;
}
} catch (Exception ex) {
// won't happen
}
}
}
}
}
return ok;
}
private Set listExpectedCanonicalTypes(ElementDefinition context) {
Set res = new HashSet<>();
TypeRefComponent tr = context.getType("canonical");
if (tr != null) {
for (CanonicalType p : tr.getTargetProfile()) {
String url = p.getValue();
StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, url);
if (sd != null) {
res.add(sd.getType());
} else {
if (url != null && url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
res.add(url.substring("http://hl7.org/fhir/StructureDefinition/".length()));
}
}
}
}
return res;
}
private boolean isCorrectCanonicalType(Resource r, ElementDefinition context) {
TypeRefComponent tr = context.getType("canonical");
if (tr != null) {
for (CanonicalType p : tr.getTargetProfile()) {
if (isCorrectCanonicalType(r, p)) {
return true;
}
}
if (tr.getTargetProfile().isEmpty()) {
return true;
}
}
return false;
}
private boolean isCorrectCanonicalType(Resource r, CanonicalType p) {
String url = p.getValue();
String t = null;
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd != null) {
t = sd.getType();
} else if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
t = url.substring("http://hl7.org/fhir/StructureDefinition/".length());
} else {
return false;
}
return Utilities.existsInList(t, "Resource", "CanonicalResource") || t.equals(r.fhirType());
}
private boolean isCanonicalURLElement(Element e, NodeStack parent) {
if (parent != null && parent.getElement().getName().equals("extension")) {
String url = parent.getElement().getChildValue("url");
if (xverManager.status(url) == XVerExtensionStatus.Valid && url.contains("-")) {
String path = url.substring(url.lastIndexOf("-")+1);
if (path.contains(".")) {
String type = path.substring(0, path.indexOf('.'));
String tail = path.substring(path.indexOf('.')+1);
if ("url".equals(tail) && VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains(type)) {
return true;
}
}
}
}
if (e.getProperty() == null || e.getProperty().getDefinition() == null) {
return false;
}
String path = e.getProperty().getDefinition().getBase().getPath();
if (path == null) {
return false;
}
String[] p = path.split("\\.");
if (p.length != 2) {
return false;
}
if (!"url".equals(p[1])) {
return false;
}
return VersionUtilities.getCanonicalResourceNames(context.getVersion()).contains((p[0]));
}
private boolean containsHtmlTags(String cnt) {
int i = cnt.indexOf("<");
while (i > -1) {
cnt = cnt.substring(i+1);
i = cnt.indexOf("<");
int e = cnt.indexOf(">");
if (e > -1 && e < i) {
String s = cnt.substring(0, e);
if (s.matches(HTML_FRAGMENT_REGEX)) {
return true;
}
}
}
return false;
}
private boolean isDefinitionURL(String url) {
return Utilities.existsInList(url,
"http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer", "http://hl7.org/fhirpath/System.Decimal",
"http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity",
"http://hl7.org/fhir/SearchParameter/Resource-filter");
}
private boolean checkInnerNames(List errors, Element e, String path, List list, boolean inPara) {
boolean ok = true;
for (XhtmlNode node : list) {
if (node.getNodeType() == NodeType.Comment) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL) && ok;
}
if (node.getNodeType() == NodeType.Element) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, HTML_ELEMENTS.contains(node.getName()), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName()) && ok;
for (String an : node.getAttributes().keySet()) {
boolean bok = an.startsWith("xmlns") || HTML_ATTRIBUTES.contains(an) || HTML_COMBO_LIST.contains(node.getName() + "." + an);
if (!bok) {
if ("xml:space".equals(an)) {
hint(errors, "2024-08-03", IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_XML_SPACE, an, node.getName());
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName()) && ok;
}
}
}
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !(inPara && HTML_BLOCK_LIST.contains(node.getName())) , I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA, node.getName()) && ok;
ok = checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())) && ok;
}
}
return ok;
}
private boolean checkReferences(ValidationContext valContext, List errors, Element e, String path, String xpath, XhtmlNode node, Element resource) {
boolean ok = true;
if (node.getNodeType() == NodeType.Element & "a".equals(node.getName()) && node.getAttribute("href") != null) {
String href = node.getAttribute("href");
if (rule(errors, "2024-07-20", IssueType.INVALID, e.line(), e.col(), path, !Utilities.noString(href), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_EMPTY_HREF, xpath, Utilities.stripEoln(node.allText()))) {
if ( href.startsWith("#") && !href.equals("#")) {
String ref = href.substring(1);
valContext.getInternalRefs().add(ref);
Set refs = new HashSet<>();
int count = countTargetMatches(resource, ref, true, "$", refs);
if (count == 0) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE, href, xpath, Utilities.stripEoln(node.allText()).trim());
} else if (count > 1) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES, href, xpath, node.allText(), CommaSeparatedStringBuilder.join(", ", refs));
}
} else if (href.contains(":") && Utilities.isAbsoluteUrl(href)) {
String scheme = href.substring(0, href.indexOf(":"));
if (rule(errors, "2024-07-20", IssueType.INVALID, e.line(), e.col(), path, !isActiveScheme(scheme), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_ACTIVE_HREF, href, xpath, Utilities.stripEoln(node.allText()).trim(), scheme)) {
if (rule(errors, "2024-07-20", IssueType.INVALID, e.line(), e.col(), path, isLiteralScheme(scheme), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_LITERAL_HREF, href, xpath, Utilities.stripEoln(node.allText()).trim(), scheme)) {
hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, isKnownScheme(scheme), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_UNKNOWN_HREF, href, xpath, node.allText().trim(), scheme);
} else {
ok = false;
}
} else {
ok = false;
}
} else {
// we can't validate at this point. Come back and revisit this some time in the future
}
}
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
checkReferences(valContext, errors, e, path, xpath+"/"+child.getName(), child, resource);
}
}
return ok;
}
private boolean isActiveScheme(String scheme) {
return Utilities.existsInList(scheme, "javascript", "vbscript");
}
private boolean isLiteralScheme(String scheme) {
return !Utilities.existsInList(scheme, "urn", "cid");
}
private boolean isKnownScheme(String scheme) {
return Utilities.existsInList(scheme, "http", "https", "tel", "mailto", "data");
}
protected int countTargetMatches(Element element, String fragment, boolean checkBundle, String path,Set refs) {
int count = 0;
if (fragment.equals(element.getIdBase())) {
count++;
refs.add(path+"/id");
}
if (element.getXhtml() != null) {
count = count + countTargetMatches(element.getXhtml(), fragment, path, refs);
}
if (element.hasChildren()) {
for (Element child : element.getChildren()) {
count = count + countTargetMatches(child, fragment, false, path+"/"+child.getName(), refs);
}
}
if (count == 0 && checkBundle) {
Element e = element.getParentForValidator();
while (e != null) {
if (e.fhirType().equals("Bundle")) {
return countTargetMatches(e, fragment, false, path+"/..", refs);
}
e = e.getParentForValidator();
}
}
return count;
}
private int countTargetMatches(XhtmlNode node, String fragment, String path,Set refs) {
int count = 0;
if (fragment.equals(node.getAttribute("id"))) {
count++;
refs.add(path+"/@id");
}
if ("a".equals(node.getName()) && fragment.equals(node.getAttribute("name"))) {
count++;
refs.add(path+"/@name");
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
count = count + countTargetMatches(child, fragment, path+"/"+child.getName(), refs);
}
}
return count;
}
private boolean checkImageSources(ValidationContext valContext, List errors, Element e, String path, String xpath, XhtmlNode node, Element resource) {
boolean ok = true;
if (node.getNodeType() == NodeType.Element & "img".equals(node.getName()) && node.getAttribute("src") != null) {
String src = node.getAttribute("src");
if (src.startsWith("#")) {
String ref = src.substring(1);
valContext.getInternalRefs().add(ref);
int count = countFragmentMatches(resource, ref);
if (count == 0) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE_IMG, src, xpath);
} else if (count > 1) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES, src, xpath);
}
} else {
// we can't validate at this point. Come back and revisit this some time in the future
}
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
checkImageSources(valContext, errors, e, path, path+"/"+child.getName(), child, resource);
}
}
return ok;
}
private boolean checkIdRefs(List errors, Element e, String path, XhtmlNode node, Element resource, NodeStack stack) {
boolean ok = true;
if (node.getNodeType() == NodeType.Element && node.getAttribute("idref") != null) {
String idref = node.getAttribute("idref");
int count = countFragmentMatches(resource, idref, stack);
if (count == 0) {
ok = warning(errors, "2023-12-01", IssueType.INVALID, e.line(), e.col(), path, idref == null, I18nConstants.XHTML_IDREF_NOT_FOUND, idref) && ok;
} else if (count > 1) {
ok = rule(errors, "2023-12-01", IssueType.INVALID, e.line(), e.col(), path, idref == null, I18nConstants.XHTML_IDREF_NOT_MULTIPLE_MATCHES, idref) && ok;
}
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
checkIdRefs(errors, e, path, child, resource, stack);
}
}
return ok;
}
private boolean checkUrls(List errors, Element e, String path, List list) {
boolean ok = true;
for (XhtmlNode node : list) {
if (node.getNodeType() == NodeType.Element) {
if ("a".equals(node.getName())) {
String msg = UrlUtil.checkValidUrl(node.getAttribute("href"), context);
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("href"), msg) && ok;
} else if ("img".equals(node.getName())) {
String msg = UrlUtil.checkValidUrl(node.getAttribute("src"), context);
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("src"), msg) && ok;
}
ok = checkUrls(errors, e, path, node.getChildNodes()) && ok;
}
}
return ok;
}
private boolean checkInnerNS(List errors, Element e, String path, List list) {
boolean ok = true;
for (XhtmlNode node : list) {
if (node.getNodeType() == NodeType.Element) {
String ns = node.getNsDecl();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS) && ok;
checkInnerNS(errors, e, path, node.getChildNodes());
}
}
return ok;
}
private boolean checkPrimitiveBinding(ValidationContext valContext, List errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) {
// We ignore bindings that aren't on string, uri or code
if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) {
return true;
}
if (noTerminologyChecks)
return true;
boolean ok = true;
String value = element.primitiveValue();
// System.out.println("check "+value+" in "+path);
// firstly, resolve the value set
ElementDefinitionBindingComponent binding = elementContext.getBinding();
if (binding.hasValueSet()) {
ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl(), profile);
if (vs == null) {
CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
} else {
ok = false;
}
} else {
EnumSet validationPolicy = policyAdvisor.policyForCodedContent(this, valContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, null, vs, new ArrayList<>());
if (!validationPolicy.isEmpty()) {
long t = System.nanoTime();
ValidationResult vr = null;
if (binding.getStrength() != BindingStrength.EXAMPLE) {
ValidationOptions options = baseOptions.withGuessSystem();
if (!validationPolicy.contains(CodedContentValidationAction.InvalidCode) && !validationPolicy.contains(CodedContentValidationAction.InvalidDisplay)) {
options = options.withCheckValueSetOnly();
}
vr = checkCodeOnServer(stack, vs, value, options);
}
ok = calculateSeverityForTxIssuesAndUpdateErrors(errors, vr, element, path, binding.getStrength() != BindingStrength.REQUIRED, binding.getValueSet(), binding.getStrength()) && ok;
timeTracker.tx(t, "vc "+value+"");
if (binding.getStrength() == BindingStrength.REQUIRED) {
removeTrackedMessagesForLocation(errors, element, path);
}
if (vr != null && !vr.isOk()) {
if (vr.IsNoService()) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value);
} else if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) {
txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
} else if (binding.getStrength() == BindingStrength.REQUIRED) {
ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE, null), getErrorMessage(vr.getMessage())) && ok;
} else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), value, stack) && ok;
else if (!noExtensibleWarnings && !isOkExtension(value, vs))
txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_17, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE, null), getErrorMessage(vr.getMessage()));
} else if (binding.getStrength() == BindingStrength.PREFERRED) {
if (baseOnly) {
txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeReference(binding.getValueSet(), vs, BindingContext.BASE, null), getErrorMessage(vr.getMessage()));
}
}
} else if (vr != null && vr.getMessage() != null){
if (vr.getSeverity() == IssueSeverity.INFORMATION) {
txHint(errors, "2023-07-04", vr.getTxLink(), IssueType.INFORMATIONAL, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_HINT, value, vr.getMessage());
} else {
txWarning(errors, "2023-07-04", vr.getTxLink(), IssueType.INFORMATIONAL, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_WARNING, value, vr.getMessage());
}
}
}
}
} else if (!noBindingMsgSuppressed) {
hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2);
}
return ok;
}
private boolean isOkExtension(String value, ValueSet vs) {
if ("http://hl7.org/fhir/ValueSet/defined-types".equals(vs.getUrl())) {
return value.startsWith("http://hl7.org/fhirpath/System.");
}
return false;
}
private boolean checkQuantity(List errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value", false), fixed.getValueElement(), fixedSource, "value", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator", false), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".unit", focus.getNamedChild("unit", false), fixed.getUnitElement(), fixedSource, "unit", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system", false), fixed.getSystemElement(), fixedSource, "system", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".code", focus.getNamedChild("code", false), fixed.getCodeElement(), fixedSource, "code", focus, pattern, context) && ok;
return ok;
}
private boolean checkQuantity(List errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) {
boolean ok = true;
String value = element.hasChild("value", false) ? element.getNamedChild("value", false).getValue() : null;
String unit = element.hasChild("unit", false) ? element.getNamedChild("unit", false).getValue() : null;
String system = element.hasChild("system", false) ? element.getNamedChild("system", false).getValue() : null;
String code = element.hasChild("code", false) ? element.getNamedChild("code", false).getValue() : null;
// todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet)
// todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation
if (!Utilities.noString(value) && definition.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
int dp = value.contains(".") ? value.substring(value.indexOf(".")+1).length() : 0;
int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_MAX_DECIMALS));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def) && ok;
}
if (system != null || code != null ) {
ok = checkCodedElement(errors, path, element, theProfile, definition, false, false, theStack, code, system, null, unit) && ok;
}
if (code != null && "http://unitsofmeasure.org".equals(system)) {
int b = code.indexOf("{");
int e = code.indexOf("}");
if (b >= 0 && e > 0 && b < e) {
String annotation = code.substring(b, e+1);
String annotationValue = code.substring(b+1, e);
if (unit == null) {
ok = bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_UCUM_ANNOTATIONS_NO_UNIT, annotation) && ok;
} else if (!unit.toLowerCase().contains(annotationValue.toLowerCase())) {
ok = bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_UCUM_ANNOTATIONS_NOT_IN_UNIT, annotation, unit) && ok;
} else {
ok = bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_UCUM_ANNOTATIONS, annotation, unit) && ok;
}
}
}
if (definition.hasMinValue()) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE)) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_QTY, definition.getMinValue().fhirType())) {
Quantity min = definition.getMinValueQuantity();
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, system.equals(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_SYSTEM_MISMATCH, system, min.getSystem()) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE)) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE)) {
if (code.equals(min.getCode())) {
// straight value comparison
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMinValue(value, min.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG, value, min.getValue().toString()) && ok;
} else if ("http://unitsofmeasure.org".equals(system)) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC)) {
Decimal v = convertUcumValue(value, code, min.getCode());
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, v != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CONVERT, value, code, min.getCode())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMinValue(v, min.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG_UCUM, value, code, min.getValue().toString(), min.getCode()) && ok;
} else {
ok = false;
}
}
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH, code, min.getCode());
}
} else {
ok = false;
}
}
} else {
ok = false;
}
}
}
if (definition.hasMaxValue()) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE)) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_QTY, definition.getMaxValue().fhirType())) {
Quantity max = definition.getMaxValueQuantity();
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, system.equals(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_SYSTEM_MISMATCH, system, max.getSystem()) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE) &&
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE)) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE)) {
if (code.equals(max.getCode())) {
// straight value comparison
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMaxValue(value, max.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG, value, max.getValue().toString()) && ok;
} else if ("http://unitsofmeasure.org".equals(system)) {
if (warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC)) {
Decimal v = convertUcumValue(value, code, max.getCode());
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, v != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CONVERT, value, code, max.getCode())) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMaxValue(v, max.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM, value, code, max.getValue().toString(), max.getCode()) && ok;
} else {
ok = false;
}
}
} else {
warning(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH, code, max.getCode());
}
} else {
ok = false;
}
}
} else {
ok = false;
}
}
}
return ok;
}
private Decimal convertUcumValue(String value, String code, String minCode) {
try {
Decimal v = new Decimal(value);
return context.getUcumService().convert(v, code, minCode);
} catch (Exception e) {
return null;
}
}
private boolean checkDecimalMaxValue(Decimal value, BigDecimal min) {
try {
Decimal m = new Decimal(min.toString());
return value.comparesTo(m) <= 0;
} catch (Exception e) {
return false; // this will be another error somewhere else?
}
}
private boolean checkDecimalMaxValue(String value, BigDecimal min) {
try {
BigDecimal v = new BigDecimal(value);
return v.compareTo(min) <= 0;
} catch (Exception e) {
return false; // this will be another error somewhere else
}
}
private boolean checkDecimalMinValue(Decimal value, BigDecimal min) {
try {
Decimal m = new Decimal(min.toString());
return value.comparesTo(m) >= 0;
} catch (Exception e) {
return false; // this will be another error somewhere else?
}
}
private boolean checkDecimalMinValue(String value, BigDecimal min) {
try {
BigDecimal v = new BigDecimal(value);
return v.compareTo(min) >= 0;
} catch (Exception e) {
return false; // this will be another error somewhere else
}
}
private boolean checkAttachment(List errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {
boolean ok = true;
long size = -1;
// first check size
String fetchError = null;
if (element.hasChild("data", false)) {
String b64 = element.getChildValue("data");
// Note: If the value isn't valid, we're not adding an error here, as the test to the
// child Base64Binary will catch it and we don't want to log it twice
boolean bok = Base64Util.isValidBase64(b64);
if (bok && element.hasChild("size", false)) {
size = Base64Util.countBase64DecodedBytes(b64);
String sz = element.getChildValue("size");
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, Long.toString(size).equals(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT, sz, size) && ok;
}
} else if (element.hasChild("size", false)) {
String sz = element.getChildValue("size");
if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, Utilities.isLong(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz)) {
size = Long.parseLong(sz);
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz) && ok;
} else {
ok = false;
}
} else if (element.hasChild("url", false)) {
String url = element.getChildValue("url");
if (definition.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
try {
if (url.startsWith("http://") || url.startsWith("https://")) {
if (fetcher == null) {
fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER, url);
} else {
byte[] cnt = fetcher.fetchRaw(this, url);
size = cnt.length;
}
} else if (url.startsWith("file:")) {
size = ManagedFileAccess.file(url.substring(5)).length();
} else {
fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url); }
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR, url, e.getMessage());
}
}
}
if (definition.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
if (warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, fetchError)) {
long def = Long.parseLong(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_MAX_SIZE));
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG, size, def) && ok;
}
}
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data", false) || element.hasChild("url", false)) || (element.hasChild("contentType", false) || element.hasChild("language", false)),
I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT);
return ok;
}
// implementation
private boolean checkRange(List errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".low", focus.getNamedChild("low", false), fixed.getLow(), fixedSource, "low", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".high", focus.getNamedChild("high", false), fixed.getHigh(), fixedSource, "high", focus, pattern, context) && ok;
return ok;
}
private boolean checkRatio(List errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator", false), fixed.getNumerator(), fixedSource, "numerator", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator", false), fixed.getDenominator(), fixedSource, "denominator", focus, pattern, context) && ok;
return ok;
}
private boolean checkReference(ValidationContext valContext,
List errors,
String path,
Element element,
StructureDefinition profile,
ElementDefinition container,
String parentType,
NodeStack stack, PercentageTracker pct, ValidationMode vmode) throws FHIRException {
boolean ok = true;
Reference reference = ObjectConverter.readAsReference(element);
String ref = reference.getReference();
if (Utilities.noString(ref)) {
if (!path.contains("element.pattern")) { // this business rule doesn't apply to patterns
if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path,
!Utilities.noString(element.getNamedChildValue("display", false)), I18nConstants.REFERENCE_REF_NODISPLAY);
}
}
return true;
} else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) {
// special known URLs that can't be validated but are known to be valid
return true;
}
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref);
BooleanHolder bh = new BooleanHolder();
ResolvedReference we = localResolve(ref, stack, errors, path, valContext.getRootResource(), valContext.getGroupingResource(), element, bh);
ok = bh.ok() && ok;
String refType;
if (ref.startsWith("#")) {
valContext.getInternalRefs().add(ref.substring(1));
refType = "contained";
} else {
if (we == null) {
refType = "remote";
} else {
refType = "bundled";
}
}
boolean conditional = ref.contains("?") && context.getResourceNamesAsSet().contains(ref.substring(0, ref.indexOf("?")));
ReferenceValidationPolicy pol;
if (refType.equals("contained") || refType.equals("bundled")) {
pol = ReferenceValidationPolicy.CHECK_VALID;
} else {
pol = policyAdvisor.policyForReference(this, valContext.getAppContext(), path, ref);
}
if (conditional) {
String query = ref.substring(ref.indexOf("?"));
boolean test = !Utilities.noString(query) && query.matches("\\?([_a-zA-Z][_a-zA-Z0-9]*=[^=&]*)(&([_a-zA-Z][_a-zA-Z0-9]*=[^=&]*))*");
//("^\\?([\\w-]+(=[\\w-]*)?(&[\\w-]+(=[\\w-]*)?)*)?$"),
ok = rule(errors, "2023-02-20", IssueType.INVALID, element.line(), element.col(), path, test, I18nConstants.REFERENCE_REF_QUERY_INVALID, ref) && ok;
} else if (pol.checkExists()) {
if (we == null) {
if (!refType.equals("contained")) {
if (fetcher == null) {
throw new FHIRException(context.formatMessage(I18nConstants.RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED));
} else {
Element ext = null;
if (fetchCache.containsKey(ref)) {
ext = fetchCache.get(ref);
} else {
try {
ext = fetcher.fetch(this, valContext.getAppContext(), ref);
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
ext = null;
// it's probably an error, but here we're just giving the user information about why resolution failed
hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), path,
false, I18nConstants.REFERENCE_RESOLUTION_FAILED, ref, e.getClass().getName(), e.getMessage());
}
if (ext != null) {
setParents(ext);
fetchCache.put(ref, ext);
}
}
we = ext == null ? null : makeExternalRef(ext, path);
}
}
}
boolean rok = (allowExamples && (ref.contains("example.org") || ref.contains("acme.com")))
|| (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS);
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, rok, I18nConstants.REFERENCE_REF_CANTRESOLVE, ref) && ok;
}
String ft;
if (we != null) {
ft = we.getType();
} else {
ft = tryParse(ref);
}
if (reference.hasType()) { // R4 onwards...
// the type has to match the specified
String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType();
TypeRefComponent containerType = container.getType("Reference");
if (!containerType.hasTargetProfile(tu)
&& !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")
&& !containerType.getTargetProfile().isEmpty()
) {
boolean matchingResource = false;
for (CanonicalType target : containerType.getTargetProfile()) {
StructureDefinition sd = resolveProfile(profile, target.asStringValue());
if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, element.line(), element.col(), path, sd != null,
I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, target.asStringValue())) {
if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) {
matchingResource = true;
break;
}
} else {
ok = false;
}
}
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource,
I18nConstants.REFERENCE_REF_WRONGTARGET, reference.getType(), container.getType("Reference").getTargetProfile()) && ok;
}
// the type has to match the actual
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path,
ft == null || ft.equals(reference.getType()), I18nConstants.REFERENCE_REF_BADTARGETTYPE, reference.getType(), ft) && ok;
}
if (we != null && pol.checkType()) {
if (warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null,
I18nConstants.REFERENCE_REF_NOTYPE)) {
// we validate as much as we can. First, can we infer a type from the profile?
boolean rok = false;
TypeRefComponent type = getReferenceTypeRef(container.getType());
if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
Set types = new HashSet<>();
List profiles = new ArrayList<>();
for (UriType u : type.getTargetProfile()) {
StructureDefinition sd = resolveProfile(profile, u.getValue());
if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null,
I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, u.getValue())) {
types.add(sd.getType());
if (ft.equals(sd.getType())) {
rok = true;
profiles.add(sd);
}
} else {
ok = false;
}
}
if (!pol.checkValid()) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0,
I18nConstants.REFERENCE_REF_CANTMATCHTYPE, ref, StringUtils.join("; ", sorted(type.getTargetProfile()))) && ok;
} else {
Map> badProfiles = new HashMap<>();
Map> goodProfiles = new HashMap<>();
int goodCount = 0;
for (StructureDefinition pr : profiles) {
List profileErrors = new ArrayList();
validateResource(we.valContext(valContext, pr), profileErrors, we.getResource(), we.getFocus(), pr,
IdStatus.OPTIONAL, we.getStack().resetIds(), pct, vmode.withReason(ValidationReason.MatchingSlice), true, false);
if (!hasErrors(profileErrors)) {
goodCount++;
goodProfiles.put(pr, profileErrors);
trackUsage(pr, valContext, element);
} else {
badProfiles.put(pr, profileErrors);
}
}
if (goodCount == 1) {
if (showMessagesFromReferences) {
for (ValidationMessage vm : goodProfiles.values().iterator().next()) {
if (!errors.contains(vm)) {
errors.add(vm);
ok = false;
}
}
}
} else if (goodProfiles.size() == 0) {
if (!isShowMessagesFromReferences()) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles),
I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())) && ok;
for (StructureDefinition sd : badProfiles.keySet()) {
slicingHint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false, false,
context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getVersionedUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
}
} else {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1,
I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())) && ok;
for (List messages : badProfiles.values()) {
for (ValidationMessage vm : messages) {
if (!errors.contains(vm)) {
errors.add(vm);
ok = false;
}
}
}
}
} else {
if (!isShowMessagesFromReferences()) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false,
I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
for (StructureDefinition sd : badProfiles.keySet()) {
slicingHint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false,
false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getVersionedUrl()),
errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
}
} else {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false,
I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
for (List messages : goodProfiles.values()) {
for (ValidationMessage vm : messages) {
if (!errors.contains(vm)) {
errors.add(vm);
ok = false;
}
}
}
}
}
}
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, rok,
I18nConstants.REFERENCE_REF_BADTARGETTYPE, ft, types.toString()) && ok;
}
if (type.hasAggregation() && !noCheckAggregation) {
boolean modeOk = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (Enumeration mode : type.getAggregation()) {
b.append(mode.getCode());
if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
modeOk = true;
else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
modeOk = true;
else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote")))
modeOk = true;
}
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk,
I18nConstants.REFERENCE_REF_AGGREGATION, refType, b.toString()) && ok;
}
}
}
if (we == null) {
TypeRefComponent type = getReferenceTypeRef(container.getType());
boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED);
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, okToRef, I18nConstants.REFERENCE_REF_NOTFOUND_BUNDLE, ref) && ok;
}
if (we == null && ft != null && assumeValidRestReferences) {
// if we == null, we inferred ft from the reference. if we are told to treat this as gospel
TypeRefComponent type = getReferenceTypeRef(container.getType());
Set types = new HashSet<>();
StructureDefinition sdFT = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ft);
boolean rok = false;
for (CanonicalType tp : type.getTargetProfile()) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue(), profile);
if (sd != null) {
types.add(sd.getType());
StructureDefinition sdF = sdFT;
while (sdF != null) {
if (sdF.getType().equals(sd.getType())) {
rok = true;
break;
}
sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition(), sdF) : null;
}
}
}
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || rok,
I18nConstants.REFERENCE_REF_BADTARGETTYPE2, ft, ref, types) && ok;
}
if (pol == ReferenceValidationPolicy.CHECK_VALID) {
// todo....
}
// todo: if the content is a resource, check that Reference.type is describing a resource
return ok;
}
private List sorted(List list) {
List res = new ArrayList();
for (CanonicalType ct : list) {
res.add(ct.asStringValue());
}
Collections.sort(res);
return res;
}
private boolean isSuspiciousReference(String url) {
if (!assumeValidRestReferences || url == null || Utilities.isAbsoluteUrl(url) || url.startsWith("#")) {
return false;
}
String[] parts = url.split("\\/");
if (parts.length == 2 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1])) {
return false;
}
if (parts.length == 4 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1]) && "_history".equals(parts[2]) && Utilities.isValidId(parts[3])) {
return false;
}
return true;
}
private String asListByUrl(Collection coll) {
List list = new ArrayList<>();
list.addAll(coll);
Collections.sort(list, new StructureDefinitionSorterByUrl());
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (StructureDefinition sd : list) {
b.append(sd.getVersionedUrl());
}
return b.toString();
}
private String asList(Collection coll) {
List list = new ArrayList<>();
list.addAll(coll);
Collections.sort(list, new CanonicalTypeSorter());
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (CanonicalType c : list) {
b.append(c.getValue());
}
return b.toString();
}
private boolean areAllBaseProfiles(List profiles) {
for (StructureDefinition sd : profiles) {
if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
return false;
}
}
return true;
}
private String errorSummaryForSlicing(List list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ValidationMessage vm : list) {
if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
b.append(vm.getLocation() + ": " + vm.getMessage());
}
}
return b.toString();
}
private String errorSummaryForSlicingAsHtml(List list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ValidationMessage vm : list) {
if (vm.isSlicingHint()) {
b.append("" + vm.getLocation() + ": " + vm.getSliceHtml() + " ");
} else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
b.append("" + vm.getLocation() + ": " + vm.getHtml() + " ");
}
}
return "" + b.toString() + "
";
}
private boolean isCritical(List list) {
for (ValidationMessage vm : list) {
if (vm.isSlicingHint() && vm.isCriticalSignpost()) {
return true;
}
}
return false;
}
private String[] errorSummaryForSlicingAsText(List list) {
List res = new ArrayList();
for (ValidationMessage vm : list) {
if (vm.isSlicingHint()) {
if (vm.sliceText != null) {
for (String s : vm.sliceText) {
res.add(vm.getLocation() + ": " + s);
}
} else {
res.add(vm.getLocation() + ": " + vm.getMessage());
}
} else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
res.add(vm.getLocation() + ": " + vm.getHtml());
}
}
return res.toArray(new String[0]);
}
private TypeRefComponent getReferenceTypeRef(List types) {
for (TypeRefComponent tr : types) {
if ("Reference".equals(tr.getCode())) {
return tr;
}
}
return null;
}
private String checkResourceType(String type) {
long t = System.nanoTime();
try {
if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
return type;
else
return null;
} finally {
timeTracker.sd(t);
}
}
private boolean checkSampledData(List errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin", false), fixed.getOrigin(), fixedSource, "origin", focus, pattern, context) && ok;
if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) {
ok = checkFixedValue(errors, path + ".interval", focus.getNamedChild("period", false), fixed.getIntervalElement(), fixedSource, "interval", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".intervalUnit", focus.getNamedChild("period", false), fixed.getIntervalUnitElement(), fixedSource, "intervalUnit", focus, pattern, context) && ok;
} else {
ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period", false), fixed.getIntervalElement(), fixedSource, "period", focus, pattern, context) && ok;
}
ok = checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor", false), fixed.getFactorElement(), fixedSource, "factor", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit", false), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit", false), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions", false), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".data", focus.getNamedChild("data", false), fixed.getDataElement(), fixedSource, "data", focus, pattern, context) && ok;
return ok;
}
private boolean checkReference(List errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".reference", focus.getNamedChild("reference", false), fixed.getReferenceElement_(), fixedSource, "reference", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".type", focus.getNamedChild("type", false), fixed.getTypeElement(), fixedSource, "type", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier", false), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern, context) && ok;
ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display", false), fixed.getDisplayElement(), fixedSource, "display", focus, pattern, context) && ok;
return ok;
}
private boolean checkTiming(List errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern, String context) {
boolean ok = true;
ok = checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat", false), fixed.getRepeat(), fixedSource, "value", focus, pattern, context) && ok;
List events = new ArrayList();
focus.getNamedChildren("event", events);
if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(), I18nConstants.BUNDLE_MSG_EVENT_COUNT, Integer.toString(fixed.getEvent().size()), Integer.toString(events.size()))) {
for (int i = 0; i < events.size(); i++)
ok = checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern, context) && ok;
} else {
ok = false;
}
return ok;
}
private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
return true;
if (codeinExpansion(c, system, code))
return true;
}
return false;
}
private boolean codeInExpansion(ValueSet vs, String system, String code) {
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
return true;
if (codeinExpansion(c, system, code))
return true;
}
return false;
}
private String describeReference(String reference, CanonicalResource target, BindingContext ctxt, String usageNote) {
if (reference == null && target == null)
return "null";
String res = null;
if (reference == null) {
res = target.getVersionedUrl();
} else if (target == null) {
res = reference;
} else {
String uref = reference.contains("|") ? reference.substring(0, reference.lastIndexOf("|")) : reference;
String vref = reference.contains("|") ? reference.substring(reference.lastIndexOf("|")+1) : null;
if (uref.equals(target.getUrl()) && (vref == null || vref.equals(target.getVersion()))) {
res = "'"+target.present()+"' ("+target.getVersionedUrl()+")";
} else {
res = reference + "(which actually refers to '"+target.present()+"' (" + target.getVersionedUrl() + "))";
}
}
switch (ctxt) {
case ADDITIONAL: return context.formatMessage(Utilities.noString(usageNote) ? I18nConstants.BINDING_ADDITIONAL_D : I18nConstants.BINDING_ADDITIONAL_UC, res, usageNote);
case MAXVS: return context.formatMessage(I18nConstants.BINDING_MAX, res);
default: return res;
}
}
private String describeTypes(List types) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (TypeRefComponent t : types) {
b.append(t.getWorkingCode());
}
return b.toString();
}
protected ElementDefinition findElement(StructureDefinition profile, String name) {
for (ElementDefinition c : profile.getSnapshot().getElement()) {
if (c.getPath().equals(name)) {
return c;
}
}
return null;
}
@Override
public CheckDisplayOption getCheckDisplay() {
return checkDisplay;
}
private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
if (code.equals(c.getCode()))
return c;
for (ConceptDefinitionComponent g : c.getConcept()) {
ConceptDefinitionComponent r = getCodeDefinition(g, code);
if (r != null)
return r;
}
return null;
}
private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
for (ConceptDefinitionComponent c : cs.getConcept()) {
ConceptDefinitionComponent r = getCodeDefinition(c, code);
if (r != null)
return r;
}
return null;
}
private IndexedElement getContainedById(Element container, String id) {
List contained = new ArrayList();
container.getNamedChildren("contained", contained);
for (int i = 0; i < contained.size(); i++) {
Element we = contained.get(i);
if (id.equals(we.getNamedChildValue(ID, false))) {
return new IndexedElement(i, we, null);
}
}
return null;
}
public IWorkerContext getContext() {
return context;
}
private List getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException {
List elements = new ArrayList();
if ("value".equals(discriminator) && element.hasFixed()) {
elements.add(element);
return elements;
}
boolean dontFollowReference = false;
if (removeResolve) { // if we're doing profile slicing, we don't want to walk into the last resolve.. we need the profile on the source not the target
if (discriminator.equals("resolve()")) {
elements.add(element);
return elements;
}
if (discriminator.endsWith(".resolve()")) {
discriminator = discriminator.substring(0, discriminator.length() - 10);
dontFollowReference = true;
}
}
TypedElementDefinition ted = null;
String fp = FHIRPathExpressionFixer.fixExpr(discriminator, null, context.getVersion());
ExpressionNode expr = null;
try {
expr = fpe.parse(fp);
} catch (Exception e) {
if (STACK_TRACE) e.printStackTrace();
throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e);
}
long t2 = System.nanoTime();
ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile, dontFollowReference);
timeTracker.sd(t2);
if (ted != null)
elements.add(ted.getElement());
for (TypeRefComponent type : element.getType()) {
for (CanonicalType p : type.getProfile()) {
String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null;
StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue(), profile);
if (sd == null)
throw new DefinitionException(context.formatMessage(I18nConstants.SD_ED_TYPE_PROFILE_UNKNOWN, p));
profile = sd;
if (id == null)
element = sd.getSnapshot().getElementFirstRep();
else {
element = null;
for (ElementDefinition t : sd.getSnapshot().getElement()) {
if (id.equals(t.getId()))
element = t;
}
if (element == null)
throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ELEMENT__IN_PROFILE_, id, p));
}
expr = fpe.parse(fp);
t2 = System.nanoTime();
ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile, dontFollowReference);
timeTracker.sd(t2);
if (ted != null)
elements.add(ted.getElement());
}
}
return elements;
}
private Element getExtensionByUrl(List extensions, String urlSimple) {
for (Element e : extensions) {
if (urlSimple.equals(e.getNamedChildValue("url", false)))
return e;
}
return null;
}
public List getExtensionDomains() {
return extensionDomains;
}
public List getImplementationGuides() {
return igs;
}
private StructureDefinition getProfileForType(String type, List list, Resource src) {
for (TypeRefComponent tr : list) {
String url = tr.getWorkingCode();
if (!Utilities.isAbsoluteUrl(url))
url = "http://hl7.org/fhir/StructureDefinition/" + url;
long t = System.nanoTime();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url, src);
timeTracker.sd(t);
if (sd != null && (sd.getTypeTail().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) {
return sd;
}
if (sd != null && (sd.getAbstract() || sd.getUrl().startsWith(Constants.NS_CDA_ROOT))) {
StructureDefinition sdt = context.fetchTypeDefinition(type);
StructureDefinition tt = sdt;
while (tt != null) {
if (sd.getUrl().equals(tt.getBaseDefinition())) {
return sdt;
}
tt = context.fetchResource(StructureDefinition.class, tt.getBaseDefinition());
}
}
}
return null;
}
private Element getValueForDiscriminator(Object appContext, List errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack, BooleanHolder bh) throws FHIRException, IOException {
String p = stack.getLiteralPath() + "." + element.getName();
Element focus = element;
String[] dlist = discriminator.split("\\.");
for (String d : dlist) {
if (focus.fhirType().equals("Reference") && d.equals("reference")) {
String url = focus.getChildValue("reference");
if (Utilities.noString(url))
throw new FHIRException(context.formatMessage(I18nConstants.NO_REFERENCE_RESOLVING_DISCRIMINATOR__FROM_, discriminator, element.getProperty().getName()));
// Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough?
Element target = resolve(appContext, url, stack, errors, p, bh);
if (target == null)
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_, url, d, discriminator, element.getProperty().getName()));
focus = target;
} else if (d.equals("value") && focus.isPrimitive()) {
return focus;
} else {
List children = focus.getChildren(d);
if (children.isEmpty())
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND__RESOLVING_DISCRIMINATOR__FROM_, d, discriminator, element.getProperty().getName()));
if (children.size() > 1)
throw new FHIRException(context.formatMessage(I18nConstants.FOUND__ITEMS_FOR__RESOLVING_DISCRIMINATOR__FROM_, Integer.toString(children.size()), d, discriminator, element.getProperty().getName()));
focus = children.get(0);
p = p + "." + d;
}
}
return focus;
}
private CodeSystem getCodeSystem(String system) {
long t = System.nanoTime();
try {
return context.fetchCodeSystem(system);
} finally {
timeTracker.tx(t, "cs "+system);
}
}
private boolean hasTime(String fmt) {
return fmt.contains("T");
}
private boolean hasTimeZone(String fmt) {
return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z"));
}
private boolean isAbsolute(String uri) {
String protocol = null;
String tail = null;
if (uri.contains(":")) {
protocol = uri.substring(0, uri.indexOf(":"));
tail = uri.substring(uri.indexOf(":")+1);
}
if (Utilities.isToken(protocol)) {
if ("file".equals(protocol)) {
return tail.startsWith("/") || tail.contains(":");
} else {
return true;
}
} else {
return false;
}
}
private boolean isCodeSystemReferenceValid(String uri) {
return isSystemReferenceValid(uri);
}
private boolean isIdentifierSystemReferenceValid(String uri) {
return isSystemReferenceValid(uri) || uri.startsWith("ldap:");
}
private boolean isSystemReferenceValid(String uri) {
return uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:");
}
public boolean isAnyExtensionsAllowed() {
return anyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
private boolean isParametersEntry(String path) {
String[] parts = path.split("\\.");
return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part"));
}
private boolean isBundleEntry(String path) {
String[] parts = path.split("\\.");
return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && pathEntryHasName(parts[parts.length - 2], ENTRY);
}
private boolean isBundleOutcome(String path) {
String[] parts = path.split("\\.");
return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response");
}
private static boolean pathEntryHasName(String thePathEntry, String theName) {
if (thePathEntry.equals(theName)) {
return true;
}
if (thePathEntry.length() >= theName.length() + 3) {
if (thePathEntry.startsWith(theName)) {
if (thePathEntry.charAt(theName.length()) == '[') {
return true;
}
}
}
return false;
}
public boolean isPrimitiveType(String code) {
return context.isPrimitiveType(code);
}
private String getErrorMessage(String message) {
return message != null ? " (error message = " + message + ")" : "";
}
public boolean isSuppressLoincSnomedMessages() {
return suppressLoincSnomedMessages;
}
private boolean nameMatches(String name, String tail) {
if (tail.endsWith("[x]"))
return name.startsWith(tail.substring(0, tail.length() - 3));
else
return (name.equals(tail));
}
private boolean passesCodeWhitespaceRules(String v) {
if (!Utilities.trimWS(v).equals(v))
return false;
boolean lastWasSpace = true;
for (char c : v.toCharArray()) {
if (c == ' ') {
if (lastWasSpace)
return false;
else
lastWasSpace = true;
} else if (Utilities.isWhitespace(c))
return false;
else
lastWasSpace = false;
}
return true;
}
public ResolvedReference localResolve(String ref, NodeStack stack, List errors, String path, Element rootResource, Element groupingResource, Element source, BooleanHolder bh) {
if (ref.startsWith("#")) {
// work back through the parent list, tracking the stack as we go
// really, there should only be one level for this (contained resources cannot contain
// contained resources), but we'll leave that to some other code to worry about
boolean wasContained = false;
Element focus = stack.getElement();
NodeStack nstack = stack;
while (focus != null) {
if (focus.getProperty().isResource()) {
// ok, we'll try to find the contained reference
if (ref.equals("#") && focus.getSpecial() != SpecialElement.CONTAINED && wasContained) {
ResolvedReference rr = new ResolvedReference();
rr.setResource(focus);
rr.setFocus(focus);
rr.setExternal(false);
rr.setStack(nstack);
// rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
// System.out.println("-->"+nstack.getLiteralPath());
return rr;
}
if (focus.getSpecial() == SpecialElement.CONTAINED) {
wasContained = true;
}
IndexedElement res = getContainedById(focus, ref.substring(1));
if (res != null) {
ResolvedReference rr = new ResolvedReference();
rr.setResource(focus);
rr.setFocus(res.getMatch());
rr.setExternal(false);
rr.setStack(nstack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
rr.getStack().pathComment(res.getMatch().fhirType()+"/"+res.getMatch().getIdBase());
return rr;
}
}
if (focus.getSpecial() == SpecialElement.BUNDLE_ENTRY || focus.getSpecial() == SpecialElement.PARAMETER) {
return null; // we don't try to resolve contained references across this boundary
}
focus = focus.getParentForValidator();
nstack = nstack.getParent();
if (focus != null && nstack == null) {
// we have run off the bottom of the stack, need to fake something
nstack = new NodeStack(context, focus, focus.fhirType(), validationLanguage);
}
}
return null;
} else {
// work back through the parent list - if any of them are bundles, try to resolve
// the resource in the bundle
// 2024-04-05 - must work through the element parents not the stack parents, as the stack is not necessarily reflective of the full parent list
Element focus = stack.getElement();
String fullUrl = null; // we're going to try to work this out as we go up
while (focus != null) {
// track the stack while we can
if (focus.getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && focus != null && focus.getParentForValidator() != null && focus.getParentForValidator().getName().equals(ENTRY)) {
String type = focus.getParentForValidator().getChildValue(TYPE);
fullUrl = focus.getParentForValidator().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary
if (fullUrl == null)
bh.see(rule(errors, NO_RULE_DATE, IssueType.REQUIRED, focus.getParentForValidator().line(), focus.getParentForValidator().col(), focus.getParentForValidator().getPath(),
Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL));
}
if (BUNDLE.equals(focus.getType())) {
String type = focus.getChildValue(TYPE);
IndexedElement res = getFromBundle(focus, ref, fullUrl, errors, path, type, "transaction".equals(type), bh);
if (res == null) {
return null;
} else {
ResolvedReference rr = new ResolvedReference();
rr.setResource(res.getMatch());
rr.setFocus(res.getMatch());
rr.setExternal(false);
rr.setStack(new NodeStack(context, null, res.getMatch(), validationLanguage));
rr.setVia(stack);
//
// !stack.push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
// res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
// res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
rr.getStack().pathComment(rr.getResource().fhirType()+"/"+rr.getResource().getIdBase());
return rr;
}
}
if (focus.getSpecial() == SpecialElement.PARAMETER && focus.getParentForValidator() != null) {
NodeStack tgt = findInParams(findParameters(focus), ref, stack);
if (tgt != null) {
ResolvedReference rr = new ResolvedReference();
rr.setResource(tgt.getElement());
rr.setFocus(tgt.getElement());
rr.setExternal(false);
rr.setStack(tgt);
rr.getStack().pathComment(tgt.getElement().fhirType()+"/"+tgt.getElement().getIdBase());
return rr;
}
}
focus = focus.getParentForValidator();
}
// we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity.
if (groupingResource != null && BUNDLE.equals(groupingResource.fhirType())) { // it could also be a Parameters resource - that case isn't handled yet
String type = groupingResource.getChildValue(TYPE);
Element entry = getEntryForSource(groupingResource, source);
fullUrl = entry.getChildValue(FULL_URL);
IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type), bh);
if (res == null) {
return null;
} else {
ResolvedReference rr = new ResolvedReference();
rr.setResource(res.getMatch());
rr.setFocus(res.getMatch());
rr.setExternal(false);
rr.setStack(new NodeStack(context, null, rootResource, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
rr.getStack().pathComment(rr.getResource().fhirType()+"/"+rr.getResource().getIdBase());
return rr;
}
}
}
return null;
}
private Element findParameters(Element focus) {
while (focus != null) {
if ("Parameters".equals(focus.fhirType())) {
return focus;
}
focus = focus.getParentForValidator();
}
return null;
}
private NodeStack findInParams(Element params, String ref, NodeStack stack) {
if (params == null) {
return null;
}
int i = 0;
for (Element child : params.getChildren("parameter")) {
NodeStack p = stack.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
if (child.hasChild("resource", false)) {
Element res = child.getNamedChild("resource", false);
if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
}
}
NodeStack pc = findInParamParts(p, child, ref);
if (pc != null) {
return pc;
}
}
return null;
}
private NodeStack findInParamParts(NodeStack pp, Element param, String ref) {
int i = 0;
for (Element child : param.getChildren("part")) {
NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
if (child.hasChild("resource", false)) {
Element res = child.getNamedChild("resource", false);
if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
}
}
NodeStack pc = findInParamParts(p, child, ref);
if (pc != null) {
return pc;
}
}
return null;
}
private Element getEntryForSource(Element bundle, Element element) {
List entries = new ArrayList();
bundle.getNamedChildren(ENTRY, entries);
for (Element entry : entries) {
if (entry.hasDescendant(element)) {
return entry;
}
}
return null;
}
private ResolvedReference makeExternalRef(Element external, String path) {
ResolvedReference res = new ResolvedReference();
res.setResource(external);
res.setFocus(external);
res.setExternal(true);
res.setStack(new NodeStack(context, external, path, validationLanguage));
return res;
}
private Element resolve(Object appContext, String ref, NodeStack stack, List errors, String path, BooleanHolder bh) throws IOException, FHIRException {
Element local = localResolve(ref, stack, errors, path, null, null, null, bh).getFocus();
if (local != null)
return local;
if (fetcher == null)
return null;
if (fetchCache.containsKey(ref)) {
return fetchCache.get(ref);
} else {
Element res = fetcher.fetch(this, appContext, ref);
setParents(res);
fetchCache.put(ref, res);
return res;
}
}
private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) {
for (ElementDefinition ed : snapshot.getElement())
if (contentReference.equals("#" + ed.getId()))
return ed;
return null;
}
private StructureDefinition resolveProfile(StructureDefinition profile, String pr) {
if (pr.startsWith("#")) {
for (Resource r : profile.getContained()) {
if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition)
return (StructureDefinition) r;
}
return null;
} else {
long t = System.nanoTime();
StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr, profile);
timeTracker.sd(t);
return fr;
}
}
private ElementDefinition resolveType(String type, List list) {
for (TypeRefComponent tr : list) {
String url = tr.getWorkingCode();
if (!Utilities.isAbsoluteUrl(url))
url = "http://hl7.org/fhir/StructureDefinition/" + url;
long t = System.nanoTime();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
timeTracker.sd(t);
if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot())
return sd.getSnapshot().getElement().get(0);
if (sd != null) {
StructureDefinition sdt = context.fetchTypeDefinition(type);
if (inheritsFrom(sdt, sd) && sdt.hasSnapshot()) {
return sdt.getSnapshot().getElement().get(0);
}
}
}
return null;
}
private boolean inheritsFrom(StructureDefinition sdt, StructureDefinition sd) {
while (sdt != null) {
if (sdt == sd) {
return true;
}
sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition());
}
return false;
}
public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) {
this.anyExtensionsAllowed = anyExtensionsAllowed;
}
@Override
public void setCheckDisplay(CheckDisplayOption checkDisplay) {
this.checkDisplay = checkDisplay;
}
public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) {
this.suppressLoincSnomedMessages = suppressLoincSnomedMessages;
}
public IdStatus getResourceIdRule() {
return resourceIdRule;
}
public void setResourceIdRule(IdStatus resourceIdRule) {
this.resourceIdRule = resourceIdRule;
}
public boolean isAllowXsiLocation() {
return allowXsiLocation;
}
public void setAllowXsiLocation(boolean allowXsiLocation) {
this.allowXsiLocation = allowXsiLocation;
}
/**
* @param element - the candidate that might be in the slice
* @param path - for reporting any errors. the XPath for the element
* @param slicer - the definition of how slicing is determined
* @param ed - the slice for which to test membership
* @param errors
* @param stack
* @param srcProfile
* @return
* @throws DefinitionException
* @throws DefinitionException
* @throws IOException
* @throws FHIRException
*/
private boolean sliceMatches(ValidationContext valContext, Element element, String path, ElementDefinition slicer, List slicerSlices, ElementDefinition ed, StructureDefinition profile, List errors, List sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException {
if (!slicer.getSlicing().hasDiscriminator())
return false; // cannot validate in this case
ExpressionNode n = (ExpressionNode) ed.getUserData(UserDataNames.validator_slice_expression_cache);
if (n == null) {
long t = System.nanoTime();
// GG: this approach is flawed because it treats discriminators individually rather than collectively
StringBuilder expression = new StringBuilder("true");
boolean anyFound = false;
Set discriminators = new HashSet<>();
for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) {
String discriminator = s.getPath();
discriminators.add(discriminator);
List criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile);
boolean found = false;
for (ElementDefinition criteriaElement : criteriaElements) {
found = true;
if ("0".equals(criteriaElement.getMax())) {
expression.append(" and " + discriminator + ".empty()");
} else if (s.getType() == DiscriminatorType.TYPE) {
if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) {
discriminator = discriminator.substring(0, discriminator.indexOf('['));
String lastNode = tail(discriminator);
String type = makeTypeForFHIRPath(criteriaElement.getPath()).substring(lastNode.length());
expression.append(" and " + discriminator + " is " + type);
} else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) {
String type = null;
if (discriminator.contains("["))
discriminator = discriminator.substring(0, discriminator.indexOf('['));
if (criteriaElement.hasType()) {
type = makeTypeForFHIRPath(criteriaElement.getType().get(0).getWorkingCode());
} else if (!criteriaElement.getPath().contains(".")) {
type = makeTypeForFHIRPath(criteriaElement.getPath());
} else {
throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl()));
}
expression.append(" and " + discriminator + " is " + type);
} else if (criteriaElement.getType().size() > 1) {
CommaSeparatedStringBuilder cb = new CommaSeparatedStringBuilder(" or ");
for (TypeRefComponent tr : criteriaElement.getType()) {
String type = makeTypeForFHIRPath(tr.getWorkingCode());
cb.append(discriminator + " is " + type);
}
expression.append(" and (" + cb.toString()+")");
} else
throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl()));
} else if (s.getType() == DiscriminatorType.PROFILE) {
if (criteriaElement.getType().size() == 0) {
throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl()));
}
List list = new ArrayList<>();
boolean ref = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()");
for (TypeRefComponent tr : criteriaElement.getType()) {
list.addAll(ref ? tr.getTargetProfile() : tr.getProfile());
}
if (list.size() == 0) {
// we don't have to find something
// throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl()));
} else if (list.size() > 1) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or ");
for (CanonicalType c : list) {
b.append(discriminator + ".conformsTo('" + c.getValue() + "')");
}
expression.append(" and (" + b + ")");
} else {
expression.append(" and " + discriminator + ".conformsTo('" + list.get(0).getValue() + "')");
}
} else if (s.getType() == DiscriminatorType.EXISTS) {
if (criteriaElement.hasMin() && criteriaElement.getMin() >= 1) {
expression.append(" and (" + discriminator + ".exists())");
} else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) {
expression.append(" and (" + discriminator + ".exists().not())");
} else {
throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0, discriminator, ed.getId()));
}
} else if (s.getType() == DiscriminatorType.POSITION) {
// we don't evaluate this one using FHIRPath, and it can't share
if (slicer.getSlicing().getDiscriminator().size() != 1) {
throw new DefinitionException(context.formatMessagePlural(slicer.getSlicing().getDiscriminator().size(), I18nConstants.Could_not_match_discriminator_for_slice_in_profile, discriminators, ed.getId(), profile.getVersionedUrl(), discriminators));
} else {
int offset = 0;
for (ElementDefinition ts : slicerSlices) {
if (ts == ed) {
break;
} else if (!ts.getMax().equals(Integer.toString(ts.getMin()))) {
throw new DefinitionException(context.formatMessagePlural(slicer.getSlicing().getDiscriminator().size(), I18nConstants.Could_not_match_discriminator_for_slice_in_profile, discriminators, ed.getId(), profile.getVersionedUrl(), discriminators));
} else {
offset = offset + ts.getMin();
}
}
int maxPos = (ed.getMax().equals("*") ? Integer.MAX_VALUE : offset + Integer.parseInt(ed.getMax()));
int position = path.endsWith("]") ? Integer.parseInt(path.substring(path.lastIndexOf("[")+1).replace("]", "")) : 0;
return position >= offset && position < maxPos;
}
} else if (criteriaElement.hasFixed()) {
buildFixedExpression(ed, expression, discriminator, criteriaElement);
} else if (criteriaElement.hasPattern()) {
buildPattternExpression(ed, expression, discriminator, criteriaElement);
} else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().hasValueSet()) {
expression.append(" and (" + discriminator + " memberOf '" + criteriaElement.getBinding().getValueSet() + "')");
} else {
found = false;
}
if (found)
break;
}
if (found)
anyFound = true;
}
if (!anyFound) {
throw new DefinitionException(context.formatMessagePlural(slicer.getSlicing().getDiscriminator().size(), I18nConstants.Could_not_match_discriminator_for_slice_in_profile, discriminators, ed.getId(), profile.getVersionedUrl(), discriminators));
}
try {
n = fpe.parse(FHIRPathExpressionFixer.fixExpr(expression.toString(), null, context.getVersion()));
} catch (FHIRLexerException e) {
if (STACK_TRACE) e.printStackTrace();
throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, expression, profile.getVersionedUrl(), path, e.getMessage()));
}
timeTracker.fpe(t);
ed.setUserData(UserDataNames.validator_slice_expression_cache, n);
} else {
}
ValidationContext shc = valContext.forSlicing();
boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
if (!pass) {
slicingHint(sliceInfo, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName(), n.toString().substring(8).trim())), "discriminator = " + Utilities.escapeXml(n.toString()), null);
for (String url : shc.getSliceRecords().keySet()) {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, url);
slicingHint(sliceInfo, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer),
context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), sdt == null ? url : sdt.getVersionedUrl()),
context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__,
url,
stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url))), errorSummaryForSlicingAsText(shc.getSliceRecords().get(url)));
}
}
return pass;
}
private String makeTypeForFHIRPath(String type) {
if (Utilities.isAbsoluteUrl(type)) {
if (type.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
return typeTail(type);
} else if (type.startsWith("http://hl7.org/cda/stds/core/StructureDefinition/")) {
String tt = typeTail(type);
if (tt.contains("-")) {
tt = '`'+tt.replace("-", "_")+'`';
}
return "CDA."+tt;
} else {
return typeTail(type); // todo?
}
} else {
String ptype = type.substring(0, 1).toLowerCase() + type.substring(1);
if (context.isPrimitiveType(ptype)) {
return ptype;
} else {
return type;
}
}
}
private String typeTail(String type) {
return type.contains("/") ? type.substring(type.lastIndexOf("/")+1) : type;
}
private boolean isBaseDefinition(String url) {
boolean b = url.startsWith("http://hl7.org/fhir/") && !url.substring(40).contains("/");
return b;
}
private String descSD(String url) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
return sd == null ? url : sd.present();
}
private boolean isProfile(ElementDefinition slicer) {
if (slicer == null || !slicer.hasSlicing()) {
return false;
}
for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) {
if (t.getType() == DiscriminatorType.PROFILE) {
return true;
}
}
return false;
}
public boolean evaluateSlicingExpression(ValidationContext valContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
String msg;
boolean pass;
try {
long t = System.nanoTime();
pass = fpe.evaluateToBoolean(valContext.forProfile(profile), valContext.getResource(), valContext.getRootResource(), element, n);
timeTracker.fpe(t);
msg = fpe.forLog();
} catch (Exception ex) {
if (STACK_TRACE) ex.printStackTrace();
throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_EVALUATING_SLICING_EXPRESSION_FOR_ELEMENT_IN_PROFILE__PATH__FHIRPATH___, profile.getVersionedUrl(), path, n, ex.getMessage()));
}
return pass;
}
private void buildPattternExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException {
DataType pattern = criteriaElement.getPattern();
if (pattern instanceof CodeableConcept) {
CodeableConcept cc = (CodeableConcept) pattern;
expression.append(" and ");
buildCodeableConceptExpression(ed, expression, discriminator, cc);
} else if (pattern instanceof Coding) {
Coding c = (Coding) pattern;
expression.append(" and ");
buildCodingExpression(ed, expression, discriminator, c);
} else if (pattern instanceof BooleanType || pattern instanceof IntegerType || pattern instanceof DecimalType) {
expression.append(" and ");
buildPrimitiveExpression(ed, expression, discriminator, pattern, false);
} else if (pattern instanceof PrimitiveType) {
expression.append(" and ");
buildPrimitiveExpression(ed, expression, discriminator, pattern, true);
} else if (pattern instanceof Identifier) {
Identifier ii = (Identifier) pattern;
expression.append(" and ");
buildIdentifierExpression(ed, expression, discriminator, ii);
} else if (pattern instanceof HumanName) {
HumanName name = (HumanName) pattern;
expression.append(" and ");
buildHumanNameExpression(ed, expression, discriminator, name);
} else if (pattern instanceof Address) {
Address add = (Address) pattern;
expression.append(" and ");
buildAddressExpression(ed, expression, discriminator, add);
} else {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_PATTERN_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), pattern.fhirType()));
}
}
private void buildIdentifierExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Identifier ii)
throws DefinitionException {
if (ii.hasExtension())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
boolean first = true;
expression.append(discriminator + ".where(");
if (ii.hasSystem()) {
first = false;
expression.append("system = '" + ii.getSystem() + "'");
}
if (ii.hasValue()) {
if (first)
first = false;
else
expression.append(" and ");
expression.append("value = '" + ii.getValue() + "'");
}
if (ii.hasUse()) {
if (first)
first = false;
else
expression.append(" and ");
expression.append("use = '" + ii.getUse() + "'");
}
if (ii.hasType()) {
if (first)
first = false;
else
expression.append(" and ");
buildCodeableConceptExpression(ed, expression, TYPE, ii.getType());
}
if (first) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), ii.fhirType()));
}
expression.append(").exists()");
}
private void buildHumanNameExpression(ElementDefinition ed, StringBuilder expression, String discriminator, HumanName name) throws DefinitionException {
if (name.hasExtension())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
boolean first = true;
expression.append(discriminator + ".where(");
if (name.hasUse()) {
first = false;
expression.append("use = '" + name.getUse().toCode() + "'");
}
if (name.hasText()) {
if (first)
first = false;
else
expression.append(" and ");
expression.append("text = '" + name.getText() + "'");
}
if (name.hasFamily()) {
if (first)
first = false;
else
expression.append(" and ");
expression.append("family = '" + name.getFamily() + "'");
}
if (name.hasGiven()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "given"));
}
if (name.hasPrefix()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "prefix"));
}
if (name.hasSuffix()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "suffix"));
}
if (name.hasPeriod()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "period"));
}
if (first) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType()));
}
expression.append(").exists()");
}
private void buildAddressExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Address add) throws DefinitionException {
if (add.hasExtension()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
}
boolean first = true;
expression.append(discriminator + ".where(");
if (add.hasUse()) {
first = false;
expression.append("use = '" + add.getUse().toCode() + "'");
}
if (add.hasType()) {
if (first) first = false; else expression.append(" and ");
expression.append("type = '" + add.getType().toCode() + "'");
}
if (add.hasText()) {
if (first) first = false; else expression.append(" and ");
expression.append("text = '" + add.getText() + "'");
}
if (add.hasCity()) {
if (first) first = false; else expression.append(" and ");
expression.append("city = '" + add.getCity() + "'");
}
if (add.hasDistrict()) {
if (first) first = false; else expression.append(" and ");
expression.append("district = '" + add.getDistrict() + "'");
}
if (add.hasState()) {
if (first) first = false; else expression.append(" and ");
expression.append("state = '" + add.getState() + "'");
}
if (add.hasPostalCode()) {
if (first) first = false; else expression.append(" and ");
expression.append("postalCode = '" + add.getPostalCode() + "'");
}
if (add.hasCountry()) {
if (first) first = false; else expression.append(" and ");
expression.append("country = '" + add.getCountry() + "'");
}
if (add.hasLine()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "line"));
}
if (add.hasPeriod()) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "period"));
}
if (first) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType()));
}
expression.append(").exists()");
}
private void buildCodeableConceptExpression(ElementDefinition ed, StringBuilder expression, String discriminator, CodeableConcept cc)
throws DefinitionException {
if (cc.hasText())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__USING_TEXT__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
if (!cc.hasCoding())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__MUST_HAVE_AT_LEAST_ONE_CODING__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
if (cc.hasExtension())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
boolean firstCoding = true;
for (Coding c : cc.getCoding()) {
if (c.hasExtension())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
if (firstCoding) firstCoding = false;
else expression.append(" and ");
expression.append(discriminator + ".coding.where(");
boolean first = true;
if (c.hasSystem()) {
first = false;
expression.append("system = '" + c.getSystem() + "'");
}
if (c.hasVersion()) {
if (first) first = false;
else expression.append(" and ");
expression.append("version = '" + c.getVersion() + "'");
}
if (c.hasCode()) {
if (first) first = false;
else expression.append(" and ");
expression.append("code = '" + c.getCode() + "'");
}
if (c.hasDisplay()) {
if (first) first = false;
else expression.append(" and ");
expression.append("display = '" + c.getDisplay() + "'");
}
if (first) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), cc.fhirType()));
}
expression.append(").exists()");
}
}
private void buildCodingExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Coding c)
throws DefinitionException {
if (c.hasExtension())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
expression.append(discriminator + ".where(");
boolean first = true;
if (c.hasSystem()) {
first = false;
expression.append("system = '" + c.getSystem() + "'");
}
if (c.hasVersion()) {
if (first) first = false;
else expression.append(" and ");
expression.append("version = '" + c.getVersion() + "'");
}
if (c.hasCode()) {
if (first) first = false;
else expression.append(" and ");
expression.append("code = '" + c.getCode() + "'");
}
if (c.hasDisplay()) {
if (first) first = false;
else expression.append(" and ");
expression.append("display = '" + c.getDisplay() + "'");
}
if (first) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), c.fhirType()));
}
expression.append(").exists()");
}
private void buildPrimitiveExpression(ElementDefinition ed, StringBuilder expression, String discriminator, DataType p, boolean quotes) throws DefinitionException {
if (p.hasExtension())
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
if (quotes) {
expression.append(discriminator + ".where(value = '" + p.primitiveValue() + "'");
} else {
expression.append(discriminator + ".where(value = " + p.primitiveValue() + "");
}
expression.append(").exists()");
}
private void buildFixedExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException {
DataType fixed = criteriaElement.getFixed();
if (fixed instanceof CodeableConcept) {
CodeableConcept cc = (CodeableConcept) fixed;
expression.append(" and ");
buildCodeableConceptExpression(ed, expression, discriminator, cc);
} else if (fixed instanceof Identifier) {
Identifier ii = (Identifier) fixed;
expression.append(" and ");
buildIdentifierExpression(ed, expression, discriminator, ii);
} else if (fixed instanceof Coding) {
Coding c = (Coding) fixed;
expression.append(" and ");
buildCodingExpression(ed, expression, discriminator, c);
} else {
expression.append(" and (");
if (fixed instanceof StringType) {
String es = Utilities.escapeJson(fixed.primitiveValue());
expression.append("'" + es + "'");
} else if (fixed instanceof UriType) {
expression.append("'" + ((UriType) fixed).asStringValue() + "'");
} else if (fixed instanceof IntegerType) {
expression.append(((IntegerType) fixed).asStringValue());
} else if (fixed instanceof DecimalType) {
expression.append(((IntegerType) fixed).asStringValue());
} else if (fixed instanceof BooleanType) {
expression.append(((BooleanType) fixed).asStringValue());
} else
throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_VALUE_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), fixed.getClass().getName()));
expression.append(" in " + discriminator + ")");
}
}
// checkSpecials = we're only going to run these tests if we are actually validating this content (as opposed to we looked it up)
private boolean start(ValidationContext valContext, List errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, PercentageTracker pct, ValidationMode mode, boolean fromContained) throws FHIRException {
boolean ok = !hasErrors(errors);
checkLang(resource, stack);
if (crumbTrails) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, defn.getVersionedUrl()));
}
boolean pctOwned = false;
if (pct == null) {
// this method is reentrant, but also the right place to tell the user what is going on if it's the root.
// if we're not at the root, we don't report progress
pctOwned = true;
pct = new PercentageTracker(resource.countDescendents()+1, resource.fhirType(), defn.getVersionedUrl(), logProgress);
}
if (BUNDLE.equals(element.fhirType())) {
if (debug) {
System.out.println("Resolve Bundle Entries "+time());
}
resolveBundleReferences(element, new ArrayList());
}
ok = startInner(valContext, errors, resource, element, defn, stack, valContext.isCheckSpecials(), pct, mode, fromContained) && ok;
if (pctOwned) {
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(valContext, errors, resource, element, sdi, stack, false, pct, mode.withSource(ProfileSource.ProfileDependency), fromContained) && ok;
if (pctOwned) {
pct.done();
}
}
}
}
Element meta = element.getNamedChild(META, false);
if (meta != null) {
List profiles = new ArrayList();
meta.getNamedChildren("profile", profiles);
int i = 0;
for (Element profile : profiles) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue());
if (!defn.getUrl().equals(profile.primitiveValue())) {
// is this a version specific reference?
VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue());
if (vu != null) {
if (!VersionUtilities.versionsCompatible(vu.getVersion(), context.getVersion())) {
hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_OTHER_VERSION, vu.getVersion());
} else if (vu.getUrl().equals(defn.getUrl())) {
hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK);
} else {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl());
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER, sdt == null ? "null" : sdt.getType()) && ok;
}
} else {
if (sd == null) {
// we'll try fetching it directly from it's source, but this is likely to fail later even if the resolution succeeds
if (fetcher == null) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue());
} else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue());
} else {
sd = lookupProfileReference(valContext, errors, element, stack, i, profile, sd);
}
}
if (sd != null) {
if (crumbTrails) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getVersionedUrl()));
}
stack.resetIds();
if (pctOwned) {
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getUrl(), logProgress);
}
ok = startInner(valContext, errors, resource, element, sd, stack, false, pct, mode.withSource(ProfileSource.MetaProfile), fromContained) && ok;
if (pctOwned) {
pct.done();
}
if (sd.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
for (Extension ext : sd.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() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED, ext.getValue().primitiveValue(), sd.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(), sd.getVersionedUrl()));
}
stack.resetIds();
if (pctOwned) {
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sdi.getUrl(), logProgress);
}
ok = startInner(valContext, errors, resource, element, sdi, stack, false, pct, mode.withSource(ProfileSource.ProfileDependency), fromContained) && ok;
if (pctOwned) {
pct.done();
}
}
}
}
}
}
}
i++;
}
}
String rt = element.fhirType();
for (ImplementationGuide ig : igs) {
for (ImplementationGuideGlobalComponent gl : ig.getGlobal()) {
if (rt.equals(gl.getType())) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, gl.getProfile(), ig);
if (warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), sd != null, I18nConstants.VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN, gl.getProfile(), ig.getVersionedUrl())) {
if (crumbTrails) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL, sd.getVersionedUrl(), ig.getVersionedUrl()));
}
stack.resetIds();
if (pctOwned) {
pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getVersionedUrl(), logProgress);
}
ok = startInner(valContext, errors, resource, element, sd, stack, false, pct, mode.withSource(ProfileSource.GlobalProfile), fromContained) && ok;
if (pctOwned) {
pct.done();
}
}
}
}
}
// System.out.println("start: "+(System.currentTimeMillis()-st)+" ("+resource.fhirType()+")");
return ok;
}
private StructureDefinition lookupProfileReference(ValidationContext valContext, List errors, Element element, NodeStack stack,
int i, Element profile, StructureDefinition sd) {
String url = profile.primitiveValue();
CanonicalResourceLookupResult cr = crLookups.get(url);
if (cr != null) {
if (cr.getError() != null) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, url, cr.getError());
} else {
sd = (StructureDefinition) cr.getResource();
}
} else {
try {
sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, valContext.getAppContext(), url);
crLookups.put(url, new CanonicalResourceLookupResult(sd));
} catch (Exception e) {
if (STACK_TRACE) { e.printStackTrace(); }
crLookups.put(url, new CanonicalResourceLookupResult(e.getMessage()));
if (e.getMessage() != null && e.getMessage().startsWith("java.net.UnknownHostException:")) {
try {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR_NETWORK, profile.primitiveValue(), new URI(url).getHost());
} catch (URISyntaxException e1) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR_NETWORK, profile.primitiveValue(), "??");
}
} else {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage());
}
}
if (sd != null) {
context.cacheResource(sd);
}
}
return sd;
}
// private void plog(String msg) {
// long n = System.currentTimeMillis();
// String elapsed = Utilities.padLeft(Long.toString(n-start), ' ', 5);
// String delta = Utilities.padLeft(lastlog == 0 ? "0" : Long.toString(n-lastlog), ' ', 5);
// lastlog = n;
// System.out.println("-- "+elapsed+" "+delta+" "+msg);
// }
private void resolveBundleReferences(Element element, List bundles) {
if (!element.hasUserData(UserDataNames.validator_bundle_resolved)) {
element.setUserData(UserDataNames.validator_bundle_resolved, true);
List list = new ArrayList();
list.addAll(bundles);
list.add(0, element);
List entries = element.getChildrenByName(ENTRY);
for (Element entry : entries) {
String fu = entry.getChildValue(FULL_URL);
Element r = entry.getNamedChild(RESOURCE, false);
if (r != null) {
resolveBundleReferencesInResource(list, r, fu);
}
}
}
}
private void resolveBundleReferencesInResource(List bundles, Element r, String fu) {
if (BUNDLE.equals(r.fhirType())) {
resolveBundleReferences(r, bundles);
} else {
for (Element child : r.getChildren()) {
resolveBundleReferencesForElement(bundles, r, fu, child);
}
}
}
private void resolveBundleReferencesForElement(List bundles, Element resource, String fu, Element element) {
if ("Reference".equals(element.fhirType())) {
String ref = element.getChildValue("reference");
if (!Utilities.noString(ref)) {
for (Element bundle : bundles) {
List entries = bundle.getChildren(ENTRY);
Element tgt = resolveInBundle(bundle, entries, ref, fu, resource.fhirType(), resource.getIdBase(), null, null, null, element, false, false);
if (tgt != null) {
element.setUserData(UserDataNames.validator_bundle_resolution, tgt.getNamedChild(RESOURCE, false));
return;
}
}
}
} else {
for (Element child : element.getChildren()) {
resolveBundleReferencesForElement(bundles, resource, fu, child);
}
}
}
private static void addMessagesReplaceExistingIfMoreSevere(List errors, List newErrors) {
for (ValidationMessage newError : newErrors) {
int index = indexOfMatchingMessageAndLocation(errors, newError);
if (index == -1) {
errors.add(newError);
} else {
ValidationMessage existingError = errors.get(index);
if (newError.getLevel().ordinal() < existingError.getLevel().ordinal()) {
errors.set(index, newError);
}
}
}
}
private static int indexOfMatchingMessageAndLocation(List messages, ValidationMessage message) {
for (int i = 0; i < messages.size(); i++) {
ValidationMessage iMessage = messages.get(i);
if (message.getMessage() != null && message.getMessage().equals(iMessage.getMessage())
&& message.getLocation() != null && message.getLocation().equals(iMessage.getLocation())) {
return i;
}
}
return -1;
}
public boolean startInner(ValidationContext valContext, List errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode, boolean fromContained) {
// the first piece of business is to see if we've validated this resource against this profile before.
// if we have (*or if we still are*), then we'll just return our existing errors
boolean ok = true;
ResourceValidationTracker resTracker = getResourceTracker(element);
List cachedErrors = resTracker.getOutcomes(defn);
if (cachedErrors != null) {
for (ValidationMessage vm : cachedErrors) {
if (!errors.contains(vm)) {
errors.add(vm);
ok = vm.getLevel() != IssueSeverity.ERROR && vm.getLevel() != IssueSeverity.FATAL && ok;
}
}
return ok;
}
if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getVersionedUrl())) {
List localErrors = new ArrayList();
resTracker.startValidating(defn);
trackUsage(defn, valContext, element);
ok = validateElement(valContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null, pct, mode) && ok;
resTracker.storeOutcomes(defn, localErrors);
addMessagesReplaceExistingIfMoreSevere(errors, localErrors);
} else {
ok = false;
}
if (checkSpecials) {
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 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 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())) {
Base base = element.getExtensionValue(ToolingExtensions.EXT_STANDARDS_STATUS);
String standardsStatus = base != null && base.isPrimitive() ? base.primitiveValue() : null;
String status = element.getNamedChildValue("status", false);
if (!Utilities.noString(status) && !Utilities.noString(standardsStatus)) {
if (warning(errors, "2023-08-14", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), statusCodesConsistent(status, standardsStatus), I18nConstants.VALIDATION_VAL_STATUS_INCONSISTENT, status, standardsStatus)) {
hint(errors, "2023-08-14", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), statusCodesDeeplyConsistent(status, standardsStatus), I18nConstants.VALIDATION_VAL_STATUS_INCONSISTENT_HINT, status, standardsStatus);
}
}
if (noExperimentalContent) {
String exp = element.getNamedChildValue("experimental");
ok = rule(errors, "2024-09-17", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), !"true".equals(exp), I18nConstants.VALIDATION_NO_EXPERIMENTAL_CONTENT) && ok;
}
if (isHL7Core(element) && !isExample()) {
ok = checkPublisherConsistency(valContext, errors, element, stack, contained) && ok;
}
String version = element.getNamedChildValue("version");
if (!Utilities.noString(version)) {
warning(errors, "2024-10-18", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), !version.contains("#"), I18nConstants.VALIDATION_VAL_VERSION_NOHASH, version);
}
}
if (element.getType().equals(BUNDLE)) {
return new BundleValidator(this, serverBase).validateBundle(errors, element, stack, checkSpecials, valContext, pct, mode) && ok;
} else if (element.getType().equals("Observation")) {
return new ObservationValidator(this).validateObservation(valContext, errors, element, stack, pct, mode) && ok;
} else if (element.getType().equals("Questionnaire")) {
return new QuestionnaireValidator(this, myEnableWhenEvaluator, fpe, questionnaireMode).validateQuestionannaire(errors, element, element, stack) && ok;
} else if (element.getType().equals("QuestionnaireResponse")) {
return new QuestionnaireValidator(this, myEnableWhenEvaluator, fpe, questionnaireMode).validateQuestionannaireResponse(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("Measure")) {
return new MeasureValidator(this).validateMeasure(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("MeasureReport")) {
return new MeasureValidator(this).validateMeasureReport(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("CapabilityStatement")) {
return validateCapabilityStatement(errors, element, stack) && ok;
} else if (element.getType().equals("CodeSystem")) {
return new CodeSystemValidator(this).validateCodeSystem(valContext, errors, element, stack, baseOptions.withLanguage(stack.getWorkingLang())) && ok;
} else if (element.getType().equals("ConceptMap")) {
return new ConceptMapValidator(this).validateConceptMap(valContext, errors, element, stack, baseOptions.withLanguage(stack.getWorkingLang())) && ok;
} else if (element.getType().equals("SearchParameter")) {
return new SearchParameterValidator(this, fpe).validateSearchParameter(errors, element, stack) && ok;
} else if (element.getType().equals("StructureDefinition")) {
return new StructureDefinitionValidator(this, fpe, wantCheckSnapshotUnchanged).validateStructureDefinition(errors, element, stack) && ok;
} else if (element.getType().equals("StructureMap")) {
return new StructureMapValidator(this, fpe, profileUtilities).validateStructureMap(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("ValueSet")) {
return new ValueSetValidator(this).validateValueSet(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("ViewDefinition")) {
return new ViewDefinitionValidator(this).validateViewDefinition(valContext, errors, element, stack) && ok;
} else if (element.getType().equals("ImplementationGuide")) {
return new ImplementationGuideValidator(this.context, xverManager, debug, session).validateImplementationGuide(valContext, errors, element, stack) && ok;
} else if ("http://hl7.org/fhir/uv/sql-on-fhir/StructureDefinition/ViewDefinition".equals(element.getProperty().getStructure().getUrl())) {
if (element.getNativeObject() != null && element.getNativeObject() instanceof JsonObject) {
JsonObject json = (JsonObject) element.getNativeObject();
Validator sqlv = new Validator(context, fpe, new ArrayList<>(), TrueFalseOrUnknown.UNKNOWN, TrueFalseOrUnknown.UNKNOWN, TrueFalseOrUnknown.UNKNOWN);
sqlv.checkViewDefinition(stack.getLiteralPath(), json);
errors.addAll(sqlv.getIssues());
ok = sqlv.isOk() && ok;
}
return ok;
} else {
return ok;
}
} finally {
timeTracker.spec(t);
}
}
private boolean checkPublisherConsistency(ValidationContext valContext, List errors, Element element, NodeStack stack, boolean contained) {
boolean ok = true;
String pub = element.getNamedChildValue("publisher", false);
ok = rule(errors, "2024-08-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), element.getExtensions(ToolingExtensions.EXT_WORKGROUP).size() <= 1, I18nConstants.VALIDATION_HL7_PUBLISHER_MULTIPLE_WGS) && ok;
Base wgT = element.getExtensionValue(ToolingExtensions.EXT_WORKGROUP);
String wg = wgT == null ? null : wgT.primitiveValue();
String url = element.getNamedChildValue("url");
if (contained && wg == null) {
Element container = valContext.getRootResource();
if (element.hasExtension(ToolingExtensions.EXT_WORKGROUP)) {
// container already specified the HL7 WG, so we don't need to test
// but we're still going to test pub if it exists
if (pub != null) {
wgT = container.getExtensionValue(ToolingExtensions.EXT_WORKGROUP);
wg = wgT == null ? null : wgT.primitiveValue();
HL7WorkGroup wgd = HL7WorkGroups.find(wg);
if (wgd != null) {
String rpub = "HL7 International / "+wgd.getName();
ok = rpub.equals(pub);
if (!ok && wgd.getName2() != null) {
ok = ("HL7 International / "+wgd.getName2()).equals(pub);
warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), ok, I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH2, wg, rpub, "HL7 International / "+wgd.getName2(), pub);
} else {
warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), ok, I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH, wg, rpub, pub);
}
}
}
return ok;
}
}
List urls = new ArrayList<>();
for (Element c : element.getChildren("contact")) {
for (Element t : c.getChildren("telecom")) {
if ("url".equals(t.getNamedChildValue("system", false)) && t.getNamedChildValue("value", false) != null) {
urls.add(t.getNamedChildValue("value", false));
}
}
}
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wg != null || url.contains("http://hl7.org/fhir/sid") || !forPublication, I18nConstants.VALIDATION_HL7_WG_NEEDED, ToolingExtensions.EXT_WORKGROUP)) {
if (wg != null) {
HL7WorkGroup wgd = HL7WorkGroups.find(wg);
if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wgd != null, I18nConstants.VALIDATION_HL7_WG_UNKNOWN, wg)) {
String rpub = "HL7 International / "+wgd.getName();
if (warning(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), pub != null, I18nConstants.VALIDATION_HL7_PUBLISHER_MISSING, wg, rpub)) {
ok = rpub.equals(pub) && ok;
if (!ok && wgd.getName2() != null) {
ok = ("HL7 International / "+wgd.getName2()).equals(pub) && ok;
warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), ok, I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH2, wg, rpub, "HL7 International / "+wgd.getName2(), pub);
} else {
warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), ok, I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH, wg, rpub, pub);
}
}
warning(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(),
Utilities.startsWithInList( wgd.getLink(), urls), I18nConstants.VALIDATION_HL7_WG_URL, wg, wgd.getLink());
return ok;
}
} else {
return ok; // HL7 sid.
}
}
return ok;
}
private boolean statusCodesConsistent(String status, String standardsStatus) {
switch (standardsStatus) {
case "draft": return Utilities.existsInList(status, "draft");
case "normative": return Utilities.existsInList(status, "active");
case "trial-use": return Utilities.existsInList(status, "draft", "active");
case "informative": return Utilities.existsInList(status, "draft", "active", "retired");
case "deprecated": return Utilities.existsInList(status, "retired");
case "withdrawn": return Utilities.existsInList(status, "retired");
case "external": return Utilities.existsInList(status, "draft", "active", "retired");
}
return true;
}
private boolean statusCodesDeeplyConsistent(String status, String standardsStatus) {
switch (standardsStatus) {
case "draft": return Utilities.existsInList(status, "draft");
case "normative": return Utilities.existsInList(status, "active");
case "trial-use": return Utilities.existsInList(status, "active");
case "informative": return Utilities.existsInList(status, "draft", "active");
case "deprecated": return Utilities.existsInList(status, "retired");
case "withdrawn": return Utilities.existsInList(status, "retired");
case "external": return Utilities.existsInList(status, "draft", "active");
}
return true;
}
private ResourceValidationTracker getResourceTracker(Element element) {
ResourceValidationTracker res = resourceTracker.get(element);
if (res == null) {
res = new ResourceValidationTracker();
resourceTracker.put(element, res);
}
return res;
}
private void checkLang(Element resource, NodeStack stack) {
String lang = resource.getNamedChildValue("language", false);
if (!Utilities.noString(lang))
stack.setWorkingLang(lang);
}
private boolean validateResourceRules(List errors, Element element, NodeStack stack) {
boolean ok= true;
String lang = element.getNamedChildValue("language", false);
Element text = element.getNamedChild("text", false);
if (text != null) {
Element div = text.getNamedChild("div", false);
if (lang != null && div != null) {
XhtmlNode xhtml = div.getXhtml();
if (xhtml != null) {
String l = xhtml.getAttribute("lang");
String xl = xhtml.getAttribute("xml:lang");
if (l == null && xl == null) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING1);
} else {
if (l == null) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING2);
} else if (!l.equals(lang)) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT1, lang, l);
}
if (xl == null) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING3);
} else if (!xl.equals(lang)) {
warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT2, lang, xl);
}
}
}
}
}
// security tags are a set (system|code)
Element meta = element.getNamedChild(META, false);
if (meta != null) {
Set tags = new HashSet<>();
List list = new ArrayList<>();
meta.getNamedChildren("security", list);
int i = 0;
for (Element e : list) {
String s = e.getNamedChildValue("system", false) + "#" + e.getNamedChildValue("code", false);
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, e.line(), e.col(), stack.getLiteralPath() + ".meta.profile[" + Integer.toString(i) + "]", !tags.contains(s), I18nConstants.META_RES_SECURITY_DUPLICATE, s) && ok;
tags.add(s);
i++;
}
}
return ok;
}
private boolean validateCapabilityStatement(List errors, Element cs, NodeStack stack) {
boolean ok = true;
int iRest = 0;
for (Element rest : cs.getChildrenByName("rest")) {
int iResource = 0;
for (Element resource : rest.getChildrenByName(RESOURCE)) {
int iSP = 0;
for (Element searchParam : resource.getChildrenByName("searchParam")) {
String ref = searchParam.getChildValue("definition");
String type = searchParam.getChildValue(TYPE);
if (!Utilities.noString(ref)) {
SearchParameter sp = context.fetchResource(SearchParameter.class, ref);
if (sp != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, searchParam.line(), searchParam.col(), stack.getLiteralPath() + ".rest[" + iRest + "].resource[" + iResource + "].searchParam[" + iSP + "]",
sp.getType().toCode().equals(type), I18nConstants.CAPABALITYSTATEMENT_CS_SP_WRONGTYPE, sp.getVersionedUrl(), sp.getType().toCode(), type) && ok;
}
}
iSP++;
}
iResource++;
}
iRest++;
}
return ok;
}
private boolean validateContains(ValidationContext valContext, List errors, String path,
ElementDefinition child, ElementDefinition context, Element resource,
Element element, NodeStack stack, IdStatus idstatus, StructureDefinition parentProfile, PercentageTracker pct, ValidationMode mode) throws FHIRException {
boolean ok = true;
if (element.isNull()) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), ToolingExtensions.readBooleanExtension(child, ToolingExtensions.EXT_JSON_NULLABLE),
I18nConstants.ELEMENT_CANNOT_BE_NULL)) {
// nothing else to validate?
} else {
ok = false;
}
} else {
SpecialElement special = element.getSpecial();
ContainedReferenceValidationPolicy containedValidationPolicy = policyAdvisor.policyForContained(this,
valContext, parentProfile, child, context.fhirType(), context.getId(), special, path, parentProfile.getUrl());
if (containedValidationPolicy.ignore()) {
return ok;
}
String resourceName = element.getType();
TypeRefComponent typeForResource = null;
CommaSeparatedStringBuilder bt = new CommaSeparatedStringBuilder();
// Iterate through all possible types
for (TypeRefComponent type : child.getType()) {
bt.append(type.getCode());
if (type.getCode().equals("Resource") || type.getCode().equals(resourceName) ) {
typeForResource = type;
break;
}
}
stack.pathComment(resourceName+"/"+element.getIdBase());
if (typeForResource == null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName, bt.toString()) && ok;
} else if (isValidResourceType(resourceName, typeForResource)) {
if (containedValidationPolicy.checkValid()) {
// special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
ValidationContext hc = null;
if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.BUNDLE_ISSUES || special == SpecialElement.PARAMETER) {
resource = element;
assert Utilities.existsInList(valContext.getResource().fhirType(), "Bundle", "Parameters") : "Containing Resource is "+valContext.getResource().fhirType()+", expected Bundle or Parameters at "+stack.getLiteralPath();
hc = valContext.forEntry(element, valContext.getResource()); // root becomes the grouping resource (should be either bundle or parameters)
} else {
hc = valContext.forContained(element);
}
stack.resetIds();
if (special != null) {
switch (special) {
case BUNDLE_ENTRY:
case BUNDLE_OUTCOME:
case PARAMETER:
idstatus = IdStatus.OPTIONAL;
break;
case CONTAINED:
stack.setContained(true);
idstatus = IdStatus.REQUIRED;
break;
default:
break;
}
}
checkSpecials(valContext, errors, element, stack, ok, pct, mode, true, ok);
if (typeForResource.getProfile().size() == 1) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, typeForResource.getProfile().get(0).asStringValue(), parentProfile);
timeTracker.sd(t);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL, special == null ? "??" : special.toHuman(), resourceName, typeForResource.getProfile().get(0).asStringValue())) {
trackUsage(profile, valContext, element);
ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode, false, special == SpecialElement.CONTAINED) && ok;
} else {
ok = false;
}
} else if (typeForResource.getProfile().isEmpty()) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class,
"http://hl7.org/fhir/StructureDefinition/" + resourceName);
timeTracker.sd(t);
trackUsage(profile, valContext, element);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), resourceName)) {
ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode, false, special == SpecialElement.CONTAINED) && ok;
} else {
ok = false;
}
} else {
// it has to conform to one of them
// iterate them in order, if none of them are valid, then report errors
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
CommaSeparatedStringBuilder bm = new CommaSeparatedStringBuilder();
List> errorsList = new ArrayList<>();
int matched = 0;
for (CanonicalType u : typeForResource.getProfile()) {
b.append(u.asStringValue());
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, u.asStringValue());
timeTracker.sd(t);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), u.asStringValue())) {
trackUsage(profile, valContext, element);
List perrors = new ArrayList<>();
errorsList.add(perrors);
if (validateResource(hc, perrors, resource, element, profile, idstatus, stack, pct, mode, false, special == SpecialElement.CONTAINED)) {
bm.append(u.asStringValue());
matched++;
}
} else {
ok = false;
}
}
if (rule(errors, "2023-09-07", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
matched > 0, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH, "", special.toHuman(), typeForResource.getCode(), b.toString())) {
hint(errors, "2023-09-07", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), matched == 1, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES, "", special.toHuman(), typeForResource.getCode(), bm.toString());
} else {
ok = false;
for (int i = 0; i < typeForResource.getProfile().size(); i++) {
hint(errors, "2023-09-07", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
matched > 0, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON, "", special.toHuman(), typeForResource.getProfile().get(i).asStringValue(), summariseErrors(errorsList.get(i)));
}
}
}
}
} else {
List types = new ArrayList<>();
for (UriType u : typeForResource.getProfile()) {
StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue(), parentProfile);
if (sd != null && !types.contains(sd.getType())) {
types.add(sd.getType());
}
}
if (types.size() == 1) {
ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE2, resourceName, types.get(0)) && ok;
} else {
ok = rulePlural(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
false, types.size(), I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE3, resourceName, types) && ok;
}
}
}
return ok;
}
private String summariseErrors(List list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(", ", " and ");
for (ValidationMessage vm : list ) {
if (vm.isError()) {
b.append(vm.getMessage());
}
}
return b.toString();
}
private boolean isValidResourceType(String type, TypeRefComponent def) {
if (!def.hasProfile() && def.getCode().equals("Resource")) {
return true;
}
if (def.getCode().equals(type)) {
return true;
}
List list = new ArrayList<>();
for (UriType u : def.getProfile()) {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue());
if (sdt != null) {
list.add(sdt);
}
}
StructureDefinition sdt = context.fetchTypeDefinition(type);
while (sdt != null) {
if (def.getWorkingCode().equals("Resource")) {
for (StructureDefinition sd : list) {
if (sd.getUrl().equals(sdt.getUrl())) {
return true;
}
if (sd.getType().equals(sdt.getType())) {
return true;
}
}
}
sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition(), sdt);
}
return false;
}
private boolean validateElement(ValidationContext valContext, List errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException {
boolean ok = true;
pct.seeElement(element);
String id = element.getChildValue("id");
if (!Utilities.noString(id)) {
if (stack.getIds().containsKey(id) && stack.getIds().get(id) != element) {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id) && ok;
}
if (!stack.isResetPoint()) {
stack.getIds().put(id, element);
}
}
if (definition.getPath().equals("StructureDefinition.snapshot")) {
// work around a known issue in the spec, that ids are duplicated in snapshot and differential
stack.resetIds();
}
ValidationInfo vi = element.addDefinition(profile, definition, mode);
if (definition.getFixed() != null) {
ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getVersionedUrl(), definition.getSliceName(), null, false, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
}
if (definition.getPattern() != null) {
ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getVersionedUrl(), definition.getSliceName(), null, true, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
}
// get the list of direct defined children, including slices
SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(profile, definition, false);
if (childDefinitions.getList().isEmpty()) {
if (actualType == null) {
vi.setValid(false);
return false; // there'll be an error elsewhere in this case, and we're going to stop.
}
childDefinitions = getActualTypeChildren(valContext, element, actualType);
} else if (definition.getType().size() > 1) {
// this only happens when the profile constrains the abstract children but leaves the choice open.
if (actualType == null) {
vi.setValid(false);
return false; // there'll be an error elsewhere in this case, and we're going to stop.
}
SourcedChildDefinitions typeChildDefinitions = getActualTypeChildren(valContext, element, actualType);
// what were going to do is merge them - the type is not allowed to constrain things that the child definitions already do (well, if it does, it'll be ignored)
childDefinitions = mergeChildLists(childDefinitions, typeChildDefinitions, definition.getPath(), actualType);
}
List children = listChildren(element, stack);
BooleanHolder bh = new BooleanHolder();
List problematicPaths = assignChildren(valContext, errors, profile, resource, stack, childDefinitions, children, bh);
ok = bh.ok() && ok;
for (ElementInfo ei : children) {
ei.getElement().addSliceDefinition(profile, ei.getDefinition(), ei.getSlice());
}
ok = checkCardinalities(errors, profile, element, stack, childDefinitions, children, problematicPaths) && ok;
// 4. check order if any slices are ordered. (todo)
// 5. inspect each child for validity
for (ElementInfo ei : children) {
ok = checkChild(valContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, pct, mode) && ok;
}
// check type invariants (after we've sliced the children)
ok = checkInvariants(valContext, errors, profile, definition, resource, element, stack, false) & ok;
vi.setValid(ok);
if (!definition.getPath().contains(".") && profile.hasExtension(ToolingExtensions.EXT_PROFILE_STYLE) && "cda".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_PROFILE_STYLE))) {
List templates = element.getChildren("templateId");
for (Element t : templates) {
String tid = t.hasChild("extension", false) ? "urn:hl7ii:"+t.getChildValue("root")+ ":"+t.getChildValue("extension") : "urn:oid:"+t.getChildValue("root");
StructureDefinition sd = cu.fetchProfileByIdentifier(tid);
if (sd == null) {
hint(errors, "2023-10-20", IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, t.hasChild("extension", false) ? I18nConstants.CDA_UNKNOWN_TEMPLATE_EXT : I18nConstants.CDA_UNKNOWN_TEMPLATE, t.getChildValue("root"), t.getChildValue("extension"));
} else {
ElementDefinition ed = sd.getSnapshot().getElementFirstRep();
if (!element.hasValidated(sd, ed)) {
element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, sd.getVersionedUrl()));
ok = validateElement(valContext, errors, sd, ed, null, null, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, extensionUrl, pct, mode) && ok;
}
}
}
}
return ok;
}
private SourcedChildDefinitions mergeChildLists(SourcedChildDefinitions source, SourcedChildDefinitions additional, String masterPath, String typePath) {
SourcedChildDefinitions res = new SourcedChildDefinitions(additional.getSource(), new ArrayList<>());
res.getList().addAll(source.getList());
for (ElementDefinition ed : additional.getList()) {
boolean inMaster = false;
for (ElementDefinition t : source.getList()) {
String tp = masterPath + ed.getPath().substring(typePath.length());
if (t.getPath().equals(tp)) {
inMaster = true;
}
}
if (!inMaster) {
res.getList().add(ed);
}
}
return res;
}
// todo: the element definition in context might assign a constrained profile for the type?
public SourcedChildDefinitions getActualTypeChildren(ValidationContext valContext, Element element, String actualType) {
SourcedChildDefinitions childDefinitions;
StructureDefinition dt = null;
if (isAbsolute(actualType))
dt = this.context.fetchResource(StructureDefinition.class, actualType);
else
dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType);
if (dt == null)
throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType));
trackUsage(dt, valContext, element);
childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0), false);
return childDefinitions;
}
public boolean checkChild(ValidationContext valContext, List errors, StructureDefinition profile, ElementDefinition definition,
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, PercentageTracker pct, ValidationMode mode)
throws FHIRException, DefinitionException {
boolean ok = true;
if (debug && ei.definition != null && ei.slice != null) {
System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against both "+ei.definition.getId()+" and "+ei.slice.getId());
}
if (ei.definition != null) {
if (debug) {
System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against defn "+ei.definition.getId()+" from "+profile.getVersionedUrl()+time());
}
ok = checkChildByDefinition(valContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.definition, false, pct, mode) && ok;
}
if (ei.slice != null) {
if (debug) {
System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against slice "+ei.slice.getId()+time());
}
ok = checkChildByDefinition(valContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true, pct, mode) && ok;
}
return ok;
}
private String time() {
long t = System.currentTimeMillis();
String s = " "+(t - start)+"ms";
start = t;
return s;
}
public boolean checkChildByDefinition(ValidationContext valContext, List errors, StructureDefinition profile,
ElementDefinition definition, Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept,
boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, ElementDefinition checkDefn, boolean isSlice, PercentageTracker pct, ValidationMode mode) {
boolean ok = true;
List profiles = new ArrayList();
String type = null;
String typeName = null;
ElementDefinition typeDefn = null;
checkMustSupport(profile, ei);
long s = System.currentTimeMillis();
boolean hasType = checkDefn.getType().size() > 0;
boolean isAbstract = hasType && Utilities.existsInList(checkDefn.getType().get(0).getWorkingCode(), "Element", "BackboneElement");
boolean isChoice = checkDefn.getType().size() > 1 || (hasType && "*".equals(checkDefn.getType().get(0).getWorkingCode()));
boolean isCDAChoice = profile.getUrl().startsWith(Constants.NS_CDA_ROOT) && ei.getElement().getExplicitType() != null;
if (hasType && !isChoice && !isAbstract && !isCDAChoice) {
type = checkDefn.getType().get(0).getWorkingCode();
typeName = type;
if (Utilities.isAbsoluteUrl(type)) {
StructureDefinition sdt = context.fetchTypeDefinition(type);
if (sdt != null) {
typeName = sdt.getTypeName();
}
}
String stype = ei.getElement().fhirType();
if (stype == null || !stype.equals(type)) {
if (checkDefn.isChoice()) {
if (extensionUrl != null && !isAbsolute(extensionUrl)) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), false, I18nConstants.EXTENSION_PROF_TYPE, profile.getVersionedUrl(), type, stype) && ok;
} else if (!isAbstractType(type) && !"Extension".equals(profile.getType())) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), stype.equals(typeName), I18nConstants.EXTENSION_PROF_TYPE, profile.getVersionedUrl(), type, stype) && ok;
}
} else if (!isAbstractType(type)) {
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), stype.equals(typeName) ||
(Utilities.existsInList(type, "string", "id") && Utilities.existsInList(stype, "string", "id")), // work around a r4 problem with id/string
I18nConstants.EXTENSION_PROF_TYPE, profile.getVersionedUrl(), type, stype) && ok;
} else if (!isResource(type)) {
// System.out.println("update type "+type+" to "+stype+"?");
type = stype;
typeName = type;
} else {
// this will be sorted out in contains ... System.out.println("update type "+type+" to "+stype+"?");
}
}
// Excluding reference is a kludge to get around versioning issues
if (checkDefn.getType().get(0).hasProfile()) {
for (CanonicalType p : checkDefn.getType().get(0).getProfile()) {
profiles.add(p.getValue());
}
}
} else if (checkDefn.getType().size() == 1 && "*".equals(checkDefn.getType().get(0).getWorkingCode())) {
String prefix = tail(checkDefn.getPath());
assert prefix.endsWith("[x]");
type = ei.getName().substring(prefix.length() - 3);
typeName = type;
if (isPrimitiveType(type)) {
type = Utilities.uncapitalize(type);
typeName = type;
}
if (checkDefn.getType().get(0).hasProfile()) {
for (CanonicalType p : checkDefn.getType().get(0).getProfile()) {
profiles.add(p.getValue());
}
}
} else if (checkDefn.getType().size() > 1 || isCDAChoice) {
String prefix = tail(checkDefn.getPath());
assert typesAreAllReference(checkDefn.getType()) || isCDAChoice|| checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB();
if (isCDAChoice) {
type = ei.getElement().getExplicitType();
typeName = type;
} else if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
type = ei.getElement().getType();
typeName = type;
} else if (ei.getElement().isResource()) {
type = ei.getElement().fhirType();
typeName = type;
} else {
prefix = prefix.substring(0, prefix.length() - 3);
for (TypeRefComponent t : checkDefn.getType())
if ((prefix + Utilities.capitalize(t.getWorkingCode())).equals(ei.getName())) {
type = t.getWorkingCode();
typeName = type;
// Excluding reference is a kludge to get around versioning issues
if (t.hasProfile() && !type.equals("Reference"))
profiles.add(t.getProfile().get(0).getValue());
}
}
if (type == null) {
TypeRefComponent trc = checkDefn.getType().get(0);
if (trc.getWorkingCode().equals("Reference")) {
type = "Reference";
typeName = type;
} else
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTYPE, ei.getName(), describeTypes(checkDefn.getType())) && ok;
}
} else if (checkDefn.getContentReference() != null) {
typeDefn = resolveNameReference(profile.getSnapshot(), checkDefn.getContentReference());
} else if (checkDefn.getType().size() == 1 && ("Element".equals(checkDefn.getType().get(0).getWorkingCode()) || "BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode()))) {
if (checkDefn.getType().get(0).hasProfile()) {
CanonicalType pu = checkDefn.getType().get(0).getProfile().get(0);
if (pu.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT))
profiles.add(pu.getValue() + "#" + pu.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT));
else
profiles.add(pu.getValue());
}
}
if (type != null) {
if (type.startsWith("@")) {
checkDefn = findElement(profile, type.substring(1));
if (isSlice) {
ei.slice = ei.definition;
} else {
ei.definition = ei.definition;
}
type = null;
typeName = type;
}
}
NodeStack localStack = stack.push(ei.getElement(), "*".equals(ei.getDefinition().getBase().getMax()) && ei.count == -1 ? 0 : ei.count, checkDefn, type == null ? typeDefn : resolveType(type, checkDefn.getType()));
if (debug) {
System.out.println(" check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getVersionedUrl()+time());
}
EnumSet actionSet = policyAdvisor.policyForElement(this, valContext.getAppContext(), profile, ei.getDefinition(), localStack.getLiteralPath());
String localStackLiteralPath = localStack.getLiteralPath();
String eiPath = ei.getPath();
if (!eiPath.equals(localStackLiteralPath)) {
assert (eiPath.equals(localStackLiteralPath)) : "ei.path: " + ei.getPath() + " - localStack.getLiteralPath: " + localStackLiteralPath;
}
boolean thisIsCodeableConcept = false;
String thisExtension = null;
boolean checkDisplay = true;
// SpecialElement special = ei.getElement().getSpecial();
// this used to say
// if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.BUNDLE_ISSUES || special == SpecialElement.PARAMETER) {
// ok = checkInvariants(valContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, ei.getElement(), ei.getElement(), localStack, false) && ok;
// but this isn't correct - when the invariant is on the element, the invariant is in the context of the resource that contains the element.
// changed 18-Jul 2023 - see https://chat.fhir.org/#narrow/stream/179266-fhirpath/topic/FHIRPath.20.25resource.20variable
if (actionSet.contains(ElementValidationAction.Invariants)) {
ok = checkInvariants(valContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, resource, ei.getElement(), localStack, false) && ok;
}
boolean checkBindings = actionSet.contains(ElementValidationAction.Bindings);
ei.getElement().markValidation(profile, checkDefn);
boolean elementValidated = false;
if (type != null) {
if (isPrimitiveType(type)) {
ok = checkPrimitive(valContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, localStack, stack, valContext.getRootResource()) && ok;
} else {
if (checkDefn.hasFixed()) {
ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, false, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
}
if (checkDefn.hasPattern()) {
ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, true, profile.getVersionedUrl()+"#"+definition.getId()) && ok;
}
}
if (type.equals("Identifier")) {
ok = checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn) && ok;
} else if (type.equals("Coding")) {
ok = (checkCoding(checkBindings ? errors : new ArrayList<>(), ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, localStack) || !checkBindings) && ok;
} else if (type.equals("Quantity")) {
ok = checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, localStack) && ok;
} else if (type.equals("Attachment")) {
ok = checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, localStack) && ok;
} else if (type.equals("CodeableConcept")) {
BooleanHolder bh = new BooleanHolder();
checkDisplay = checkCodeableConcept(checkBindings ? errors : new ArrayList<>(), ei.getPath(), ei.getElement(), profile, checkDefn, localStack, bh);
ok = (bh.ok() || !checkBindings) & ok;
thisIsCodeableConcept = true;
} else if (type.equals("Reference")) {
ok = checkReference(valContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, actualType, localStack, pct, mode) && ok;
// We only check extensions if we're not in a complex extension or if the element we're dealing with is not defined as part of that complex extension
} else if (type.equals("Extension")) {
Element eurl = ei.getElement().getNamedChild("url", false);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), eurl != null, I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) {
String url = eurl.primitiveValue();
thisExtension = url;
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), !Utilities.noString(url), I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) {
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), (extensionUrl != null) || Utilities.isAbsoluteUrl(url), I18nConstants.EXTENSION_EXT_URL_ABSOLUTE)) {
ok = checkExtension(valContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl, pct, mode) && ok;
} else {
ok = false;
}
} else {
ok = false;
}
} else {
ok = false;
}
} else if (type.equals("Resource") || isResource(type)) {
ok = validateContains(valContext, errors, ei.getPath(), checkDefn, definition, resource, ei.getElement(),
localStack, idStatusForEntry(element, ei), profile, pct, mode) && ok; // if
elementValidated = true;
// (str.matches(".*([.,/])work\\1$"))
} else if (Utilities.isAbsoluteUrl(type)) {
StructureDefinition defn = context.fetchTypeDefinition(type);
if (defn != null && defn.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) {
String style = ToolingExtensions.readStringExtension(defn, ToolingExtensions.EXT_BINDING_STYLE);
if ("CDA".equals(style)) {
if (!ei.getElement().hasChild("nullFlavor", false)) {
if (cdaTypeIs(defn, "CS")) {
ok = checkCDACodeSimple(valContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, localStack, defn) && ok;
} else if (cdaTypeIs(defn, "CV") || cdaTypeIs(defn, "PQ")) {
ok = checkCDACoding(errors, ei.getPath(), cdaTypeIs(defn, "PQ"), ei.getElement(), profile, checkDefn, localStack, defn, inCodeableConcept, checkDisplayInContext) && ok;
} else if (cdaTypeIs(defn, "CD") || cdaTypeIs(defn, "CE")) {
ok = checkCDACodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, localStack, defn) && ok;
thisIsCodeableConcept = true;
}
}
}
}
}
} else {
if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), localStack.getLiteralPath(), checkDefn != null, I18nConstants.VALIDATION_VAL_CONTENT_UNKNOWN, ei.getName())) {
ok = validateElement(valContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, false, true, null, pct, mode) && ok;
} else {
ok = false;
}
}
StructureDefinition p = null;
String tail = null;
if (profiles.isEmpty()) {
if (type != null) {
p = getProfileForType(type, checkDefn.getType(), profile);
// If dealing with a primitive type, then we need to check the current child against
// the invariants (constraints) on the current element, because otherwise it only gets
// checked against the primary type's invariants: LLoyd
//if (p.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
// checkInvariants(valContext, errors, ei.path, profile, ei.definition, null, null, resource, ei.element);
//}
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_NOTYPE, type) && ok;
}
} else if (profiles.size() == 1) {
String url = profiles.get(0);
if (url.contains("#")) {
tail = url.substring(url.indexOf("#") + 1);
url = url.substring(0, url.indexOf("#"));
}
p = this.context.fetchResource(StructureDefinition.class, url, profile);
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, profiles.get(0)) && ok;
} else {
elementValidated = true;
HashMap> goodProfiles = new HashMap>();
HashMap