001package org.hl7.fhir.validation.instance;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import static org.apache.commons.lang3.StringUtils.isBlank;
034import static org.apache.commons.lang3.StringUtils.isNotBlank;
035
036import java.io.ByteArrayInputStream;
037import java.io.File;
038import java.io.IOException;
039import java.io.InputStream;
040import java.math.BigDecimal;
041import java.net.URI;
042import java.net.URISyntaxException;
043import java.nio.charset.StandardCharsets;
044import java.util.ArrayList;
045import java.util.Calendar;
046import java.util.Collection;
047import java.util.Collections;
048import java.util.Comparator;
049import java.util.HashMap;
050import java.util.HashSet;
051import java.util.List;
052import java.util.Map;
053import java.util.Set;
054import java.util.UUID;
055
056import org.apache.commons.codec.binary.Base64InputStream;
057import org.apache.commons.lang3.NotImplementedException;
058import org.apache.commons.lang3.StringUtils;
059import org.fhir.ucum.Decimal;
060import org.hl7.fhir.exceptions.DefinitionException;
061import org.hl7.fhir.exceptions.FHIRException;
062import org.hl7.fhir.exceptions.PathEngineException;
063import org.hl7.fhir.exceptions.TerminologyServiceException;
064import org.hl7.fhir.r5.conformance.ProfileUtilities;
065import org.hl7.fhir.r5.conformance.ProfileUtilities.SourcedChildDefinitions;
066import org.hl7.fhir.r5.context.ContextUtilities;
067import org.hl7.fhir.r5.context.IWorkerContext;
068import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
069import org.hl7.fhir.r5.elementmodel.Element;
070import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
071import org.hl7.fhir.r5.elementmodel.JsonParser;
072import org.hl7.fhir.r5.elementmodel.Manager;
073import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
074import org.hl7.fhir.r5.elementmodel.ObjectConverter;
075import org.hl7.fhir.r5.elementmodel.ParserBase;
076import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
077import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy;
078import org.hl7.fhir.r5.elementmodel.ResourceParser;
079import org.hl7.fhir.r5.elementmodel.XmlParser;
080import org.hl7.fhir.r5.formats.FormatUtilities;
081import org.hl7.fhir.r5.model.Address;
082import org.hl7.fhir.r5.model.Attachment;
083import org.hl7.fhir.r5.model.Base;
084import org.hl7.fhir.r5.model.Base.ProfileSource;
085import org.hl7.fhir.r5.model.Base.ValidationInfo;
086import org.hl7.fhir.r5.model.Base.ValidationMode;
087import org.hl7.fhir.r5.model.Base.ValidationReason;
088import org.hl7.fhir.r5.model.BooleanType;
089import org.hl7.fhir.r5.model.CanonicalResource;
090import org.hl7.fhir.r5.model.CanonicalType;
091import org.hl7.fhir.r5.model.CodeSystem;
092import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
093import org.hl7.fhir.r5.model.CodeableConcept;
094import org.hl7.fhir.r5.model.Coding;
095import org.hl7.fhir.r5.model.ContactPoint;
096import org.hl7.fhir.r5.model.DataType;
097import org.hl7.fhir.r5.model.DateTimeType;
098import org.hl7.fhir.r5.model.DateType;
099import org.hl7.fhir.r5.model.DecimalType;
100import org.hl7.fhir.r5.model.ElementDefinition;
101import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
102import org.hl7.fhir.r5.model.ElementDefinition.ConstraintSeverity;
103import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
104import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
105import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
106import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
107import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
108import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
109import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
110import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
111import org.hl7.fhir.r5.model.Enumeration;
112import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
113import org.hl7.fhir.r5.model.ExpressionNode;
114import org.hl7.fhir.r5.model.Extension;
115import org.hl7.fhir.r5.model.HumanName;
116import org.hl7.fhir.r5.model.Identifier;
117import org.hl7.fhir.r5.model.ImplementationGuide;
118import org.hl7.fhir.r5.model.ImplementationGuide.ImplementationGuideGlobalComponent;
119import org.hl7.fhir.r5.model.InstantType;
120import org.hl7.fhir.r5.model.IntegerType;
121import org.hl7.fhir.r5.model.Period;
122import org.hl7.fhir.r5.model.PrimitiveType;
123import org.hl7.fhir.r5.model.Quantity;
124import org.hl7.fhir.r5.model.Range;
125import org.hl7.fhir.r5.model.Ratio;
126import org.hl7.fhir.r5.model.Reference;
127import org.hl7.fhir.r5.model.Resource;
128import org.hl7.fhir.r5.model.SampledData;
129import org.hl7.fhir.r5.model.SearchParameter;
130import org.hl7.fhir.r5.model.StringType;
131import org.hl7.fhir.r5.model.StructureDefinition;
132import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
133import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
134import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
135import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
136import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
137import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
138import org.hl7.fhir.r5.model.TimeType;
139import org.hl7.fhir.r5.model.Timing;
140import org.hl7.fhir.r5.model.TypeDetails;
141import org.hl7.fhir.r5.model.UriType;
142import org.hl7.fhir.r5.model.ValueSet;
143import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
144import org.hl7.fhir.r5.renderers.DataRenderer;
145import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
146import org.hl7.fhir.r5.utils.BuildExtensions;
147import org.hl7.fhir.r5.utils.FHIRLexer.FHIRLexerException;
148import org.hl7.fhir.r5.utils.FHIRPathEngine;
149import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
150import org.hl7.fhir.r5.utils.FHIRPathEngine.TypedElementDefinition;
151import org.hl7.fhir.r5.utils.ResourceUtilities;
152import org.hl7.fhir.r5.utils.ToolingExtensions;
153import org.hl7.fhir.r5.utils.XVerExtensionManager;
154import org.hl7.fhir.r5.utils.validation.BundleValidationRule;
155import org.hl7.fhir.r5.utils.validation.IResourceValidator;
156import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor;
157import org.hl7.fhir.r5.utils.validation.IValidationProfileUsageTracker;
158import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher;
159import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel;
160import org.hl7.fhir.r5.utils.validation.constants.BindingKind;
161import org.hl7.fhir.r5.utils.validation.constants.CheckDisplayOption;
162import org.hl7.fhir.r5.utils.validation.constants.CodedContentValidationPolicy;
163import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy;
164import org.hl7.fhir.r5.utils.validation.constants.IdStatus;
165import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy;
166import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
167import org.hl7.fhir.utilities.MarkDownProcessor;
168import org.hl7.fhir.utilities.SIDUtilities;
169import org.hl7.fhir.utilities.UnicodeUtilities;
170import org.hl7.fhir.utilities.Utilities;
171import org.hl7.fhir.utilities.Utilities.DecimalStatus;
172import org.hl7.fhir.utilities.VersionUtilities;
173import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
174import org.hl7.fhir.utilities.i18n.I18nConstants;
175import org.hl7.fhir.utilities.json.model.JsonObject;
176import org.hl7.fhir.utilities.validation.ValidationMessage;
177import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
178import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
179import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
180import org.hl7.fhir.utilities.validation.ValidationOptions;
181import org.hl7.fhir.utilities.xhtml.NodeType;
182import org.hl7.fhir.utilities.xhtml.XhtmlNode;
183import org.hl7.fhir.validation.BaseValidator;
184import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
185import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
186import org.hl7.fhir.validation.instance.type.BundleValidator;
187import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
188import org.hl7.fhir.validation.instance.type.MeasureValidator;
189import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
190import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
191import org.hl7.fhir.validation.instance.type.StructureDefinitionValidator;
192import org.hl7.fhir.validation.instance.type.ValueSetValidator;
193import org.hl7.fhir.validation.instance.utils.ChildIterator;
194import org.hl7.fhir.validation.instance.utils.ElementInfo;
195import org.hl7.fhir.validation.instance.utils.FHIRPathExpressionFixer;
196import org.hl7.fhir.validation.instance.utils.IndexedElement;
197import org.hl7.fhir.validation.instance.utils.NodeStack;
198import org.hl7.fhir.validation.instance.utils.ResolvedReference;
199import org.hl7.fhir.validation.instance.utils.ResourceValidationTracker;
200import org.hl7.fhir.validation.instance.utils.ValidatorHostContext;
201import org.w3c.dom.Document;
202
203/**
204 * Thinking of using this in a java program? Don't!
205 * You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine
206 * <p>
207 * Validation todo:
208 * - support @default slices
209 *
210 * @author Grahame Grieve
211 */
212/*
213 * todo:
214 * check urn's don't start oid: or uuid:
215 * check MetadataResource.url is absolute
216 */
217
218public class InstanceValidator extends BaseValidator implements IResourceValidator {
219  
220  public class StructureDefinitionSorterByUrl implements Comparator<StructureDefinition> {
221
222    @Override
223    public int compare(StructureDefinition o1, StructureDefinition o2) {
224      return o1.getUrl().compareTo(o2.getUrl());
225    }
226
227  }
228
229  public class CanonicalTypeSorter implements Comparator<CanonicalType> {
230
231    @Override
232    public int compare(CanonicalType o1, CanonicalType o2) {
233      return o1.getValue().compareTo(o2.getValue());
234    }
235
236  }
237
238  public class CanonicalResourceLookupResult {
239
240    private CanonicalResource resource;
241    private String error;
242
243    public CanonicalResourceLookupResult(CanonicalResource resource) {
244      this.resource = resource;
245    }
246
247    public CanonicalResourceLookupResult(String error) {
248      this.error = error;
249    }
250  }
251  
252  private static final String EXECUTED_CONSTRAINT_LIST = "validator.executed.invariant.list";
253  private static final String EXECUTION_ID = "validator.execution.id";
254  private static final String HTML_FRAGMENT_REGEX = "[a-zA-Z]\\w*(((\\s+)(\\S)*)*)";
255  private static final boolean STACK_TRACE = false;
256  private static final boolean DEBUG_ELEMENT = false;
257  
258  private class ValidatorHostServices implements IEvaluationContext {
259
260    @Override
261    public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
262      ValidatorHostContext c = (ValidatorHostContext) appContext;
263      if (externalHostServices != null)
264        return externalHostServices.resolveConstant(c.getAppContext(), name, beforeContext);
265      else
266        return new ArrayList<Base>();
267    }
268
269    @Override
270    public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
271      ValidatorHostContext c = (ValidatorHostContext) appContext;
272      if (externalHostServices != null)
273        return externalHostServices.resolveConstantType(c.getAppContext(), name);
274      else
275        return null;
276    }
277
278    @Override
279    public boolean log(String argument, List<Base> focus) {
280      if (externalHostServices != null)
281        return externalHostServices.log(argument, focus);
282      else
283        return false;
284    }
285
286    @Override
287    public FunctionDetails resolveFunction(String functionName) {
288      throw new FHIRException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESRESOLVEFUNCTION_, functionName));
289    }
290
291    @Override
292    public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
293      throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCHECKFUNCTION));
294    }
295
296    @Override
297    public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
298      throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESEXECUTEFUNCTION));
299    }
300
301    @Override
302    public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException {
303      ValidatorHostContext c = (ValidatorHostContext) appContext;
304
305      if (refContext != null && refContext.hasUserData("validator.bundle.resolution")) {
306        return (Base) refContext.getUserData("validator.bundle.resolution");
307      }
308
309      if (c.getAppContext() instanceof Element) {
310        Element element = (Element) c.getAppContext();
311        while (element != null) {
312          Base res = resolveInBundle(url, element);
313          if (res != null) {
314            return res;
315          }
316          element = element.getParentForValidator();  
317        }
318      }
319      Base res = resolveInBundle(url, c.getResource());
320      if (res != null) {
321        return res;
322      }
323      Element element = c.getRootResource();
324      while (element != null) {
325        res = resolveInBundle(url, element);
326        if (res != null) {
327          return res;
328        }
329        element = element.getParentForValidator();  
330      }
331
332      if (externalHostServices != null) {
333        return externalHostServices.resolveReference(c.getAppContext(), url, refContext);
334      } else if (fetcher != null) {
335        try {
336          return fetcher.fetch(InstanceValidator.this, c.getAppContext(), url);
337        } catch (IOException e) {
338          throw new FHIRException(e);
339        }
340      } else {
341        throw new Error(context.formatMessage(I18nConstants.NOT_DONE_YET__RESOLVE__LOCALLY_2, url));
342      }
343    }
344
345   
346    @Override
347    public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
348      ValidatorHostContext ctxt = (ValidatorHostContext) appContext;
349      StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
350      if (sd == null) {
351        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_, url));
352      }
353      InstanceValidator self = InstanceValidator.this;
354      List<ValidationMessage> valerrors = new ArrayList<ValidationMessage>();
355      ValidationMode mode = new ValidationMode(ValidationReason.Expression, ProfileSource.FromExpression);
356      if (item instanceof Resource) {
357        try {
358          Element e = new ObjectConverter(context).convert((Resource) item);
359          setParents(e);
360          self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null,
361              mode);
362        } catch (IOException e1) {
363          throw new FHIRException(e1);
364        }
365      } else if (item instanceof Element) {
366        Element e = (Element) item;
367        if (e.getSpecial() == SpecialElement.CONTAINED) {
368          self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, ctxt.getRootResource(), ctxt.getGroupingResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode);          
369        } else if (e.getSpecial() != null) {
370          self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e, e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode);          
371        } else {
372          self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode);
373        }
374      } else
375        throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
376      boolean ok = true;
377      List<ValidationMessage> record = new ArrayList<>();
378      for (ValidationMessage v : valerrors) {
379        ok = ok && !v.getLevel().isError();
380        if (v.getLevel().isError() || v.isSlicingHint()) {
381          record.add(v);
382        }
383      }
384      if (!ok && !record.isEmpty()) {
385        ctxt.sliceNotes(url, record);
386      }
387      return ok;
388    }
389
390    @Override
391    public ValueSet resolveValueSet(Object appContext, String url) {
392      ValidatorHostContext c = (ValidatorHostContext) appContext;
393      if (c.getProfile() != null && url.startsWith("#")) {
394        for (Resource r : c.getProfile().getContained()) {
395          if (r.getId().equals(url.substring(1))) {
396            if (r instanceof ValueSet)
397              return (ValueSet) r;
398            else
399              throw new FHIRException(context.formatMessage(I18nConstants.REFERENCE__REFERS_TO_A__NOT_A_VALUESET, url, r.fhirType()));
400          }
401        }
402        return null;
403      }
404      return context.fetchResource(ValueSet.class, url);
405    }
406
407  }
408  private FHIRPathEngine fpe;
409
410  public FHIRPathEngine getFHIRPathEngine() {
411    return fpe;
412  }
413
414  // configuration items
415  private CheckDisplayOption checkDisplay;
416  private boolean anyExtensionsAllowed;
417  private boolean errorForUnknownProfiles;
418  private boolean noInvariantChecks;
419  private boolean wantInvariantInMessage;
420  private boolean noTerminologyChecks;
421  private boolean hintAboutNonMustSupport;
422  private boolean showMessagesFromReferences;
423  private BestPracticeWarningLevel bpWarnings;
424  private String validationLanguage;
425  private boolean baseOnly;
426  private boolean noCheckAggregation;
427  private boolean wantCheckSnapshotUnchanged;
428  private boolean noUnicodeBiDiControlChars;
429  private HtmlInMarkdownCheck htmlInMarkdownCheck;
430  private boolean allowComments;
431 
432  private List<ImplementationGuide> igs = new ArrayList<>();
433  private List<String> extensionDomains = new ArrayList<String>();
434
435  private IdStatus resourceIdRule;
436  private boolean allowXsiLocation;
437
438  // used during the build process to keep the overall volume of messages down
439  private boolean suppressLoincSnomedMessages;
440
441  // time tracking
442  private boolean noBindingMsgSuppressed;
443  private boolean debug;
444  private Map<String, Element> fetchCache = new HashMap<>();
445  private HashMap<Element, ResourceValidationTracker> resourceTracker = new HashMap<>();
446  private IValidatorResourceFetcher fetcher;
447  private IValidationPolicyAdvisor policyAdvisor;
448  long time = 0;
449  long start = 0;
450  long lastlog = 0;
451  private IEvaluationContext externalHostServices;
452  private boolean noExtensibleWarnings;
453  private String serverBase;
454
455  private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
456  private String executionId;
457  private IValidationProfileUsageTracker tracker;
458  private ValidatorHostServices validatorServices;
459  private boolean assumeValidRestReferences;
460  private boolean allowExamples;
461  private boolean securityChecks;
462  private ProfileUtilities profileUtilities;
463  private boolean crumbTrails;
464  private List<BundleValidationRule> bundleValidationRules = new ArrayList<>();
465  private boolean validateValueSetCodesOnTxServer = true;
466  private QuestionnaireMode questionnaireMode;
467  private ValidationOptions baseOptions = new ValidationOptions();
468  private Map<String, CanonicalResourceLookupResult> crLookups = new HashMap<>();
469  private boolean logProgress;
470  private boolean forPublication;
471
472  public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices, XVerExtensionManager xverManager) {
473    super(theContext, xverManager);
474    start = System.currentTimeMillis();
475    this.externalHostServices = hostServices;
476    this.profileUtilities = new ProfileUtilities(theContext, null, null);
477    fpe = new FHIRPathEngine(context);
478    validatorServices = new ValidatorHostServices();
479    fpe.setHostServices(validatorServices);
480    if (theContext.getVersion().startsWith("3.0") || theContext.getVersion().startsWith("1.0"))
481      fpe.setLegacyMode(true);
482    source = Source.InstanceValidator;
483  }
484
485  @Override
486  public boolean isNoExtensibleWarnings() {
487    return noExtensibleWarnings;
488  }
489
490  @Override
491  public IResourceValidator setNoExtensibleWarnings(boolean noExtensibleWarnings) {
492    this.noExtensibleWarnings = noExtensibleWarnings;
493    return this;
494  }
495
496  @Override
497  public boolean isShowMessagesFromReferences() {
498    return showMessagesFromReferences;
499  }
500
501  @Override
502  public void setShowMessagesFromReferences(boolean showMessagesFromReferences) {
503    this.showMessagesFromReferences = showMessagesFromReferences;
504  }
505
506  @Override
507  public boolean isNoInvariantChecks() {
508    return noInvariantChecks;
509  }
510
511  @Override
512  public IResourceValidator setNoInvariantChecks(boolean value) {
513    this.noInvariantChecks = value;
514    return this;
515  }
516
517  @Override
518  public boolean isWantInvariantInMessage() {
519    return wantInvariantInMessage;
520  }
521
522  @Override
523  public IResourceValidator setWantInvariantInMessage(boolean wantInvariantInMessage) {
524    this.wantInvariantInMessage = wantInvariantInMessage;
525    return this;
526  }
527
528  public IValidatorResourceFetcher getFetcher() {
529    return this.fetcher;
530  }
531
532  public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
533    this.fetcher = value;
534    return this;
535  }
536
537  @Override
538  public IValidationPolicyAdvisor getPolicyAdvisor() {
539    return policyAdvisor;
540  }
541
542  @Override
543  public IResourceValidator setPolicyAdvisor(IValidationPolicyAdvisor advisor) {
544    this.policyAdvisor = advisor;
545    return this;
546  }
547
548  public IValidationProfileUsageTracker getTracker() {
549    return this.tracker;
550  }
551
552  public IResourceValidator setTracker(IValidationProfileUsageTracker value) {
553    this.tracker = value;
554    return this;
555  }
556
557
558  public boolean isHintAboutNonMustSupport() {
559    return hintAboutNonMustSupport;
560  }
561
562  public void setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) {
563    this.hintAboutNonMustSupport = hintAboutNonMustSupport;
564  }
565
566  public boolean isAssumeValidRestReferences() {
567    return this.assumeValidRestReferences;
568  }
569
570  public void setAssumeValidRestReferences(boolean value) {
571    this.assumeValidRestReferences = value;
572  }
573
574  public boolean isAllowExamples() {
575    return this.allowExamples;
576  }
577
578  public void setAllowExamples(boolean value) {
579    this.allowExamples = value;
580  }
581
582  public boolean isAllowComments() {
583    return allowComments;
584  }
585
586  public void setAllowComments(boolean allowComments) {
587    this.allowComments = allowComments;
588  }
589
590  public boolean isCrumbTrails() {
591    return crumbTrails;
592  }
593
594  public void setCrumbTrails(boolean crumbTrails) {
595    this.crumbTrails = crumbTrails;
596  }
597
598  public boolean isDoImplicitFHIRPathStringConversion() {
599    return fpe.isDoImplicitStringConversion();
600  }
601
602  public void setDoImplicitFHIRPathStringConversion(boolean doImplicitFHIRPathStringConversion) {
603    fpe.setDoImplicitStringConversion(doImplicitFHIRPathStringConversion);
604  }
605
606  private boolean allowUnknownExtension(String url) {
607    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"))
608      // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
609      return true;
610    for (String s : extensionDomains)
611      if (url.startsWith(s))
612        return true;
613    return anyExtensionsAllowed;
614  }
615
616  private boolean isKnownExtension(String url) {
617    // Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with
618    if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || 
619        url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") ||
620        url.equals("http://hl7.org/fhir/StructureDefinition/codesystem-properties-mode"))
621      return true;
622    for (String s : extensionDomains)
623      if (url.startsWith(s))
624        return true;
625    return false;
626  }
627
628  private boolean bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message, Object... theMessageArguments) {
629    if (bpWarnings != null) {
630      switch (bpWarnings) {
631        case Error:
632          rule(errors, NO_RULE_DATE, invalid, line, col, literalPath, test, message, theMessageArguments);
633          return test;
634        case Warning:
635          warning(errors, NO_RULE_DATE, invalid, line, col, literalPath, test, message, theMessageArguments);
636          return true;
637        case Hint:
638          hint(errors, NO_RULE_DATE, invalid, line, col, literalPath, test, message, theMessageArguments);
639          return true;
640        default: // do nothing
641          break;
642      }
643    }
644    return true;
645  }
646
647  @Override
648  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws FHIRException {
649    return validate(appContext, errors, stream, format, new ArrayList<>());
650  }
651
652  @Override
653  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws FHIRException {
654    ArrayList<StructureDefinition> profiles = new ArrayList<>();
655    if (profile != null) {
656      profiles.add(getSpecifiedProfile(profile));
657    }
658    return validate(appContext, errors, stream, format, profiles);
659  }
660
661  private StructureDefinition getSpecifiedProfile(String profile) {
662    StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile);
663    if (sd == null) {
664      throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_LOCATE_THE_PROFILE__IN_ORDER_TO_VALIDATE_AGAINST_IT, profile));
665    }
666    return sd;
667  }
668
669  @Override
670  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, List<StructureDefinition> profiles) throws FHIRException {
671    ParserBase parser = Manager.makeParser(context, format);
672    List<StructureDefinition> logicals = new ArrayList<>();
673    for (StructureDefinition sd : profiles) {
674      if (sd.getKind() == StructureDefinitionKind.LOGICAL) {
675        logicals.add(sd);
676      }
677    }
678    if (logicals.size() > 0) {
679      if (rulePlural(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, "Configuration", logicals.size() == 1, logicals.size(), I18nConstants.MULTIPLE_LOGICAL_MODELS, ResourceUtilities.listUrls(logicals))) {
680        parser.setLogical(logicals.get(0));              
681      }
682    }
683    if (parser instanceof XmlParser) {
684      ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
685    }
686    parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
687    if (parser instanceof XmlParser) {
688      ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
689    }
690    if (parser instanceof JsonParser) {
691      ((JsonParser) parser).setAllowComments(allowComments);
692    }
693    long t = System.nanoTime();
694    List<NamedElement> list = null;
695    try {
696      list = parser.parse(stream);
697    } catch (IOException e1) {
698      throw new FHIRException(e1);
699    }
700    timeTracker.load(t);
701    if (list != null && !list.isEmpty()) {
702      String url = parser.getImpliedProfile();
703      if (url != null) {
704        StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
705        if (sd == null) {
706          rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, "Payload", false, "Implied profile "+url+" not known to validator");          
707        } else {
708          profiles.add(sd);
709        }
710      }
711      for (NamedElement ne : list) {
712        validate(appContext, errors, ne.getName(), ne.getElement(), profiles);
713      }
714    }
715    return (list == null || list.isEmpty()) ? null : list.get(0).getElement(); // todo: this is broken, but fixing it really complicates things elsewhere, so we do this for now
716  }
717
718  @Override
719  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource) throws FHIRException {
720    return validate(appContext, errors, resource, new ArrayList<>());
721  }
722
723  @Override
724  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, String profile) throws FHIRException {
725    ArrayList<StructureDefinition> profiles = new ArrayList<>();
726    if (profile != null) {
727      profiles.add(getSpecifiedProfile(profile));
728    }
729    return validate(appContext, errors, resource, profiles);
730  }
731
732  @Override
733  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, List<StructureDefinition> profiles) throws FHIRException {
734    long t = System.nanoTime();
735    Element e = new ResourceParser(context).parse(resource);
736    timeTracker.load(t);
737    validate(appContext, errors, null, e, profiles);
738    return e;
739  }
740
741  @Override
742  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element) throws FHIRException {
743    return validate(appContext, errors, element, new ArrayList<>());
744  }
745
746  @Override
747  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws FHIRException {
748    ArrayList<StructureDefinition> profiles = new ArrayList<>();
749    if (profile != null) {
750      profiles.add(getSpecifiedProfile(profile));
751    }
752    return validate(appContext, errors, element, profiles);
753  }
754
755  @Override
756  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, List<StructureDefinition> profiles) throws FHIRException {
757    XmlParser parser = new XmlParser(context);
758    parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
759    long t = System.nanoTime();
760    Element e;
761    try {
762      e = parser.parse(element);
763    } catch (IOException e1) {
764      throw new FHIRException(e1);
765    }
766    timeTracker.load(t);
767    if (e != null) {
768      validate(appContext, errors, null, e, profiles);
769    }
770    return e;
771  }
772
773  @Override
774  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document) throws FHIRException {
775    return validate(appContext, errors, document, new ArrayList<>());
776  }
777
778  @Override
779  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, String profile) throws FHIRException {
780    ArrayList<StructureDefinition> profiles = new ArrayList<>();
781    if (profile != null) {
782      profiles.add(getSpecifiedProfile(profile));
783    }
784    return validate(appContext, errors, document, profiles);
785  }
786
787  @Override
788  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, List<StructureDefinition> profiles) throws FHIRException {
789    XmlParser parser = new XmlParser(context);
790    parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
791    long t = System.nanoTime();
792    Element e;
793    try {
794      e = parser.parse(document);
795    } catch (IOException e1) {
796      throw new FHIRException(e1);
797    }
798    timeTracker.load(t);
799    if (e != null)
800      validate(appContext, errors, null, e, profiles);
801    return e;
802  }
803
804  @Override
805  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object) throws FHIRException {
806    return validate(appContext, errors, object, new ArrayList<>());
807  }
808
809  @Override
810  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, String profile) throws FHIRException {
811    ArrayList<StructureDefinition> profiles = new ArrayList<>();
812    if (profile != null) {
813      profiles.add(getSpecifiedProfile(profile));
814    }
815    return validate(appContext, errors, object, profiles);
816  }
817
818  @Override
819  public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, List<StructureDefinition> profiles) throws FHIRException {
820    JsonParser parser = new JsonParser(context, new ProfileUtilities(context, null, null, fpe));
821    parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
822    long t = System.nanoTime();
823    Element e = parser.parse(object);
824    timeTracker.load(t);
825    if (e != null)
826      validate(appContext, errors, null, e, profiles);
827    return e;
828  }
829
830  @Override
831  public void validate(Object appContext, List<ValidationMessage> errors, String initialPath, Element element) throws FHIRException {
832    validate(appContext, errors, initialPath, element, new ArrayList<>());
833  }
834
835  @Override
836  public void validate(Object appContext, List<ValidationMessage> errors, String initialPath, Element element, String profile) throws FHIRException {
837    ArrayList<StructureDefinition> profiles = new ArrayList<>();
838    if (profile != null) {
839      profiles.add(getSpecifiedProfile(profile));
840    }
841    validate(appContext, errors, initialPath, element, profiles);
842  }
843
844  @Override
845  public void validate(Object appContext, List<ValidationMessage> errors, String path, Element element, List<StructureDefinition> profiles) throws FHIRException {
846    // this is the main entry point; all the other public entry points end up here coming here...
847    // so the first thing to do is to clear the internal state
848    fetchCache.clear();
849    fetchCache.put(element.fhirType() + "/" + element.getIdBase(), element);
850    resourceTracker.clear();
851    trackedMessages.clear();
852    messagesToRemove.clear();
853    executionId = UUID.randomUUID().toString();
854    baseOnly = profiles.isEmpty();
855    setParents(element);
856
857    long t = System.nanoTime();
858    NodeStack stack = new NodeStack(context, path, element, validationLanguage);
859    if (profiles == null || profiles.isEmpty()) {
860      validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.BaseDefinition));
861    } else {
862      int i = 0;
863      while (i < profiles.size()) {
864        StructureDefinition sd = profiles.get(i);
865        if (sd.hasExtension(ToolingExtensions.EXT_SD_DEPENDENCY)) {
866          for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_DEPENDENCY)) {
867            StructureDefinition dep = context.fetchResource( StructureDefinition.class, ext.getValue().primitiveValue(), sd);
868            if (dep == null) {
869              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());                
870            } else if (!profiles.contains(dep)) {
871              profiles.add(dep);
872            }
873          }
874        }
875        i++;
876      }
877      for (StructureDefinition defn : profiles) {
878        validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.ConfigProfile));
879      }
880    }
881    if (hintAboutNonMustSupport) {
882      checkElementUsage(errors, element, stack);
883    }
884    errors.removeAll(messagesToRemove);
885    timeTracker.overall(t);
886    if (DEBUG_ELEMENT) {
887      element.printToOutput();
888    }
889  }
890
891
892  private void checkElementUsage(List<ValidationMessage> errors, Element element, NodeStack stack) {
893    String elementUsage = element.getUserString("elementSupported");
894    hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage == null || elementUsage.equals("Y"), I18nConstants.MUSTSUPPORT_VAL_MUSTSUPPORT, element.getName(), element.getProperty().getStructure().getVersionedUrl());
895
896    if (element.hasChildren()) {
897      String prevName = "";
898      int elementCount = 0;
899      for (Element ce : element.getChildren()) {
900        if (ce.getName().equals(prevName))
901          elementCount++;
902        else {
903          elementCount = 1;
904          prevName = ce.getName();
905        }
906        checkElementUsage(errors, ce, stack.push(ce, elementCount, null, null));
907      }
908    }
909  }
910
911  private boolean check(String v1, String v2) {
912    return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
913  }
914
915  private boolean checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern) {
916    boolean ok = true;
917    ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern) && ok;
918    ok = checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern) && ok;
919    ok = checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), fixedSource, "city", focus, pattern) && ok;
920    ok = checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), fixedSource, "state", focus, pattern) && ok;
921    ok = checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), fixedSource, "country", focus, pattern) && ok;
922    ok = checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), fixedSource, "postalCode", focus, pattern) && ok;
923
924    List<Element> lines = new ArrayList<Element>();
925    focus.getNamedChildren("line", lines);
926    boolean lineSizeCheck;
927    
928    if (pattern) {
929      lineSizeCheck = lines.size() >= fixed.getLine().size();
930      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()),
931        Integer.toString(lines.size()))) {
932        for (int i = 0; i < fixed.getLine().size(); i++) {
933          StringType fixedLine = fixed.getLine().get(i);
934          boolean found = false;
935          List<ValidationMessage> allErrorsFixed = new ArrayList<>();
936          List<ValidationMessage> errorsFixed = null;
937          for (int j = 0; j < lines.size() && !found; ++j) {
938            errorsFixed = new ArrayList<>();
939            checkFixedValue(errorsFixed, path + ".line", lines.get(j), fixedLine, fixedSource, "line", focus, pattern);
940            if (!hasErrors(errorsFixed)) {
941              found = true;
942            } else {
943              errorsFixed.stream().filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal()).forEach(t -> allErrorsFixed.add(t));
944            }
945          }
946          if (!found) {
947            ok = rule(errorsFixed, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, false, I18nConstants.PATTERN_CHECK_STRING, fixedLine.getValue(), fixedSource, allErrorsFixed) && ok;
948          }
949        }
950      }
951    } else if (!pattern) {
952      lineSizeCheck = lines.size() == fixed.getLine().size();
953      if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, lineSizeCheck, I18nConstants.FIXED_TYPE_CHECKS_DT_ADDRESS_LINE,
954        Integer.toString(fixed.getLine().size()), Integer.toString(lines.size()))) {
955        for (int i = 0; i < lines.size(); i++) {
956          ok = checkFixedValue(errors, path + ".line", lines.get(i), fixed.getLine().get(i), fixedSource, "line", focus, pattern) && ok;
957        }
958      }
959    }  
960    return ok;
961  }
962
963  private boolean checkAttachment(List<ValidationMessage> errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern) {
964    boolean ok = true;
965    ok = checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), fixedSource, "contentType", focus, pattern) && ok;
966    ok = checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), fixedSource, "language", focus, pattern) && ok;
967    ok = checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern) && ok;
968    ok = checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), fixedSource, "url", focus, pattern) && ok;
969    ok = checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), fixedSource, "size", focus, pattern) && ok;
970    ok = checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), fixedSource, "hash", focus, pattern) && ok;
971    ok = checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), fixedSource, "title", focus, pattern) && ok;
972
973    return ok;
974  }
975
976  // public API
977  private boolean checkCode(List<ValidationMessage> errors, Element element, String path, String code, String system, String version, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException {
978    long t = System.nanoTime();
979    boolean ss = context.supportsSystem(system);
980    timeTracker.tx(t, "ss "+system);
981    if (ss) {
982      t = System.nanoTime();
983      ValidationResult s = checkCodeOnServer(stack, code, system, version, display, checkDisplay);
984      timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'");
985      if (s == null)
986        return true;
987      if (s.isOk()) {
988        if (s.getMessage() != null)
989          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);
990        return true;
991      }
992      if (s.getErrorClass() != null && s.getErrorClass().isInfrastructure())
993        txWarning(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
994      else if (s.getSeverity() == IssueSeverity.INFORMATION)
995        txHint(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
996      else if (s.getSeverity() == IssueSeverity.WARNING)
997        txWarning(errors, NO_RULE_DATE, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
998      else
999        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);
1000      return true;
1001    } else if (system.startsWith("http://build.fhir.org") || system.startsWith("https://build.fhir.org")) {
1002      rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_WRONG_BUILD, system, suggestSystemForBuild(system));        
1003      return false;
1004    } 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")) {
1005      if (SIDUtilities.isknownCodeSystem(system)) {
1006        return true; // else don't check these (for now)
1007      } else if (system.startsWith("http://hl7.org/fhir/test")) {
1008        return true; // we don't validate these
1009      } else if (system.endsWith(".html")) {
1010        rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_WRONG_HTML, system, suggestSystemForPage(system));        
1011        return false;
1012      } else {
1013        CodeSystem cs = getCodeSystem(system);
1014        if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, I18nConstants.TERMINOLOGY_TX_SYSTEM_UNKNOWN, system)) {
1015          ConceptDefinitionComponent def = getCodeDefinition(cs, code);
1016          if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, I18nConstants.TERMINOLOGY_TX_CODE_UNKNOWN, system, code))
1017            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());
1018        }
1019        return false;
1020      }
1021    } else if (context.isNoTerminologyServer() && Utilities.existsInList(system, "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")) {
1022      return true; // no checks in this case
1023    } else if (startsWithButIsNot(system, "http://snomed.info/sct", "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
1024      rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_INVALID, system);
1025      return false;
1026    } else {
1027      try {
1028        if (context.fetchResourceWithException(ValueSet.class, system, element.getProperty().getStructure()) != null) {
1029          rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET, system);
1030          // 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.
1031        }
1032        boolean done = false;
1033        if (system.startsWith("https:") && system.length() > 7) {
1034          String ns = "http:"+system.substring(6);
1035          CodeSystem cs = getCodeSystem(ns);
1036          if (cs != null || Utilities.existsInList(system, "https://loinc.org", "https://unitsofmeasure.org", "https://snomed.info/sct", "https://www.nlm.nih.gov/research/umls/rxnorm")) {
1037            rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_HTTPS, system);
1038            done = true;
1039          }           
1040        } 
1041        if (!isAllowExamples() || !Utilities.startsWithInList(system, "http://example.org", "https://example.org")) {
1042          hint(errors, NO_RULE_DATE, IssueType.UNKNOWN, element.line(), element.col(), path, done, I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system);
1043        }
1044        return true;
1045      } catch (Exception e) {
1046        return true;
1047      }
1048    }
1049  }
1050
1051  private Object suggestSystemForPage(String system) {
1052    if (system.contains("/codesystem-")) {
1053      String s = system.substring(system.indexOf("/codesystem-")+12);
1054      String url = "http://hl7.org/fhir/"+s.replace(".html", "");
1055      if (context.fetchCodeSystem(url) != null) {
1056        return url;
1057      } else {
1058        return "{unable to determine intended url}";
1059      }
1060    }
1061    if (system.contains("/valueset-")) {
1062      String s = system.substring(system.indexOf("/valueset-")+8);
1063      String url = "http://hl7.org/fhir/"+s.replace(".html", "");
1064      if (context.fetchCodeSystem(url) != null) {
1065        return url;
1066      } else {
1067        return "{unable to determine intended url}";
1068      }
1069    }
1070    return "{unable to determine intended url}";
1071  }
1072
1073  private Object suggestSystemForBuild(String system) {
1074    if (system.contains("/codesystem-")) {
1075      String s = system.substring(system.indexOf("/codesystem-")+12);
1076      String url = "http://hl7.org/fhir/"+s.replace(".html", "");
1077      if (context.fetchCodeSystem(url) != null) {
1078        return url;
1079      } else {
1080        return "{unable to determine intended url}";
1081      }
1082    }
1083    if (system.contains("/valueset-")) {
1084      String s = system.substring(system.indexOf("/valueset-")+8);
1085      String url = "http://hl7.org/fhir/"+s.replace(".html", "");
1086      if (context.fetchCodeSystem(url) != null) {
1087        return url;
1088      } else {
1089        return "{unable to determine intended url}";
1090      }
1091    }
1092    system = system.replace("https://", "http://");
1093    if (system.length() < 22) {
1094      return "{unable to determine intended url}";
1095    }
1096    system = "http://hl7.org/fhir/"+system.substring(22).replace(".html", "");
1097    if (context.fetchCodeSystem(system) != null) {
1098      return system;
1099    } else {
1100      return "{unable to determine intended url}";
1101    }
1102  }
1103  
1104  private boolean startsWithButIsNot(String system, String... uri) {
1105    for (String s : uri)
1106      if (!system.equals(s) && system.startsWith(s))
1107        return true;
1108    return false;
1109  }
1110
1111
1112  private boolean hasErrors(List<ValidationMessage> errors) {
1113    if (errors != null) {
1114      for (ValidationMessage vm : errors) {
1115        if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) {
1116          return true;
1117        }
1118      }
1119    }
1120    return false;
1121  }
1122
1123  private boolean checkCodeableConcept(List<ValidationMessage> errors, String path, Element focus, CodeableConcept fixed, String fixedSource, boolean pattern) {
1124    boolean ok = true;
1125    ok = checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern) && ok;
1126    List<Element> codings = new ArrayList<Element>();
1127    focus.getNamedChildren("coding", codings);
1128    if (pattern) {
1129      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()))) {
1130        for (int i = 0; i < fixed.getCoding().size(); i++) {
1131          Coding fixedCoding = fixed.getCoding().get(i);
1132          boolean found = false;
1133          List<ValidationMessage> allErrorsFixed = new ArrayList<>();
1134          List<ValidationMessage> errorsFixed;
1135          for (int j = 0; j < codings.size() && !found; ++j) {
1136            errorsFixed = new ArrayList<>();
1137            ok = checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern) && ok;
1138            if (!hasErrors(errorsFixed)) {
1139              found = true;
1140            } else {
1141              errorsFixed
1142                .stream()
1143                .filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal())
1144                .forEach(t -> allErrorsFixed.add(t));
1145            }
1146          }
1147          if (!found) {
1148            // The argonaut DSTU2 labs profile requires userSelected=false on the category.coding and this
1149            // needs to produce an understandable error message
1150//            String message = "Expected CodeableConcept " + (pattern ? "pattern" : "fixed value") + " not found for" +
1151//              " system: " + fixedCoding.getSystemElement().asStringValue() +
1152//              " code: " + fixedCoding.getCodeElement().asStringValue() +
1153//              " display: " + fixedCoding.getDisplayElement().asStringValue();
1154//            if (fixedCoding.hasUserSelected()) {
1155//              message += " userSelected: " + fixedCoding.getUserSelected();
1156//            }
1157//            message += " - Issues: " + allErrorsFixed;
1158//            TYPE_CHECKS_PATTERN_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4}
1159//            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} 
1160//            TYPE_CHECKS_FIXED_CC = The pattern [system {0}, code {1}, and display "{2}"] defined in the profile {3} not found. Issues: {4}
1161//            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} 
1162
1163            if (fixedCoding.hasUserSelected()) {
1164              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, 
1165                  fixedCoding.getSystemElement().asStringValue(), fixedCoding.getCodeElement().asStringValue(), fixedCoding.getDisplayElement().asStringValue(),
1166                  fixedSource, allErrorsFixed, fixedCoding.getUserSelected()) && ok;
1167              
1168            } else {
1169              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, 
1170                  fixedCoding.getSystemElement().asStringValue(), fixedCoding.getCodeElement().asStringValue(), fixedCoding.getDisplayElement().asStringValue(),
1171                  fixedSource, allErrorsFixed) && ok;
1172            }
1173          }
1174        }
1175      } else {
1176        ok = false;
1177      }      
1178    } else {
1179      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()))) {
1180        for (int i = 0; i < codings.size(); i++)
1181          ok = checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus, false) && ok;
1182      } else {
1183        ok = false;
1184      }
1185    }
1186    return ok;
1187  }
1188
1189  private boolean checkCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack) {
1190    boolean res = true;
1191    if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
1192      ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
1193      if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) {
1194        if (binding.hasValueSet()) {
1195          ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl(), profile);
1196          if (valueset == null) {
1197            CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
1198            if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
1199              warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
1200            }
1201          } else {
1202            try {
1203              CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
1204              if (!cc.hasCoding()) {
1205                if (binding.getStrength() == BindingStrength.REQUIRED)
1206                  rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET, describeReference(binding.getValueSet(), valueset));
1207                else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1208                  if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1209                    rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)), valueset.getVersionedUrl());
1210                  else
1211                    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(binding.getValueSet(), valueset));
1212                }
1213              } else {
1214                long t = System.nanoTime();
1215
1216                // Check whether the codes are appropriate for the type of binding we have
1217                boolean bindingsOk = true;
1218                if (binding.getStrength() != BindingStrength.EXAMPLE) {
1219                  if (binding.getStrength() == BindingStrength.REQUIRED) {
1220                    removeTrackedMessagesForLocation(errors, element, path);
1221                  }
1222                  boolean atLeastOneSystemIsSupported = false;
1223                  for (Coding nextCoding : cc.getCoding()) {
1224                    String nextSystem = nextCoding.getSystem();
1225                    if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
1226                      atLeastOneSystemIsSupported = true;
1227                      break;
1228                    }
1229                  }
1230
1231                  if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
1232                    // ignore this since we can't validate but it doesn't matter..
1233                  } else {
1234                    ValidationResult vr = checkCodeOnServer(stack, valueset, cc, true); // we're going to validate the codings directly, so only check the valueset
1235                    if (!vr.isOk()) {
1236                      bindingsOk = false;
1237                      if (vr.getErrorClass() != null && vr.getErrorClass() == TerminologyServiceErrorClass.NOSERVICE) { 
1238                        if (binding.getStrength() == BindingStrength.REQUIRED || (binding.getStrength() == BindingStrength.EXTENSIBLE && binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))) {
1239                          txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOSVC_BOUND_REQ, describeReference(binding.getValueSet()));
1240                        } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1241                          txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOSVC_BOUND_EXT, describeReference(binding.getValueSet()));
1242                        }
1243                      } else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
1244                        if (binding.getStrength() == BindingStrength.REQUIRED)
1245                          txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString());
1246                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1247                          if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1248                            checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack);
1249                          else if (!noExtensibleWarnings)
1250                            txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString());
1251                        } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1252                          if (baseOnly) {
1253                            txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_3_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString());
1254                          }
1255                        }
1256                      } else {
1257                        if (binding.getStrength() == BindingStrength.REQUIRED) {
1258                          txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc));
1259                        } else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1260                          if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1261                            checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack);
1262                          if (!noExtensibleWarnings)
1263                            txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc));
1264                        } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1265                          if (baseOnly) {
1266                            txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc));
1267                          }
1268                        }
1269                      }
1270                    } else if (vr.getMessage() != null) {
1271                      res = false;
1272                      txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
1273                    } else {
1274                      if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1275                        removeTrackedMessagesForLocation(errors, element, path);
1276                      }
1277                      res = false;
1278                    }
1279                  }
1280                  // Then, for any codes that are in code systems we are able
1281                  // to validate, we'll validate that the codes actually exist
1282                  if (bindingsOk) {
1283                    for (Coding nextCoding : cc.getCoding()) {
1284                      checkBindings(errors, path, element, stack, valueset, nextCoding);
1285                    }
1286                  }
1287                  timeTracker.tx(t, "vc "+cc.toString());
1288                }
1289              }
1290            } catch (Exception e) {
1291              if (STACK_TRACE) e.printStackTrace();
1292              warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage());
1293            }
1294          }
1295        } else if (binding.hasValueSet()) {
1296          hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
1297        } else if (!noBindingMsgSuppressed) {
1298          hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
1299        }
1300      }
1301    }
1302    return res;
1303  }
1304
1305  public void checkBindings(List<ValidationMessage> errors, String path, Element element, NodeStack stack, ValueSet valueset, Coding nextCoding) {
1306    if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem())) {
1307      ValidationResult vr = checkCodeOnServer(stack, valueset, nextCoding, false);
1308      if (vr.getSeverity() != null/* && vr.hasMessage()*/) {
1309        if (vr.getSeverity() == IssueSeverity.INFORMATION) {
1310          txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
1311        } else if (vr.getSeverity() == IssueSeverity.WARNING) {
1312          txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
1313        } else {
1314          txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
1315        }
1316      }
1317    }
1318  }
1319
1320
1321  private boolean checkTerminologyCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, StructureDefinition logical) {
1322    boolean res = true;
1323    if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
1324      ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
1325      if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING, path)) {
1326        if (binding.hasValueSet()) {
1327          ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl(), profile);
1328          if (valueset == null) {
1329            CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
1330            if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
1331              warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
1332            }
1333          } else {
1334            try {
1335              CodeableConcept cc = convertToCodeableConcept(element, logical);
1336              if (!cc.hasCoding()) {
1337                if (binding.getStrength() == BindingStrength.REQUIRED)
1338                  rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getVersionedUrl());
1339                else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1340                  if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1341                    rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESETMAX, describeReference(ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET)), valueset.getVersionedUrl());
1342                  else
1343                    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_VALUESET_EXT, describeReference(binding.getValueSet(), valueset));
1344                }
1345              } else {
1346                long t = System.nanoTime();
1347
1348                // Check whether the codes are appropriate for the type of binding we have
1349                boolean bindingsOk = true;
1350                if (binding.getStrength() != BindingStrength.EXAMPLE) {
1351                  if (binding.getStrength() == BindingStrength.REQUIRED) {
1352                    removeTrackedMessagesForLocation(errors, element, path);
1353                  }
1354
1355                  boolean atLeastOneSystemIsSupported = false;
1356                  for (Coding nextCoding : cc.getCoding()) {
1357                    String nextSystem = nextCoding.getSystem();
1358                    if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
1359                      atLeastOneSystemIsSupported = true;
1360                      break;
1361                    }
1362                  }
1363
1364                  if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
1365                    // ignore this since we can't validate but it doesn't matter..
1366                  } else {
1367                    ValidationResult vr = checkCodeOnServer(stack, valueset, cc, false); // we're going to validate the codings directly
1368                    if (!vr.isOk()) {
1369                      bindingsOk = false;
1370                      if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
1371                        if (binding.getStrength() == BindingStrength.REQUIRED)
1372                          txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_1_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString());
1373                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1374                          if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1375                            checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack);
1376                          else if (!noExtensibleWarnings)
1377                            txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_2_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString());
1378                        } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1379                          if (baseOnly) {
1380                            txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_3_CC, describeReference(binding.getValueSet()), vr.getErrorClass().toString());
1381                          }
1382                        }
1383                      } else {
1384                        if (binding.getStrength() == BindingStrength.REQUIRED)
1385                          txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_1_CC, describeReference(binding.getValueSet()), valueset, ccSummary(cc));
1386                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1387                          if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1388                            checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), cc, stack);
1389                          if (!noExtensibleWarnings)
1390                            txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_2_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc));
1391                        } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1392                          if (baseOnly) {
1393                            txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_3_CC, describeReference(binding.getValueSet(), valueset), ccSummary(cc));
1394                          }
1395                        }
1396                      }
1397                    } else if (vr.getMessage() != null) {
1398                      res = false;
1399                      txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
1400                    } else {
1401                      res = false;
1402                    }
1403                  }
1404                  // Then, for any codes that are in code systems we are able
1405                  // to validate, we'll validate that the codes actually exist
1406                  if (bindingsOk) {
1407                    for (Coding nextCoding : cc.getCoding()) {
1408                      String nextCode = nextCoding.getCode();
1409                      String nextSystem = nextCoding.getSystem();
1410                      String nextVersion = nextCoding.getVersion();
1411                      if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
1412                        ValidationResult vr = checkCodeOnServer(stack, nextCode, nextSystem, nextVersion, null, false);
1413                        if (!vr.isOk()) {
1414                          txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CODE_NOTVALID, nextCode, nextSystem);
1415                        }
1416                      }
1417                    }
1418                  }
1419                  timeTracker.tx(t, DataRenderer.display(context, cc));
1420                }
1421              }
1422            } catch (Exception e) {
1423              if (STACK_TRACE) e.printStackTrace();
1424              warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT, e.getMessage());
1425            }
1426            // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding.
1427            if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) {
1428              checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical);
1429            }
1430          }
1431        } else if (binding.hasValueSet()) {
1432          hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
1433        } else if (!noBindingMsgSuppressed) {
1434          hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
1435        }
1436      }
1437    }
1438    return res;
1439  }
1440
1441  private boolean checkTerminologyCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) {
1442    boolean ok = false;
1443    Coding c = convertToCoding(element, logical);
1444    String code = c.getCode();
1445    String system = c.getSystem();
1446    String display = c.getDisplay();
1447    String version = c.getVersion();
1448    ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isCodeSystemReferenceValid(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE) && ok;
1449
1450    if (system != null && code != null && !noTerminologyChecks) {
1451      ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, system) && ok;
1452      try {
1453        if (checkCode(errors, element, path, code, system, version, display, checkDisplay, stack)) {
1454          if (theElementCntext != null && theElementCntext.hasBinding()) {
1455            ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
1456            if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) {
1457              if (binding.hasValueSet()) {
1458                ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl(), profile);
1459                if (valueset == null) {
1460                  CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
1461                  if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
1462                    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
1463                  } else {
1464                    ok = false;
1465                  }
1466                } else {
1467                  try {
1468                    long t = System.nanoTime();
1469                    ValidationResult vr = null;
1470                    if (binding.getStrength() != BindingStrength.EXAMPLE) {
1471                      vr = checkCodeOnServer(stack, valueset, c, true);
1472                    }
1473                    if (binding.getStrength() == BindingStrength.REQUIRED) {
1474                      removeTrackedMessagesForLocation(errors, element, path);
1475                    }
1476
1477                    timeTracker.tx(t, "vc "+system+"#"+code+" '"+display+"'");
1478                    if (vr != null && !vr.isOk()) {
1479                      if (vr.IsNoService())
1480                        txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER);
1481                      else if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
1482                        if (binding.getStrength() == BindingStrength.REQUIRED)
1483                          txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(binding.getValueSet(), valueset), vr.getMessage(), system+"#"+code);
1484                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1485                          if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1486                            checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), c, stack);
1487                          else if (!noExtensibleWarnings)
1488                            txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(binding.getValueSet(), valueset));
1489                        } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1490                          if (baseOnly) {
1491                            txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(binding.getValueSet(), valueset));
1492                          }
1493                        }
1494                      } else if (binding.getStrength() == BindingStrength.REQUIRED)
1495                        ok= txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_4, describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code) && ok;
1496                      else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1497                        if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1498                          ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), c, stack) && ok;
1499                        else
1500                          txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_5, describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code);
1501                      } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1502                        if (baseOnly) {
1503                          txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_6, describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""), system+"#"+code);
1504                        }
1505                      }
1506                    }
1507                  } catch (Exception e) {
1508                    if (STACK_TRACE) e.printStackTrace();
1509                    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING1, e.getMessage());
1510                  }
1511                }
1512              } else if (binding.hasValueSet()) {
1513                hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
1514              } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
1515                hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
1516              }
1517            }
1518          }
1519        } else {
1520          ok = false;
1521        }
1522      } catch (Exception e) {
1523        if (STACK_TRACE) e.printStackTrace();
1524        rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING2, e.getMessage(), e.toString());
1525        ok = false;
1526      }
1527    }
1528    return ok;
1529  }
1530
1531  private CodeableConcept convertToCodeableConcept(Element element, StructureDefinition logical) {
1532    CodeableConcept res = new CodeableConcept();
1533    for (ElementDefinition ed : logical.getSnapshot().getElement()) {
1534      if (Utilities.charCount(ed.getPath(), '.') == 1) {
1535        List<String> maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
1536        for (String m : maps) {
1537          String name = tail(ed.getPath());
1538          List<Element> list = new ArrayList<>();
1539          element.getNamedChildren(name, list);
1540          if (!list.isEmpty()) {
1541            if ("Coding.code".equals(m)) {
1542              res.getCodingFirstRep().setCode(list.get(0).primitiveValue());
1543            } else if ("Coding.system[fmt:OID]".equals(m)) {
1544              String oid = list.get(0).primitiveValue();
1545              String url = new ContextUtilities(context).oid2Uri(oid);
1546              if (url != null) {
1547                res.getCodingFirstRep().setSystem(url);
1548              } else {
1549                res.getCodingFirstRep().setSystem("urn:oid:" + oid);
1550              }
1551            } else if ("Coding.version".equals(m)) {
1552              res.getCodingFirstRep().setVersion(list.get(0).primitiveValue());
1553            } else if ("Coding.display".equals(m)) {
1554              res.getCodingFirstRep().setDisplay(list.get(0).primitiveValue());
1555            } else if ("CodeableConcept.text".equals(m)) {
1556              res.setText(list.get(0).primitiveValue());
1557            } else if ("CodeableConcept.coding".equals(m)) {
1558              StructureDefinition c = context.fetchTypeDefinition(ed.getTypeFirstRep().getCode());
1559              for (Element e : list) {
1560                res.addCoding(convertToCoding(e, c));
1561              }
1562            }
1563          }
1564        }
1565      }
1566    }
1567    return res;
1568  }
1569
1570  private Coding convertToCoding(Element element, StructureDefinition logical) {
1571    Coding res = new Coding();
1572    for (ElementDefinition ed : logical.getSnapshot().getElement()) {
1573      if (Utilities.charCount(ed.getPath(), '.') == 1) {
1574        List<String> maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
1575        for (String m : maps) {
1576          String name = tail(ed.getPath());
1577          List<Element> list = new ArrayList<>();
1578          element.getNamedChildren(name, list);
1579          if (!list.isEmpty()) {
1580            if ("Coding.code".equals(m)) {
1581              res.setCode(list.get(0).primitiveValue());
1582            } else if ("Coding.system[fmt:OID]".equals(m)) {
1583              String oid = list.get(0).primitiveValue();
1584              String url = new ContextUtilities(context).oid2Uri(oid);
1585              if (url != null) {
1586                res.setSystem(url);
1587              } else {
1588                res.setSystem("urn:oid:" + oid);
1589              }
1590            } else if ("Coding.version".equals(m)) {
1591              res.setVersion(list.get(0).primitiveValue());
1592            } else if ("Coding.display".equals(m)) {
1593              res.setDisplay(list.get(0).primitiveValue());
1594            }
1595          }
1596        }
1597      }
1598    }
1599    return res;
1600  }
1601
1602  private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) {
1603    ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile);
1604    if (valueset == null) {
1605      CodeSystem cs = context.fetchCodeSystem(maxVSUrl);
1606      if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) {
1607        warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl));
1608      }
1609    } else {
1610      try {
1611        long t = System.nanoTime();
1612        ValidationResult vr = checkCodeOnServer(stack, valueset, cc, false);
1613        timeTracker.tx(t, "vc "+cc.toString());
1614        if (!vr.isOk()) {
1615          if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
1616            txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_7, describeReference(maxVSUrl, valueset), vr.getMessage());
1617          else
1618            txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_8, describeReference(maxVSUrl, valueset), ccSummary(cc));
1619        }
1620      } catch (Exception e) {
1621        if (STACK_TRACE) e.printStackTrace();
1622        warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage());
1623      }
1624    }
1625  }
1626
1627//  private String describeValueSet(String url) {
1628//    ValueSet vs = context.fetchResource(ValueSet.class, url);
1629//    if (vs != null) {
1630//      return "'"+vs.present()+"' ("+url+")";
1631//    } else {
1632//      return "("+url+")";
1633//    }
1634//  }
1635
1636  private boolean checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, Coding c, NodeStack stack) {
1637    boolean ok = true;
1638    ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile);
1639    if (valueset == null) {
1640      CodeSystem cs = context.fetchCodeSystem(maxVSUrl);
1641      if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) {
1642        warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl));
1643      } else {
1644        ok = false;
1645      }
1646    } else {
1647      try {
1648        long t = System.nanoTime();
1649        ValidationResult vr = checkCodeOnServer(stack, valueset, c, true);
1650        timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
1651        if (!vr.isOk()) {
1652          if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
1653            txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset), vr.getMessage());
1654          else
1655            ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_10, describeReference(maxVSUrl, valueset), c.getSystem(), c.getCode()) && ok;
1656        }
1657      } catch (Exception e) {
1658        if (STACK_TRACE) e.printStackTrace();
1659        warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage());
1660      }
1661    }
1662    return ok;
1663  }
1664
1665  private boolean checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) {
1666    boolean ok = true;
1667    ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl(), profile);
1668    if (valueset == null) {
1669      CodeSystem cs = context.fetchCodeSystem(maxVSUrl);
1670      if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(maxVSUrl))) {
1671        warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(maxVSUrl));
1672      } else {
1673        ok = false;
1674      }
1675    } else {
1676      try {
1677        long t = System.nanoTime();
1678        ValidationResult vr = checkCodeOnServer(stack, valueset, value, baseOptions.setLanguage(stack.getWorkingLang()));
1679        timeTracker.tx(t, "vc "+value);
1680        if (!vr.isOk()) {
1681          if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
1682            txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_9, describeReference(maxVSUrl, valueset), vr.getMessage());
1683          else {
1684            ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_11, describeReference(maxVSUrl, valueset), vr.getMessage()) && ok;
1685          }
1686        }
1687      } catch (Exception e) {
1688        if (STACK_TRACE) e.printStackTrace();
1689          warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODEABLECONCEPT_MAX, e.getMessage());
1690      }
1691    }
1692    return ok;
1693  }
1694
1695  private String ccSummary(CodeableConcept cc) {
1696    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1697    for (Coding c : cc.getCoding())
1698      b.append(c.getSystem() + "#" + c.getCode());
1699    return b.toString();
1700  }
1701
1702  private boolean checkCoding(List<ValidationMessage> errors, String path, Element focus, Coding fixed, String fixedSource, boolean pattern) {
1703    boolean ok = true;
1704    ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern) && ok;
1705    ok = checkFixedValue(errors, path + ".version", focus.getNamedChild("version"), fixed.getVersionElement(), fixedSource, "version", focus, pattern) && ok;
1706    ok = checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern) && ok;
1707    ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern) && ok;
1708    ok = checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern) && ok;
1709    return ok;
1710  }
1711
1712  private boolean checkCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) {
1713    String code = element.getNamedChildValue("code");
1714    String system = element.getNamedChildValue("system");
1715    String version = element.getNamedChildValue("version");
1716    String display = element.getNamedChildValue("display");
1717    return checkCodedElement(errors, path, element, profile, theElementCntext, inCodeableConcept, checkDisplay, stack, code, system, version, display);
1718  }
1719
1720  private boolean checkCodedElement(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack,
1721      String theCode, String theSystem, String theVersion, String theDisplay) {
1722    boolean ok = true;
1723    
1724    ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, theSystem == null || isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE) && ok;
1725    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
1726
1727    if (theSystem != null && theCode != null && !noTerminologyChecks) {
1728      ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, theSystem) && ok;
1729      try {
1730        if (checkCode(errors, element, path, theCode, theSystem, theVersion, theDisplay, checkDisplay, stack))
1731          if (theElementCntext != null && theElementCntext.hasBinding()) {
1732            ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
1733            if (warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, I18nConstants.TERMINOLOGY_TX_BINDING_MISSING2, path)) {
1734              if (binding.hasValueSet()) {
1735                ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl(), profile);
1736                if (valueset == null) {
1737                  CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
1738                  if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
1739                    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
1740                  } else {
1741                    ok = false;
1742                  }
1743                } else {  
1744                  try {
1745                    Coding c = ObjectConverter.readAsCoding(element);
1746                    long t = System.nanoTime();
1747                    ValidationResult vr = null;
1748                    if (binding.getStrength() != BindingStrength.EXAMPLE) {
1749                      vr = checkCodeOnServer(stack, valueset, c, true);
1750                    }
1751                    timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'");
1752                    if (binding.getStrength() == BindingStrength.REQUIRED) {
1753                      removeTrackedMessagesForLocation(errors, element, path);
1754                    }
1755
1756                    if (vr != null && !vr.isOk()) {
1757                      if (vr.IsNoService())
1758                        txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSERVER);
1759                      else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
1760                        if (binding.getStrength() == BindingStrength.REQUIRED)
1761                          ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_4a, describeReference(binding.getValueSet(), valueset), vr.getMessage(), theSystem+"#"+theCode) && ok;
1762                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1763                          if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1764                            checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), c, stack);
1765                          else if (!noExtensibleWarnings)
1766                            txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_5, describeReference(binding.getValueSet(), valueset));
1767                        } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1768                          if (baseOnly) {
1769                            txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_CONFIRM_6, describeReference(binding.getValueSet(), valueset));
1770                          }
1771                        }
1772                      } else if (binding.getStrength() == BindingStrength.REQUIRED)
1773                        ok = txRule(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_12, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode) && ok;
1774                      else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1775                        if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
1776                          ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), c, stack) && ok;
1777                        else if (!noExtensibleWarnings) {
1778                          txWarningForLaterRemoval(element, errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_13, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), c.getSystem()+"#"+c.getCode());
1779                        }
1780                      } else if (binding.getStrength() == BindingStrength.PREFERRED) {
1781                        if (baseOnly) {
1782                          txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode);
1783                        }
1784                      }
1785                    } else if (vr != null && vr.getMessage() != null) {
1786                      txWarning(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
1787                    }
1788                  } catch (Exception e) {
1789                    if (STACK_TRACE) e.printStackTrace();
1790                    warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING1, e.getMessage());
1791                  }
1792                }
1793              } else if (binding.hasValueSet()) {
1794                hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_CANTCHECK);
1795              } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
1796                hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE, path);
1797              }
1798            }
1799          }
1800      } catch (Exception e) {
1801        if (STACK_TRACE) e.printStackTrace();
1802        rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_ERROR_CODING2, e.getMessage(), e.toString());
1803        ok = false;
1804      }
1805    }
1806    return ok;
1807  }
1808
1809  private boolean isValueSet(String url) {
1810    try {
1811      ValueSet vs = context.fetchResourceWithException(ValueSet.class, url);
1812      return vs != null;
1813    } catch (Exception e) {
1814      return false;
1815    }
1816  }
1817
1818  private boolean checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed, String fixedSource, boolean pattern) {
1819    boolean ok = true;
1820    ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern) && ok;
1821    ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok;
1822    ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern) && ok;
1823    ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern) && ok;
1824    return ok;
1825  }
1826
1827  private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> 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 {
1828    String url = element.getNamedChildValue("url");
1829    boolean isModifier = element.getName().equals("modifierExtension");
1830    assert def.getIsModifier() == isModifier;
1831    
1832    long t = System.nanoTime();
1833    StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
1834    timeTracker.sd(t);
1835    if (ex == null) {
1836      ex = getXverExt(errors, path, element, url);
1837    }
1838    if (ex == null) {
1839      if (extensionUrl != null && !isAbsolute(url)) {
1840        if (extensionUrl.equals(profile.getUrl())) {
1841          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());
1842        }
1843      } else if (SpecialExtensions.isKnownExtension(url)) {
1844        ex = SpecialExtensions.getDefinition(url);
1845      } else if (Utilities.existsInList(url, BuildExtensions.allConsts())) {
1846        // nothing
1847      } else if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN_NOTHERE, url)) {
1848        hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), I18nConstants.EXTENSION_EXT_UNKNOWN, url);
1849      }
1850    }
1851    if (ex != null) {
1852      trackUsage(ex, hostContext, element);
1853      // check internal definitions are coherent
1854      if (isModifier) {
1855        rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY);
1856      } else {
1857        rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN);
1858      }
1859      // two questions
1860      // 1. can this extension be used here?
1861      checkExtensionContext(errors, resource, container, ex, containerStack, hostContext, isModifier);
1862
1863      if (isModifier)
1864        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);
1865      else
1866        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);
1867
1868      // check the type of the extension:
1869      Set<String> allowedTypes = listExtensionTypes(ex);
1870      String actualType = getExtensionType(element);
1871      if (actualType != null)
1872        rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), I18nConstants.EXTENSION_EXT_TYPE, url, allowedTypes.toString(), actualType);
1873      else if (element.hasChildren("extension"))
1874        rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_WRONG, url);
1875      else
1876        rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), I18nConstants.EXTENSION_EXT_SIMPLE_ABSENT, url);
1877
1878      // 3. is the content of the extension valid?
1879      validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url, pct, mode);
1880
1881    }
1882    return ex;
1883  }
1884
1885 
1886
1887  private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) {
1888    for (ElementDefinition ed : profile.getSnapshot().getElement()) {
1889      if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) {
1890        return true;
1891      }
1892    }
1893    return false;
1894  }
1895
1896  private String getExtensionType(Element element) {
1897    for (Element e : element.getChildren()) {
1898      if (e.getName().startsWith("value")) {
1899        String tn = e.getName().substring(5);
1900        String ltn = Utilities.uncapitalize(tn);
1901        if (isPrimitiveType(ltn))
1902          return ltn;
1903        else
1904          return tn;
1905      }
1906    }
1907    return null;
1908  }
1909
1910  private Set<String> listExtensionTypes(StructureDefinition ex) {
1911    ElementDefinition vd = null;
1912    for (ElementDefinition ed : ex.getSnapshot().getElement()) {
1913      if (ed.getPath().startsWith("Extension.value")) {
1914        vd = ed;
1915        break;
1916      }
1917    }
1918    Set<String> res = new HashSet<String>();
1919    if (vd != null && !"0".equals(vd.getMax())) {
1920      for (TypeRefComponent tr : vd.getType()) {
1921        res.add(tr.getWorkingCode());
1922      }
1923    }
1924    // special hacks 
1925    if (ex.getUrl().equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type")) {
1926      res.add("uri");
1927      res.add("url");
1928    }
1929    return res;
1930  }
1931
1932  private boolean checkExtensionContext(List<ValidationMessage> errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext, boolean modifier) {
1933    String extUrl = definition.getUrl();
1934    boolean ok = false;
1935    CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder();
1936    List<String> plist = new ArrayList<>();
1937    plist.add(stripIndexes(stack.getLiteralPath()));
1938    for (String s : stack.getLogicalPaths()) {
1939      String p = stripIndexes(s);
1940      // 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....
1941      if (Utilities.existsInList(p, "ElementDefinition.example.value", "ElementDefinition.pattern", "ElementDefinition.fixed")) {
1942        return true;
1943      }
1944      plist.add(p);
1945
1946    }
1947
1948    for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) {
1949      if (ok) {
1950        break;
1951      }
1952      if (ctxt.getType() == ExtensionContextType.ELEMENT) {
1953        String en = ctxt.getExpression();
1954        contexts.append("e:" + en);
1955        if (Utilities.existsInList(en, "Element", "Any")) {
1956          ok = true;
1957        } else if (en.equals("Resource") && container.isResource()) {
1958          ok = true;
1959        }
1960        for (String p : plist) {
1961          if (ok) {
1962            break;
1963          }
1964          if (p.equals(en)) {
1965            ok = true;
1966          } else {
1967            String pn = p;
1968            String pt = "";
1969            if (p.contains(".")) {
1970              pn = p.substring(0, p.indexOf("."));
1971              pt = p.substring(p.indexOf("."));
1972            }
1973            StructureDefinition sd = context.fetchTypeDefinition(pn);
1974            while (sd != null) {
1975              if ((sd.getType() + pt).equals(en)) {
1976                ok = true;
1977                break;
1978              }
1979              if (sd.getBaseDefinition() != null) {
1980                sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
1981              } else {
1982                sd = null;
1983              }
1984            }
1985          }
1986        }
1987      } else if (ctxt.getType() == ExtensionContextType.EXTENSION) {
1988        contexts.append("x:" + ctxt.getExpression());
1989        NodeStack estack = stack.getParent();
1990        if (estack != null && estack.getElement().fhirType().equals("Extension")) {
1991          String ext = estack.getElement().getNamedChildValue("url");
1992          if (ctxt.getExpression().equals(ext)) {
1993            ok = true;
1994          }
1995        }
1996      } else if (ctxt.getType() == ExtensionContextType.FHIRPATH) {
1997        contexts.append("p:" + ctxt.getExpression());
1998        // The context is all elements that match the FHIRPath query found in the expression.
1999        List<Base> res = fpe.evaluate(hostContext, resource, hostContext.getRootResource(), resource, fpe.parse(ctxt.getExpression()));
2000        if (res.contains(container)) {
2001          ok = true;
2002        }
2003      } else {
2004        throw new Error(context.formatMessage(I18nConstants.UNRECOGNISED_EXTENSION_CONTEXT_, ctxt.getTypeElement().asStringValue()));
2005      }
2006    }
2007    if (!ok) {
2008      if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) {
2009        warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
2010            modifier ? I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_XVER : I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString());
2011      } else {
2012        rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
2013            modifier ? I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG : I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString());        
2014      }
2015      return false;
2016    } else {
2017      if (definition.hasContextInvariant()) {
2018        for (StringType s : definition.getContextInvariant()) {
2019          if (!fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), container, fpe.parse(s.getValue()))) {
2020            if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) {
2021              warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.PROFILE_EXT_NOT_HERE, extUrl, s.getValue());              
2022              return true;
2023            } else {
2024              rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false, I18nConstants.PROFILE_EXT_NOT_HERE, extUrl, s.getValue());
2025              return false;
2026            }
2027          }
2028        }
2029      }
2030      return true;
2031    }
2032  }
2033
2034  private List<StructureDefinitionContextComponent> fixContexts(String extUrl, List<StructureDefinitionContextComponent> list) {
2035    List<StructureDefinitionContextComponent> res = new ArrayList<>();
2036    for (StructureDefinitionContextComponent ctxt : list) {
2037      res.add(ctxt.copy());
2038    }
2039    if (ToolingExtensions.EXT_FHIR_TYPE.equals(extUrl)) {
2040      list.get(0).setExpression("ElementDefinition.type");
2041    }
2042    // the history of this is a mess - see https://jira.hl7.org/browse/FHIR-13328
2043    // we in practice we will support it in either place, but the specification says on ElementDefinition, not on ElementDefinition.type
2044    // but this creates validation errors people can't fix all over the place if we don't do this.
2045    if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) {
2046      StructureDefinitionContextComponent e = new StructureDefinitionContextComponent();
2047      e.setExpression("ElementDefinition.type");
2048      e.setType(ExtensionContextType.ELEMENT);
2049      list.add(e);
2050    }
2051    if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version".equals(extUrl)) {
2052      list.get(0).setExpression("Element"); // well, it can't be used anywhere but the list of places it can be used is quite long
2053    }
2054    if (!VersionUtilities.isThisOrLater("4.6", context.getVersion())) {
2055      if (Utilities.existsInList(extUrl, "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", "http://hl7.org/fhir/StructureDefinition/capabilitystatement-prohibited")) {
2056        list.get(0).setExpression("Element"); // well, they can't be used anywhere but the list of places they can be used is quite long        
2057      }
2058    }
2059    return list;
2060  }
2061
2062  private String stripIndexes(String path) {
2063    boolean skip = false;
2064    StringBuilder b = new StringBuilder();
2065    for (char c : path.toCharArray()) {
2066      if (skip) {
2067        if (c == ']') {
2068          skip = false;
2069        }
2070      } else if (c == '[') {
2071        skip = true;
2072      } else {
2073        b.append(c);
2074      }
2075    }
2076    return b.toString();
2077  }
2078
2079  @SuppressWarnings("rawtypes")
2080  private boolean checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent, boolean pattern) {
2081    boolean ok = true;
2082    if ((fixed == null || fixed.isEmpty()) && focus == null) {
2083      ; // this is all good
2084    } else if ((fixed == null || fixed.isEmpty()) && focus != null) {
2085      ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, pattern, I18nConstants.PROFILE_VAL_NOTALLOWED, focus.getName(), (pattern ? "pattern" : "fixed value"));
2086    } else if (fixed != null && !fixed.isEmpty() && focus == null) {
2087      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);
2088    } else {
2089      String value = focus.primitiveValue();
2090      if (fixed instanceof org.hl7.fhir.r5.model.BooleanType)
2091        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());
2092      else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType)
2093        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());
2094      else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType)
2095        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());
2096      else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType)
2097        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());
2098      else if (fixed instanceof org.hl7.fhir.r5.model.InstantType)
2099        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());
2100      else if (fixed instanceof org.hl7.fhir.r5.model.CodeType)
2101        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());
2102      else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration)
2103        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());
2104      else if (fixed instanceof org.hl7.fhir.r5.model.StringType)
2105        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());
2106      else if (fixed instanceof org.hl7.fhir.r5.model.UriType)
2107        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());
2108      else if (fixed instanceof org.hl7.fhir.r5.model.DateType)
2109        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());
2110      else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType)
2111        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());
2112      else if (fixed instanceof org.hl7.fhir.r5.model.OidType)
2113        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());
2114      else if (fixed instanceof org.hl7.fhir.r5.model.UuidType)
2115        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());
2116      else if (fixed instanceof org.hl7.fhir.r5.model.IdType)
2117        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());
2118      else if (fixed instanceof Quantity)
2119        checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern);
2120      else if (fixed instanceof Address)
2121        ok = checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern);
2122      else if (fixed instanceof ContactPoint)
2123        ok = checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern);
2124      else if (fixed instanceof Attachment)
2125        ok = checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern);
2126      else if (fixed instanceof Identifier)
2127        ok = checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern);
2128      else if (fixed instanceof Coding)
2129        ok = checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern);
2130      else if (fixed instanceof HumanName)
2131        ok = checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern);
2132      else if (fixed instanceof CodeableConcept)
2133        ok = checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern);
2134      else if (fixed instanceof Timing)
2135        ok = checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern);
2136      else if (fixed instanceof Period)
2137        ok = checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern);
2138      else if (fixed instanceof Range)
2139        ok = checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern);
2140      else if (fixed instanceof Ratio)
2141        ok = checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern);
2142      else if (fixed instanceof SampledData)
2143        ok = checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern);
2144      else if (fixed instanceof Reference)
2145        ok = checkReference(errors, path, focus, (Reference) fixed, fixedSource, pattern);
2146      else
2147        ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, I18nConstants.INTERNAL_INT_BAD_TYPE, fixed.fhirType());
2148      List<Element> extensions = new ArrayList<Element>();
2149      focus.getNamedChildren("extension", extensions);
2150      if (fixed.getExtension().size() == 0) {
2151        ok = rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0 || pattern == true, I18nConstants.EXTENSION_EXT_FIXED_BANNED) && ok;
2152      } 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()))) {
2153        for (Extension e : fixed.getExtension()) {
2154          Element ex = getExtensionByUrl(extensions, e.getUrl());
2155          if (rule(errors, NO_RULE_DATE, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) {
2156            ok = checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"), false) && ok;
2157          }
2158        }
2159      }
2160    }
2161    return ok;
2162  }
2163
2164  private boolean checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) {
2165    boolean ok = true;
2166    ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
2167    ok = checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
2168    ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
2169
2170    List<Element> parts = new ArrayList<Element>();
2171    if (!pattern || fixed.hasFamily()) {
2172      focus.getNamedChildren("family", parts);
2173      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()))) {
2174        for (int i = 0; i < parts.size(); i++)
2175          ok = checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern) && ok;
2176      }
2177    }
2178    if (!pattern || fixed.hasGiven()) {
2179      focus.getNamedChildren("given", parts);
2180      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()))) {
2181        for (int i = 0; i < parts.size(); i++)
2182          ok = checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern) && ok;
2183      }
2184    }
2185    if (!pattern || fixed.hasPrefix()) {
2186      focus.getNamedChildren("prefix", parts);
2187      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()))) {
2188        for (int i = 0; i < parts.size(); i++)
2189          ok = checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern) && ok;
2190      }
2191    }
2192    if (!pattern || fixed.hasSuffix()) {
2193      focus.getNamedChildren("suffix", parts);
2194      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()))) {
2195        for (int i = 0; i < parts.size(); i++)
2196          ok = checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern) && ok;
2197      }
2198    }
2199    return ok;
2200  }
2201
2202  private boolean checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) {
2203    boolean ok = true;
2204    String system = element.getNamedChildValue("system");
2205    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;
2206    if ("urn:ietf:rfc:3986".equals(system)) {
2207      String value = element.getNamedChildValue("value");
2208      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) && ok; 
2209    }
2210    return ok;
2211  }
2212
2213  private boolean checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) {
2214    boolean ok = true;
2215    ok = checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern) && ok;
2216    ok = checkFixedValue(errors, path + ".type", focus.getNamedChild(TYPE), fixed.getType(), fixedSource, TYPE, focus, pattern) && ok;
2217    ok = checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern) && ok;
2218    ok = checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern) && ok;
2219    ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern) && ok;
2220    ok = checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern) && ok;
2221    return ok;
2222  }
2223
2224  private boolean checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) {
2225    boolean ok = true;
2226    ok = checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern) && ok;
2227    ok = checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern) && ok;
2228    return ok;
2229  }
2230
2231  private boolean checkPrimitive(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException {
2232    boolean ok = true;
2233    if (isBlank(e.primitiveValue())) {
2234      if (e.primitiveValue() == null)
2235        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT) && ok;
2236      else if (e.primitiveValue().length() == 0)
2237        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY) && ok;
2238      else if (StringUtils.isWhitespace(e.primitiveValue()))
2239        warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS);
2240      if (context.hasBinding()) {
2241        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;
2242      }
2243      return ok;
2244    } else {
2245      boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue());
2246      if (hasBiDiControls) {
2247        if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, e.line(), e.col(), path, !noUnicodeBiDiControlChars, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_DISALLOWED, UnicodeUtilities.replaceBiDiChars(e.primitiveValue()))) {
2248          String msg = UnicodeUtilities.checkUnicodeWellFormed(e.primitiveValue());
2249          warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_MATCH, msg);
2250        } else {
2251          ok = false;
2252        }
2253      }
2254    }
2255    String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
2256    // there's a messy history here - this extension snhould only be on the element definition itself, but for historical reasons 
2257    //( see task 13328) it might also be found on one the types
2258    if (regex != null) {
2259      for (TypeRefComponent tr : context.getType()) {
2260        if (tr.hasExtension(ToolingExtensions.EXT_REGEX)) {
2261          regex = tr.getExtensionString(ToolingExtensions.EXT_REGEX);
2262          break;
2263        }
2264      }      
2265    }
2266    if (regex != null) {
2267      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;
2268    }
2269    if (!"xhtml".equals(type)) {
2270      if (securityChecks) {
2271        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR) && ok;
2272      } else if (!"markdown".equals(type)){
2273        hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING);
2274      }
2275    }
2276    
2277    
2278    if (type.equals("boolean")) {
2279      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;
2280    }
2281    if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
2282      String url = e.primitiveValue();
2283      ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID) && ok;
2284      ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID) && ok;
2285      rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", ""))
2286        // work around an old invalid example in a core package
2287        || "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);
2288      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;
2289      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;
2290
2291      if (type.equals("oid")) {
2292        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;
2293      }
2294      if (type.equals("uuid")) {
2295        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;
2296      }
2297      if (type.equals("canonical")) {
2298        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;        
2299      }
2300
2301      if (url != null && url.startsWith("urn:uuid:")) {
2302        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(url.substring(9)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID) && ok;
2303      }
2304      if (url != null && url.startsWith("urn:oid:")) {
2305        String cc = url.substring(8);
2306        // OIDs shorter than 5 chars are almost never valid for namespaces, except for the special OIDs 1.3.88 and 1.3.160
2307        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(cc) && (cc.lastIndexOf('.') >= 5 ||
2308            Utilities.existsInList(cc, "1.3.160", "1.3.88")), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID, cc) && ok;
2309      }
2310
2311      if (isCanonicalURLElement(e)) {
2312        // we get to here if this is a defining canonical URL (e.g. CodeSystem.url)
2313        // the URL must be an IRI if present
2314        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url), 
2315            node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url) && ok;                  
2316      } 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
2317        ok = validateReference(hostContext, errors, path, type, context, e, url) && ok;
2318      }
2319    }
2320    if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) {
2321      // work around an old issue with ElementDefinition.id
2322      if (!context.getPath().equals("ElementDefinition.id")) {
2323        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;
2324      }
2325    }
2326    if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
2327      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)) {
2328        warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_WS, prepWSPresentation(e.primitiveValue()));
2329        if (rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) {
2330          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;
2331        } else {
2332          ok = false;
2333        }
2334      } else {
2335        ok = false;
2336      }
2337    }
2338    if (type.equals("dateTime")) {
2339      warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
2340      ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
2341        e.primitiveValue()
2342          .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()) && ok;
2343      ok = 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) && ok;
2344      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;
2345      try {
2346        DateTimeType dt = new DateTimeType(e.primitiveValue());
2347      } catch (Exception ex) {
2348        rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage());
2349        ok = false;
2350      }
2351    }
2352    if (type.equals("time")) {
2353      ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
2354        e.primitiveValue()
2355          .matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID) && ok;
2356      try {
2357        TimeType dt = new TimeType(e.primitiveValue());
2358      } catch (Exception ex) {
2359        rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage());
2360        ok = false;
2361      }
2362    }
2363    if (type.equals("date")) {
2364      warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
2365      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]))?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID) && ok;
2366      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;
2367      try {
2368        DateType dt = new DateType(e.primitiveValue());
2369      } catch (Exception ex) {
2370        rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage());
2371        ok = false;
2372      }
2373    }
2374    if (type.equals("base64Binary")) {
2375      String encoded = e.primitiveValue();
2376      if (isNotBlank(encoded)) {
2377        boolean bok = isValidBase64(encoded);
2378        if (!bok) {
2379          String value = encoded.length() < 100 ? encoded : "(snip)";
2380          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value) && ok;
2381        } else {
2382          boolean wsok = !base64HasWhitespace(encoded);
2383          if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) {
2384            ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR) && ok;            
2385          } else {
2386            warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING);            
2387          }
2388        }
2389        if (bok && context.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
2390          int size = countBase64DecodedBytes(encoded);
2391          long def = Long.parseLong(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_SIZE));
2392          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;
2393        }
2394
2395      }
2396    }
2397    if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
2398      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())) {
2399        Integer v = new Integer(e.getValue()).intValue();
2400        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;
2401        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;
2402        if (type.equals("unsignedInt"))
2403          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
2404        if (type.equals("positiveInt"))
2405          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
2406      }
2407    }
2408    if (type.equals("integer64")) {
2409      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())) {
2410        Long v = new Long(e.getValue()).longValue();
2411        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;
2412        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;
2413        if (type.equals("unsignedInt"))
2414          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
2415        if (type.equals("positiveInt"))
2416          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
2417      } else {
2418        ok = false;
2419      }
2420    }
2421    if (type.equals("decimal")) {
2422      if (e.primitiveValue() != null) {
2423        DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
2424        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())) {
2425          warning(errors, NO_RULE_DATE, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue());
2426          try {            
2427            Decimal v = new Decimal(e.getValue());
2428            if (context.hasMaxValueDecimalType() && context.getMaxValueDecimalType().hasValue()) {
2429              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;
2430            } else if (context.hasMaxValueIntegerType() && context.getMaxValueIntegerType().hasValue()) {
2431              // users can also provide a max integer type. It's not clear whether that's actually valid, but we'll check for it anyway
2432              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;
2433            }
2434            
2435            if (context.hasMinValueDecimalType() && context.getMaxValueDecimalType().hasValue()) {
2436              ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMaxValueDecimalType()) && ok;
2437            } else if (context.hasMinValueIntegerType() && context.getMaxValueIntegerType().hasValue()) {
2438              ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, checkDecimalMinValue(v, new BigDecimal(context.getMaxValueIntegerType().getValue())), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, context.getMaxValueIntegerType()) && ok;
2439            }
2440          } catch (Exception ex) {
2441            // should never happen?
2442          }
2443        } else {
2444          ok = false;
2445        }
2446      }
2447      if (context.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
2448        int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0;
2449        int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_MAX_DECIMALS));
2450        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;
2451      }
2452    }
2453    if (type.equals("instant")) {
2454      ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
2455        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()) && ok;
2456      warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
2457      try {
2458        InstantType dt = new InstantType(e.primitiveValue());
2459      } catch (Exception ex) {
2460        rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage());
2461        ok = false;
2462      }
2463    }
2464
2465    if (type.equals("code") && e.primitiveValue() != null) {
2466      // 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
2467      // other than single spaces in the contents
2468      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;
2469      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;
2470    }
2471
2472    if (context.hasBinding() && e.primitiveValue() != null) {
2473      // special cases
2474      if ("StructureDefinition.type".equals(context.getPath()) && "http://hl7.org/fhir/StructureDefinition/StructureDefinition".equals(profile.getUrl())) {
2475        ok = checkTypeValue(errors, path, e, node.getElement());
2476      } else {
2477        ok = checkPrimitiveBinding(hostContext, errors, path, type, context, e, profile, node) && ok;
2478      }
2479    }
2480
2481    if (type.equals("markdown") && htmlInMarkdownCheck != HtmlInMarkdownCheck.NONE) {
2482      String raw = e.primitiveValue();
2483      String processed = MarkDownProcessor.preProcess(raw);
2484      if (!raw.equals(processed)) {
2485        int i = 0;
2486        while (i < raw.length() && raw.charAt(1) == processed.charAt(i)) {
2487          i++;
2488        }
2489        if (i < raw.length()-1 ) {
2490          ok = 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;
2491        } else {
2492          ok = 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;
2493        }
2494      }
2495    }
2496    if (type.equals("xhtml")) {
2497      XhtmlNode xhtml = e.getXhtml();
2498      if (xhtml != null) { // if it is null, this is an error already noted in the parsers
2499        // check that the namespace is there and correct.
2500        String ns = xhtml.getNsDecl();
2501        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;
2502        // check that inner namespaces are all correct
2503        checkInnerNS(errors, e, path, xhtml.getChildNodes());
2504        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, ns) && ok;
2505        // check that no illegal elements and attributes have been used
2506        ok = checkInnerNames(errors, e, path, xhtml.getChildNodes(), false) && ok;
2507        ok = checkUrls(errors, e, path, xhtml.getChildNodes()) && ok;
2508      }
2509    }
2510
2511    if (context.hasFixed()) {
2512      ok = checkFixedValue(errors, path, e, context.getFixed(), profile.getVersionedUrl(), context.getSliceName(), null, false) && ok;
2513    }
2514    if (context.hasPattern()) {
2515      ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true) && ok;
2516    }
2517
2518    // for nothing to check
2519    return ok;
2520  }
2521
2522  private boolean checkTypeValue(List<ValidationMessage> errors, String path, Element e, Element sd) {
2523    String v = e.primitiveValue();
2524    if (v == null) {
2525      return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.SD_TYPE_MISSING);
2526    }
2527    String url = sd.getChildValue("url");
2528    String d = sd.getChildValue("derivation"); 
2529    String k = sd.getChildValue("kind"); 
2530    if (Utilities.isAbsoluteUrl(v)) {
2531      warning(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, ns(v).equals(ns(url)) || ns(v).equals(ns(url).replace("StructureDefinition/", "")), I18nConstants.SD_TYPE_NOT_MATCH_NS, v, url);
2532      return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, "logical".equals(k), I18nConstants.SD_TYPE_NOT_LOGICAL, v, k);
2533    } else {
2534      boolean tok = false;
2535      for (StructureDefinition t : context.fetchResourcesByType(StructureDefinition.class)) {
2536        if (t.hasSourcePackage() && t.getSourcePackage().getId().startsWith("hl7.fhir.r") && v.equals(t.getType())) {
2537          tok = true;
2538        }
2539      }
2540      if (tok) {
2541        if (!(("http://hl7.org/fhir/StructureDefinition/"+v).equals(url))) {
2542          return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, "constraint".equals(d), I18nConstants.SD_TYPE_NOT_DERIVED, v);
2543        } else {
2544          return true;
2545        }
2546      } else {
2547        return rule(errors, "2022-11-02", IssueType.INVALID, e.line(), e.col(), path, tok, I18nConstants.SD_TYPE_NOT_LOCAL, v);
2548      }
2549    }
2550  }
2551
2552  private String ns(String url) {
2553    return url.contains("/") ? url.substring(0, url.lastIndexOf("/")) : url;
2554  }
2555
2556  private Object prepWSPresentation(String s) {
2557    if (Utilities.noString(s)) {
2558      return "";
2559    }
2560    if (!StringUtils.containsWhitespace(s.trim())) {
2561      return s;
2562    }
2563    int b = 0;
2564    while (Character.isWhitespace(s.charAt(b))) {
2565      b++;
2566    }
2567    while (!Character.isWhitespace(s.charAt(b))) {
2568      b++;
2569    }
2570    int e = s.length() - 1;
2571    while (Character.isWhitespace(s.charAt(e))) {
2572      e--;
2573    }
2574    while (!Character.isWhitespace(s.charAt(e))) {
2575      e--;
2576    }
2577    return s.substring(0, b)+"..."+s.substring(e+1);
2578  }
2579
2580  public boolean validateReference(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, String url) {
2581    boolean ok = true;
2582    // now, do we check the URI target?
2583    if (fetcher != null && !type.equals("uuid")) {
2584      boolean found;
2585      try {
2586        found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || 
2587            SpecialExtensions.isKnownExtension(url) || isXverUrl(url);
2588        if (!found) {
2589          found = fetcher.resolveURL(this, hostContext, path, url, type);
2590        }
2591      } catch (IOException e1) {
2592        found = false;
2593      }
2594      if (!found) {
2595        if (type.equals("canonical")) {
2596          ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url);
2597          if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) {
2598            ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url) && ok;
2599          } else {
2600            hint(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url);
2601          }
2602        } else {
2603          if (url.contains("hl7.org") || url.contains("fhir.org")) {
2604            ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url) && ok;;
2605          } else if (url.contains("example.org") || url.contains("acme.com")) {
2606            ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url) && ok;;
2607          } else {
2608            warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url);
2609          }
2610        }
2611      } else {
2612        if (type.equals("canonical")) {
2613          ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url);
2614          if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) {
2615            try {
2616              Resource r = null;
2617              if (url.startsWith("#")) {
2618                r = loadContainedResource(errors, path, hostContext.getRootResource(), url.substring(1), Resource.class);
2619              }
2620              if (r == null) {
2621               r = fetcher.fetchCanonicalResource(this, url);
2622              }
2623              if (r == null) {
2624                r = this.context.fetchResource(Resource.class, url);
2625              }
2626              if (r == null) {
2627                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);                    
2628              } 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))) {
2629                if (rp == ReferenceValidationPolicy.CHECK_VALID) {
2630                  // todo....
2631                }
2632              }
2633            } catch (Exception ex) {
2634              // won't happen 
2635            }
2636          }
2637        }            
2638      }
2639    }
2640    return ok;
2641  }
2642
2643  private Set<String> listExpectedCanonicalTypes(ElementDefinition context) {
2644    Set<String> res = new HashSet<>();
2645    TypeRefComponent tr = context.getType("canonical");
2646    if (tr != null) {
2647      for (CanonicalType p : tr.getTargetProfile()) {
2648        String url = p.getValue();
2649        StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, url);
2650        if (sd != null) {
2651          res.add(sd.getType());
2652        } else {
2653          if (url != null && url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2654            res.add(url.substring("http://hl7.org/fhir/StructureDefinition/".length()));
2655          }
2656        }
2657      }
2658    }
2659    return res;
2660  }
2661
2662  private boolean isCorrectCanonicalType(Resource r, ElementDefinition context) {
2663    TypeRefComponent tr = context.getType("canonical");
2664    if (tr != null) {
2665      for (CanonicalType p : tr.getTargetProfile()) {
2666        if (isCorrectCanonicalType(r, p)) {
2667          return true;
2668        }
2669      }
2670      if (tr.getTargetProfile().isEmpty()) {
2671        return true;
2672      }
2673    }
2674    return false;
2675  }
2676
2677  private boolean isCorrectCanonicalType(Resource r, CanonicalType p) {
2678    String url = p.getValue();
2679    String t = null;
2680    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
2681    if (sd != null) {
2682      t = sd.getType();
2683    } else if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2684      t = url.substring("http://hl7.org/fhir/StructureDefinition/".length());
2685    } else {
2686      return false;
2687    }
2688    return Utilities.existsInList(t, "Resource", "CanonicalResource") || t.equals(r.fhirType());
2689  }
2690
2691  private boolean isCanonicalURLElement(Element e) {
2692    if (e.getProperty() == null || e.getProperty().getDefinition() == null) {
2693      return false;
2694    }
2695    String path = e.getProperty().getDefinition().getBase().getPath();
2696    if (path == null) {
2697      return false;
2698    }
2699    String[] p = path.split("\\."); 
2700    if (p.length != 2) {
2701      return false;
2702    }
2703    if (!"url".equals(p[1])) {
2704      return false;
2705    }
2706    return Utilities.existsInList(p[0], VersionUtilities.getCanonicalResourceNames(context.getVersion()));
2707  }
2708
2709  private boolean containsHtmlTags(String cnt) {
2710    int i = cnt.indexOf("<");
2711    while (i > -1) {
2712      cnt = cnt.substring(i+1);
2713      i = cnt.indexOf("<");
2714      int e = cnt.indexOf(">");
2715      if (e > -1 && e < i) {
2716        String s = cnt.substring(0, e);
2717        if (s.matches(HTML_FRAGMENT_REGEX)) {
2718          return true;
2719        }
2720      }
2721    }
2722    return false;
2723  }
2724
2725  /**
2726   * Technically this is not bulletproof as some invalid base64 won't be caught,
2727   * but I think it's good enough. The original code used Java8 Base64 decoder
2728   * but I've replaced it with a regex for 2 reasons:
2729   * 1. This code will run on any version of Java
2730   * 2. This code doesn't actually decode, which is much easier on memory use for big payloads
2731   */
2732  private boolean isValidBase64(String theEncoded) {
2733    if (theEncoded == null) {
2734      return false;
2735    }
2736    int charCount = 0;
2737    boolean ok = true;
2738    for (int i = 0; i < theEncoded.length(); i++) {
2739      char nextChar = theEncoded.charAt(i);
2740      if (Character.isWhitespace(nextChar)) {
2741        continue;
2742      }
2743      if (Character.isLetterOrDigit(nextChar)) {
2744        charCount++;
2745      }
2746      if (nextChar == '/' || nextChar == '=' || nextChar == '+') {
2747        charCount++;
2748      }
2749    }
2750
2751    if (charCount > 0 && charCount % 4 != 0) {
2752      ok = false;
2753    }
2754    return ok;
2755  }
2756
2757  private boolean base64HasWhitespace(String theEncoded) {
2758    if (theEncoded == null) {
2759      return false;
2760    }
2761    for (int i = 0; i < theEncoded.length(); i++) {
2762      char nextChar = theEncoded.charAt(i);
2763      if (Character.isWhitespace(nextChar)) {
2764        return true;
2765      }
2766    }
2767    return false;
2768
2769  }
2770
2771
2772  private int countBase64DecodedBytes(String theEncoded) {
2773    Base64InputStream inputStream = new Base64InputStream(new ByteArrayInputStream(theEncoded.getBytes(StandardCharsets.UTF_8)));
2774    try {
2775      try {
2776        for (int counter = 0; ; counter++) {
2777          if (inputStream.read() == -1) {
2778            return counter;
2779          }
2780        }
2781      } finally {
2782          inputStream.close();
2783      }
2784    } catch (IOException e) {
2785      throw new IllegalStateException(e); // should not happen
2786    }
2787  }
2788
2789  private boolean isDefinitionURL(String url) {
2790    return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer",
2791      "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");
2792  }
2793
2794  private boolean checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list, boolean inPara) {
2795    boolean ok = true;
2796    for (XhtmlNode node : list) {
2797      if (node.getNodeType() == NodeType.Comment) {
2798        rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL);
2799      }
2800      if (node.getNodeType() == NodeType.Element) {
2801        rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(),
2802          "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
2803          "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
2804          "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
2805          "code", "samp", "img", "map", "area"), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName());
2806        
2807        for (String an : node.getAttributes().keySet()) {
2808          boolean bok = an.startsWith("xmlns") || Utilities.existsInList(an,
2809            "title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex",
2810            // tables
2811            "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
2812
2813            Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
2814              "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
2815              "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
2816              "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
2817              "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
2818            );          
2819          if (!bok) {
2820            rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName());
2821          }
2822        }
2823        
2824        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !(inPara && Utilities.existsInList(node.getName(), "div",  "blockquote", "table", "ol", "ul", "p")) , I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA, node.getName()) && ok;
2825        
2826        ok = checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName())) && ok;
2827      }
2828    }
2829    return ok;
2830  }
2831
2832  private boolean checkUrls(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
2833    boolean ok = true;
2834    for (XhtmlNode node : list) {
2835      if (node.getNodeType() == NodeType.Element) {
2836        if ("a".equals(node.getName())) {
2837          String msg = checkValidUrl(node.getAttribute("href"));
2838          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("href"), msg) && ok;
2839        } else if ("img".equals(node.getName())) {
2840          String msg = checkValidUrl(node.getAttribute("src"));
2841          ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("src"), msg) && ok;
2842        }
2843        ok = checkUrls(errors, e, path, node.getChildNodes()) && ok;
2844      }
2845    }
2846    return ok;
2847  }
2848
2849  private String checkValidUrl(String value) {
2850    if (value == null) {
2851      return null;
2852    }
2853    if (Utilities.noString(value)) {
2854      return context.formatMessage(I18nConstants.XHTML_URL_EMPTY);
2855    }
2856
2857    if (value.startsWith("data:")) {
2858      String[] p = value.substring(5).split("\\,");
2859      if (p.length < 2) {
2860        return context.formatMessage(I18nConstants.XHTML_URL_DATA_NO_DATA, value);        
2861      } else if (p.length > 2) {
2862        return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID_COMMA, value);                
2863      } else if (!p[0].endsWith(";base64") || !isValidBase64(p[1])) {
2864        return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID, value);                        
2865      } else {
2866        if (p[0].startsWith(" ")) {
2867          p[0] = p[0].trim(); 
2868        }
2869        String mMsg = checkValidMimeType(p[0].substring(0, p[0].lastIndexOf(";")));
2870        if (mMsg != null) {
2871          return context.formatMessage(I18nConstants.XHTML_URL_DATA_MIMETYPE, value, mMsg);                  
2872        }
2873      }
2874      return null;
2875    } else {
2876      Set<Character> invalidChars = new HashSet<>();
2877      int c = 0;
2878      for (char ch : value.toCharArray()) {
2879        if (!(Character.isDigit(ch) || Character.isAlphabetic(ch) || Utilities.existsInList(ch, ';', '?', ':', '@', '&', '=', '+', '$', '.', ',', '/', '%', '-', '_', '~', '#', '[', ']', '!', '\'', '(', ')', '*', '|' ))) {
2880          c++;
2881          invalidChars.add(ch);
2882        }
2883      }
2884      if (invalidChars.isEmpty()) {
2885        return null;
2886      } else {
2887        return context.formatMessagePlural(c, I18nConstants.XHTML_URL_INVALID_CHARS, invalidChars.toString());
2888      }
2889    }
2890  }
2891
2892  private String checkValidMimeType(String mt) {
2893    if (!mt.matches("^(\\w+|\\*)\\/(\\w+|\\*)((;\\s*(\\w+)=\\s*(\\S+))?)$")) {
2894      return "Mime type invalid";
2895    }
2896    return null;
2897  }
2898
2899  private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
2900    for (XhtmlNode node : list) {
2901      if (node.getNodeType() == NodeType.Element) {
2902        String ns = node.getNsDecl();
2903        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);
2904        checkInnerNS(errors, e, path, node.getChildNodes());
2905      }
2906    }
2907  }
2908
2909  private boolean checkPrimitiveBinding(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) {
2910    // We ignore bindings that aren't on string, uri or code
2911    if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) {
2912      return true;
2913    }
2914    if (noTerminologyChecks)
2915      return true;
2916    
2917    boolean ok = true;
2918    String value = element.primitiveValue();
2919    // System.out.println("check "+value+" in "+path);
2920
2921    // firstly, resolve the value set
2922    ElementDefinitionBindingComponent binding = elementContext.getBinding();
2923    if (binding.hasValueSet()) {
2924      ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl(), profile);
2925      if (vs == null) { 
2926        CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
2927        if (rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
2928          warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
2929        } else {
2930          ok = false;
2931        }
2932      } else {
2933        CodedContentValidationPolicy validationPolicy = getPolicyAdvisor() == null ?
2934            CodedContentValidationPolicy.VALUESET : getPolicyAdvisor().policyForCodedContent(this, hostContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, vs, new ArrayList<>());
2935
2936        if (validationPolicy != CodedContentValidationPolicy.IGNORE) {
2937          long t = System.nanoTime();
2938          ValidationResult vr = null;
2939          if (binding.getStrength() != BindingStrength.EXAMPLE) {
2940            ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem();
2941            if (validationPolicy == CodedContentValidationPolicy.CODE) {
2942              options = options.noCheckValueSetMembership();              
2943            }
2944            vr = checkCodeOnServer(stack, vs, value, options);
2945          }
2946          timeTracker.tx(t, "vc "+value+"");
2947          if (binding.getStrength() == BindingStrength.REQUIRED) {
2948            removeTrackedMessagesForLocation(errors, element, path);
2949          }
2950          if (vr != null && !vr.isOk()) {
2951            if (vr.IsNoService())
2952              txHint(errors, NO_RULE_DATE, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value);
2953            else if (binding.getStrength() == BindingStrength.REQUIRED)
2954              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), getErrorMessage(vr.getMessage())) && ok;
2955            else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
2956              if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET))
2957                ok = checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), value, stack) && ok;
2958              else if (!noExtensibleWarnings && !isOkExtension(value, vs))
2959                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), getErrorMessage(vr.getMessage()));
2960            } else if (binding.getStrength() == BindingStrength.PREFERRED) {
2961              if (baseOnly) {
2962                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), getErrorMessage(vr.getMessage()));
2963              }
2964            }
2965          }
2966        }
2967      }
2968    } else if (!noBindingMsgSuppressed) {
2969      hint(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2);
2970    }
2971    return ok;
2972  }
2973
2974  private boolean isOkExtension(String value, ValueSet vs) {
2975    if ("http://hl7.org/fhir/ValueSet/defined-types".equals(vs.getUrl())) {
2976      return value.startsWith("http://hl7.org/fhirpath/System.");
2977    }
2978    return false;
2979  }
2980
2981  private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) {
2982    checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
2983    checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern);
2984    checkFixedValue(errors, path + ".unit", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "unit", focus, pattern);
2985    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
2986    checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
2987  }
2988
2989  private boolean checkQuantity(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) {
2990    boolean ok = true;
2991    String value = element.hasChild("value") ? element.getNamedChild("value").getValue() : null;
2992    String unit = element.hasChild("unit") ? element.getNamedChild("unit").getValue() : null;
2993    String system = element.hasChild("system") ? element.getNamedChild("system").getValue() : null;
2994    String code = element.hasChild("code") ? element.getNamedChild("code").getValue() : null;
2995
2996    // todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet)
2997    // todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation
2998
2999    if (!Utilities.noString(value) && definition.hasExtension(ToolingExtensions.EXT_MAX_DECIMALS)) {
3000      int dp = value.contains(".") ? value.substring(value.indexOf(".")+1).length() : 0;
3001      int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_MAX_DECIMALS));
3002      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;
3003    }
3004
3005    if (system != null || code != null ) {
3006      ok = checkCodedElement(errors, path, element, theProfile, definition, false, false, theStack, code, system, null, unit) && ok;
3007    }
3008
3009    if (code != null && "http://unitsofmeasure.org".equals(system)) {
3010      int b = code.indexOf("{");
3011      int e = code.indexOf("}");
3012      if (b >= 0 && e > 0 && b < e) {
3013        ok = bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1)) && ok;
3014      }
3015    }
3016
3017    if (definition.hasMinValue()) {
3018      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)) {
3019        if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_QTY, definition.getMinValue().fhirType())) {
3020          Quantity min = definition.getMinValueQuantity();
3021          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) &&
3022              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) && 
3023              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()) &&
3024              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) &&
3025              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)) {
3026            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)) {
3027              if (code.equals(min.getCode())) {
3028                // straight value comparison
3029                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;
3030              } else if ("http://unitsofmeasure.org".equals(system)) {
3031                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)) {
3032                  Decimal v = convertUcumValue(value, code, min.getCode());
3033                  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())) {
3034                    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;
3035                  } else {
3036                    ok = false;
3037                  }
3038                }
3039              } else {
3040                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());
3041              }
3042            } else {
3043              ok = false;
3044            }
3045          }
3046        } else {
3047          ok = false;
3048        }
3049      }
3050    }
3051    
3052    if (definition.hasMaxValue()) {
3053      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)) {
3054        if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_QTY, definition.getMaxValue().fhirType())) {
3055          Quantity max = definition.getMaxValueQuantity();
3056          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) &&
3057              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) && 
3058              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()) &&
3059              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) &&
3060              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)) {
3061            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)) {
3062              if (code.equals(max.getCode())) {
3063                // straight value comparison
3064                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;
3065              } else if ("http://unitsofmeasure.org".equals(system)) {
3066                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)) {
3067                  Decimal v = convertUcumValue(value, code, max.getCode());
3068                  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())) {
3069                    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;
3070                  } else {
3071                    ok = false;
3072                  }
3073                }
3074              } else {
3075                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());
3076              }
3077            } else {
3078              ok = false;
3079            }
3080          }
3081        } else {
3082          ok = false;
3083        }
3084      }
3085    }
3086    return ok;
3087  }
3088  
3089  private Decimal convertUcumValue(String value, String code, String minCode) {
3090    try {
3091      Decimal v = new Decimal(value);
3092      return context.getUcumService().convert(v, code, minCode);
3093    } catch (Exception e) {
3094      return null;
3095    }
3096  }
3097
3098  private boolean checkDecimalMaxValue(Decimal value, BigDecimal min) {
3099    try {
3100      Decimal m = new Decimal(min.toString());
3101      return value.comparesTo(m) <= 0;
3102    } catch (Exception e) {
3103      return false; // this will be another error somewhere else?
3104    }
3105  }
3106
3107  private boolean checkDecimalMaxValue(String value, BigDecimal min) {
3108    try {
3109      BigDecimal v = new BigDecimal(value);
3110      return v.compareTo(min) <= 0;      
3111    } catch (Exception e) {
3112      return false; // this will be another error somewhere else
3113    }
3114  }
3115
3116  private boolean checkDecimalMinValue(Decimal value, BigDecimal min) {
3117    try {
3118      Decimal m = new Decimal(min.toString());
3119      return value.comparesTo(m) >= 0;
3120    } catch (Exception e) {
3121      return false; // this will be another error somewhere else?
3122    }
3123  }
3124
3125  private boolean checkDecimalMinValue(String value, BigDecimal min) {
3126    try {
3127      BigDecimal v = new BigDecimal(value);
3128      return v.compareTo(min) >= 0;      
3129    } catch (Exception e) {
3130      return false; // this will be another error somewhere else
3131    }
3132  }
3133
3134  private boolean checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {
3135    boolean ok = true;
3136    long size = -1;
3137    // first check size
3138    String fetchError = null;
3139    if (element.hasChild("data")) {
3140      String b64 = element.getChildValue("data");
3141      // Note: If the value isn't valid, we're not adding an error here, as the test to the
3142      // child Base64Binary will catch it and we don't want to log it twice
3143      boolean bok = isValidBase64(b64);
3144      if (bok && element.hasChild("size")) {
3145        size = countBase64DecodedBytes(b64);
3146        String sz = element.getChildValue("size");
3147        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;
3148      }
3149    } else if (element.hasChild("size")) {
3150      String sz = element.getChildValue("size");
3151      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)) {
3152        size = Long.parseLong(sz);
3153        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;
3154      }
3155    } else if (element.hasChild("url")) {
3156      String url = element.getChildValue("url"); 
3157      if (definition.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
3158        try {
3159          if (url.startsWith("http://") || url.startsWith("https://")) {
3160            if (fetcher == null) {
3161              fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER, url);  
3162            } else {
3163              byte[] cnt = fetcher.fetchRaw(this, url);
3164              size = cnt.length;
3165            }
3166          } else if (url.startsWith("file:")) {
3167            size = new File(url.substring(5)).length();
3168          } else {
3169            fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url);          }
3170        } catch (Exception e) {
3171          if (STACK_TRACE) e.printStackTrace();
3172          fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR, url, e.getMessage());
3173        }
3174      }
3175    }
3176    if (definition.hasExtension(ToolingExtensions.EXT_MAX_SIZE)) {
3177      if (warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, fetchError)) {
3178        long def = Long.parseLong(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_MAX_SIZE));
3179        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;
3180      }
3181    }
3182    warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")), 
3183          I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT);
3184    return ok;
3185  }
3186
3187  // implementation
3188
3189  private boolean checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern) {
3190    boolean ok = true;
3191    ok = checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern) && ok;
3192    ok = checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern) && ok;
3193    return ok;
3194  }
3195
3196  private boolean checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) {
3197    boolean ok = true;
3198    ok = checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern) && ok;
3199    ok = checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern) && ok;
3200    return ok;
3201  }
3202
3203  private boolean checkReference(ValidatorHostContext hostContext,
3204                              List<ValidationMessage> errors,
3205                              String path,
3206                              Element element,
3207                              StructureDefinition profile,
3208                              ElementDefinition container,
3209                              String parentType,
3210                              NodeStack stack, PercentageTracker pct, ValidationMode vmode) throws FHIRException {
3211    boolean ok = true;
3212    Reference reference = ObjectConverter.readAsReference(element);
3213
3214    String ref = reference.getReference();
3215    if (Utilities.noString(ref)) {
3216      if (!path.contains("element.pattern")) { // this business rule doesn't apply to patterns
3217        if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) {
3218          warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path,
3219            !Utilities.noString(element.getNamedChildValue("display")), I18nConstants.REFERENCE_REF_NODISPLAY);
3220        }
3221      }
3222      return true;
3223    } else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) {
3224      // special known URLs that can't be validated but are known to be valid
3225      return true;
3226    }
3227    warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref);      
3228
3229    ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element);
3230    String refType;
3231    if (ref.startsWith("#")) {
3232      refType = "contained";
3233    } else {
3234      if (we == null) {
3235        refType = "remote";
3236      } else {
3237        refType = "bundled";
3238      }
3239    }
3240    ReferenceValidationPolicy pol;
3241    if (refType.equals("contained") || refType.equals("bundled")) {
3242      pol = ReferenceValidationPolicy.CHECK_VALID;
3243    } else {
3244      if (policyAdvisor == null) {
3245        pol = ReferenceValidationPolicy.IGNORE;
3246      } else {
3247        pol = policyAdvisor.policyForReference(this, hostContext.getAppContext(), path, ref);
3248      }
3249    }
3250
3251    if (pol.checkExists()) {
3252      if (we == null) {
3253        if (!refType.equals("contained")) {
3254          if (fetcher == null) {
3255            throw new FHIRException(context.formatMessage(I18nConstants.RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED));
3256          } else {
3257            Element ext = null;
3258            if (fetchCache.containsKey(ref)) {
3259              ext = fetchCache.get(ref);
3260            } else {
3261              try {
3262                ext = fetcher.fetch(this, hostContext.getAppContext(), ref);
3263              } catch (IOException e) {
3264                if (STACK_TRACE) e.printStackTrace();
3265                throw new FHIRException(e);
3266              }
3267              if (ext != null) {
3268                setParents(ext);
3269                fetchCache.put(ref, ext);
3270              }
3271            }
3272            we = ext == null ? null : makeExternalRef(ext, path);
3273          }
3274        }
3275      }
3276      boolean rok = (allowExamples && (ref.contains("example.org") || ref.contains("acme.com")))
3277        || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS);
3278      ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, rok, I18nConstants.REFERENCE_REF_CANTRESOLVE, ref) && ok;
3279    }
3280
3281    String ft;
3282    if (we != null) {
3283      ft = we.getType();
3284    } else {
3285      ft = tryParse(ref);
3286    }
3287
3288    if (reference.hasType()) { // R4 onwards...
3289      // the type has to match the specified
3290      String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType();
3291      TypeRefComponent containerType = container.getType("Reference");
3292      if (!containerType.hasTargetProfile(tu)
3293        && !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")
3294        && !containerType.getTargetProfile().isEmpty()
3295      ) {
3296        boolean matchingResource = false;
3297        for (CanonicalType target : containerType.getTargetProfile()) {
3298          StructureDefinition sd = resolveProfile(profile, target.asStringValue());
3299          if (rule(errors, NO_RULE_DATE, IssueType.NOTFOUND, element.line(), element.col(), path, sd != null,
3300              I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, target.asStringValue())) {
3301            if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) {
3302              matchingResource = true;
3303              break;
3304            }
3305          } else {
3306            ok = false;
3307          }
3308        }
3309        ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource,
3310          I18nConstants.REFERENCE_REF_WRONGTARGET, reference.getType(), container.getType("Reference").getTargetProfile()) && ok;
3311
3312      }
3313      // the type has to match the actual
3314      ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path,
3315        ft == null || ft.equals(reference.getType()), I18nConstants.REFERENCE_REF_BADTARGETTYPE, reference.getType(), ft) && ok;
3316    }
3317
3318    if (we != null && pol.checkType()) {
3319      if (warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null,
3320        I18nConstants.REFERENCE_REF_NOTYPE)) {
3321        // we validate as much as we can. First, can we infer a type from the profile?
3322        boolean rok = false;
3323        TypeRefComponent type = getReferenceTypeRef(container.getType());
3324        if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
3325          Set<String> types = new HashSet<>();
3326          List<StructureDefinition> profiles = new ArrayList<>();
3327          for (UriType u : type.getTargetProfile()) {
3328            StructureDefinition sd = resolveProfile(profile, u.getValue());
3329            if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null,
3330              I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, u.getValue())) {
3331              types.add(sd.getType());
3332              if (ft.equals(sd.getType())) {
3333                rok = true;
3334                profiles.add(sd);
3335              }
3336            } else {
3337              ok = false;
3338            }
3339          }
3340          if (!pol.checkValid()) {
3341            ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0,
3342              I18nConstants.REFERENCE_REF_CANTMATCHTYPE, ref, StringUtils.join("; ", type.getTargetProfile())) && ok;
3343          } else {
3344            Map<StructureDefinition, List<ValidationMessage>> badProfiles = new HashMap<>();
3345            Map<StructureDefinition, List<ValidationMessage>> goodProfiles = new HashMap<>();
3346            int goodCount = 0;
3347            for (StructureDefinition pr : profiles) {
3348              List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
3349              validateResource(we.hostContext(hostContext, pr), profileErrors, we.getResource(), we.getFocus(), pr,
3350                IdStatus.OPTIONAL, we.getStack().resetIds(), pct, vmode.withReason(ValidationReason.MatchingSlice)); 
3351              if (!hasErrors(profileErrors)) {
3352                goodCount++;
3353                goodProfiles.put(pr, profileErrors);
3354                trackUsage(pr, hostContext, element);
3355              } else {
3356                badProfiles.put(pr, profileErrors);
3357              }
3358            }
3359            if (goodCount == 1) {
3360              if (showMessagesFromReferences) {
3361                for (ValidationMessage vm : goodProfiles.values().iterator().next()) {
3362                  if (!errors.contains(vm)) {
3363                    errors.add(vm);
3364                    ok = false;
3365                  }
3366                }
3367              }
3368
3369            } else if (goodProfiles.size() == 0) {
3370              if (!isShowMessagesFromReferences()) {
3371                ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles),
3372                  I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())) && ok;
3373                for (StructureDefinition sd : badProfiles.keySet()) {
3374                  slicingHint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false, false, 
3375                    context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getVersionedUrl()), 
3376                    errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
3377                }
3378              } else {
3379                ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1,
3380                  I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())) && ok;
3381                for (List<ValidationMessage> messages : badProfiles.values()) {
3382                  for (ValidationMessage vm : messages) {
3383                    if (!errors.contains(vm)) {
3384                      errors.add(vm);
3385                      ok = false;
3386                    }
3387                  }
3388                }
3389              }
3390            } else {
3391              if (!isShowMessagesFromReferences()) {
3392                warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false,
3393                  I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
3394                for (StructureDefinition sd : badProfiles.keySet()) {
3395                  slicingHint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false,
3396                    false,  context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getVersionedUrl()),
3397                      errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
3398                }
3399              } else {
3400                warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false,
3401                  I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
3402                for (List<ValidationMessage> messages : goodProfiles.values()) {
3403                  for (ValidationMessage vm : messages) {
3404                    if (!errors.contains(vm)) {
3405                      errors.add(vm);
3406                      ok = false;
3407                    }
3408                  }
3409                }
3410              }
3411            }
3412          }
3413          ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, rok,
3414            I18nConstants.REFERENCE_REF_BADTARGETTYPE, ft, types.toString()) && ok;
3415        }
3416        if (type.hasAggregation() && !noCheckAggregation) {
3417          boolean modeOk = false;
3418          CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3419          for (Enumeration<AggregationMode> mode : type.getAggregation()) {
3420            b.append(mode.getCode());
3421            if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
3422              modeOk = true;
3423            else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
3424              modeOk = true;
3425            else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote")))
3426              modeOk = true;
3427          }
3428          ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk,
3429            I18nConstants.REFERENCE_REF_AGGREGATION, refType, b.toString()) && ok;
3430        }
3431      }
3432    }
3433    if (we == null) {
3434      TypeRefComponent type = getReferenceTypeRef(container.getType());
3435      boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED);
3436      ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, okToRef, I18nConstants.REFERENCE_REF_NOTFOUND_BUNDLE, ref) && ok;
3437    }
3438    if (we == null && ft != null && assumeValidRestReferences) {
3439      // if we == null, we inferred ft from the reference. if we are told to treat this as gospel
3440      TypeRefComponent type = getReferenceTypeRef(container.getType());
3441      Set<String> types = new HashSet<>();
3442      StructureDefinition sdFT = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ft);
3443      boolean rok = false;
3444      for (CanonicalType tp : type.getTargetProfile()) {
3445        StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue(), profile);
3446        if (sd != null) {
3447          types.add(sd.getType());
3448        }
3449        StructureDefinition sdF = sdFT;
3450        while (sdF != null) {
3451          if (sdF.getType().equals(sd.getType())) {
3452            rok = true;
3453            break;
3454          }
3455          sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition(), sdF) : null;
3456        }
3457      }
3458      ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || rok,
3459        I18nConstants.REFERENCE_REF_BADTARGETTYPE2, ft, ref, types) && ok;
3460
3461    }
3462    if (pol == ReferenceValidationPolicy.CHECK_VALID) {
3463      // todo....
3464    }
3465    
3466    // todo: if the content is a resource, check that Reference.type is describing a resource
3467    return ok;
3468  }
3469
3470  private boolean isSuspiciousReference(String url) {
3471    if (!assumeValidRestReferences || url == null || Utilities.isAbsoluteUrl(url) || url.startsWith("#")) {
3472      return false;
3473    }
3474    String[] parts = url.split("\\/");
3475    if (parts.length == 2 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1])) {
3476      return false;
3477    }
3478    if (parts.length == 4 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1]) && "_history".equals(parts[2]) && Utilities.isValidId(parts[3])) {
3479      return false;
3480    }
3481    return true;
3482  }
3483
3484  private String asListByUrl(Collection<StructureDefinition> coll) {
3485    List<StructureDefinition> list = new ArrayList<>();
3486    list.addAll(coll);
3487    Collections.sort(list, new StructureDefinitionSorterByUrl());
3488    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3489    for (StructureDefinition sd : list) {
3490      b.append(sd.getUrl());
3491    }
3492    return b.toString();
3493  }
3494
3495  private String asList(Collection<CanonicalType> coll) {
3496    List<CanonicalType> list = new ArrayList<>();
3497    list.addAll(coll);
3498    Collections.sort(list, new CanonicalTypeSorter());
3499    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3500    for (CanonicalType c : list) {
3501      b.append(c.getValue());
3502    }
3503    return b.toString();
3504  }
3505
3506  private boolean areAllBaseProfiles(List<StructureDefinition> profiles) {
3507    for (StructureDefinition sd : profiles) {
3508      if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
3509        return false;
3510      }
3511    }
3512    return true;
3513  }
3514
3515  private String errorSummaryForSlicing(List<ValidationMessage> list) {
3516    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3517    for (ValidationMessage vm : list) {
3518      if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
3519        b.append(vm.getLocation() + ": " + vm.getMessage());
3520      }
3521    }
3522    return b.toString();
3523  }
3524
3525  private String errorSummaryForSlicingAsHtml(List<ValidationMessage> list) {
3526    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3527    for (ValidationMessage vm : list) {
3528      if (vm.isSlicingHint()) {
3529        b.append("<li>" + vm.getLocation() + ": " + vm.getSliceHtml() + "</li>");
3530      } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
3531        b.append("<li>" + vm.getLocation() + ": " + vm.getHtml() + "</li>");
3532      }
3533    }
3534    return "<ul>" + b.toString() + "</ul>";
3535  }
3536
3537  private boolean isCritical(List<ValidationMessage> list) {
3538    for (ValidationMessage vm : list) {
3539      if (vm.isSlicingHint() && vm.isCriticalSignpost()) {
3540        return true;
3541      }
3542    }
3543    return false;
3544  }
3545  
3546  private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) {
3547    List<String> res = new ArrayList<String>();
3548    for (ValidationMessage vm : list) {
3549      if (vm.isSlicingHint()) {
3550        if (vm.sliceText != null) {
3551          for (String s : vm.sliceText) {
3552            res.add(vm.getLocation() + ": " + s);
3553          }
3554        } else {
3555          res.add(vm.getLocation() + ": " + vm.getMessage());
3556        }
3557      } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
3558        res.add(vm.getLocation() + ": " + vm.getHtml());
3559      }
3560    }
3561    return res.toArray(new String[0]);
3562  }
3563
3564  private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) {
3565    for (TypeRefComponent tr : types) {
3566      if ("Reference".equals(tr.getCode())) {
3567        return tr;
3568      }
3569    }
3570    return null;
3571  }
3572
3573  private String checkResourceType(String type) {
3574    long t = System.nanoTime();
3575    try {
3576      if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
3577        return type;
3578      else
3579        return null;
3580    } finally {
3581      timeTracker.sd(t);
3582    }
3583  }
3584
3585  private boolean checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern) {
3586    boolean ok = true;
3587    ok = checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), fixedSource, "origin", focus, pattern) && ok;
3588    if (VersionUtilities.isR5VerOrLater(context.getVersion())) {
3589      ok = checkFixedValue(errors, path + ".interval", focus.getNamedChild("period"), fixed.getIntervalElement(), fixedSource, "interval", focus, pattern) && ok;
3590      ok = checkFixedValue(errors, path + ".intervalUnit", focus.getNamedChild("period"), fixed.getIntervalUnitElement(), fixedSource, "intervalUnit", focus, pattern) && ok;
3591    } else {
3592      ok = checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getIntervalElement(), fixedSource, "period", focus, pattern) && ok;
3593    }
3594    ok = checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), fixedSource, "factor", focus, pattern) && ok;
3595    ok = checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern) && ok;
3596    ok = checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern) && ok;
3597    ok = checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern) && ok;
3598    ok = checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern) && ok;
3599    return ok;
3600  }
3601
3602  private boolean checkReference(List<ValidationMessage> errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern) {
3603    boolean ok = true;
3604    ok = checkFixedValue(errors, path + ".reference", focus.getNamedChild("reference"), fixed.getReferenceElement_(), fixedSource, "reference", focus, pattern) && ok;
3605    ok = checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getTypeElement(), fixedSource, "type", focus, pattern) && ok;
3606    ok = checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier"), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern) && ok;
3607    ok = checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern) && ok;
3608    return ok;
3609  }
3610
3611  private boolean checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) {
3612    boolean ok = true;
3613    ok = checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), fixedSource, "value", focus, pattern) && ok;
3614
3615    List<Element> events = new ArrayList<Element>();
3616    focus.getNamedChildren("event", events);
3617    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()))) {
3618      for (int i = 0; i < events.size(); i++)
3619        ok = checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern) && ok;
3620    }
3621    return ok;
3622  }
3623
3624  private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
3625    for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
3626      if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
3627        return true;
3628      if (codeinExpansion(c, system, code))
3629        return true;
3630    }
3631    return false;
3632  }
3633
3634  private boolean codeInExpansion(ValueSet vs, String system, String code) {
3635    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
3636      if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
3637        return true;
3638      if (codeinExpansion(c, system, code))
3639        return true;
3640    }
3641    return false;
3642  }
3643
3644  private String describeReference(String reference, CanonicalResource target) {
3645    if (reference == null && target == null)
3646      return "null";
3647    if (reference == null) {
3648      return target.getVersionedUrl();
3649    }
3650    if (target == null) {
3651      return reference;
3652    }
3653    String uref = reference.contains("|") ? reference.substring(0, reference.lastIndexOf("|")) : reference;
3654    String vref = reference.contains("|") ? reference.substring(reference.lastIndexOf("|")+1) : null;
3655    if (uref.equals(target.getUrl()) && (vref == null || vref.equals(target.getVersion()))) {
3656      return "'"+target.present()+"' ("+target.getVersionedUrl()+")";
3657    }
3658    return reference + "(which actually refers to '"+target.present()+"' (" + target.getVersionedUrl() + "))";
3659  }
3660
3661  private String describeTypes(List<TypeRefComponent> types) {
3662    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3663    for (TypeRefComponent t : types) {
3664      b.append(t.getWorkingCode());
3665    }
3666    return b.toString();
3667  }
3668
3669  protected ElementDefinition findElement(StructureDefinition profile, String name) {
3670    for (ElementDefinition c : profile.getSnapshot().getElement()) {
3671      if (c.getPath().equals(name)) {
3672        return c;
3673      }
3674    }
3675    return null;
3676  }
3677
3678  public BestPracticeWarningLevel getBestPracticeWarningLevel() {
3679    return bpWarnings;
3680  }
3681
3682  @Override
3683  public CheckDisplayOption getCheckDisplay() {
3684    return checkDisplay;
3685  }
3686
3687  private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
3688    if (code.equals(c.getCode()))
3689      return c;
3690    for (ConceptDefinitionComponent g : c.getConcept()) {
3691      ConceptDefinitionComponent r = getCodeDefinition(g, code);
3692      if (r != null)
3693        return r;
3694    }
3695    return null;
3696  }
3697
3698  private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
3699    for (ConceptDefinitionComponent c : cs.getConcept()) {
3700      ConceptDefinitionComponent r = getCodeDefinition(c, code);
3701      if (r != null)
3702        return r;
3703    }
3704    return null;
3705  }
3706
3707  private IndexedElement getContainedById(Element container, String id) {
3708    List<Element> contained = new ArrayList<Element>();
3709    container.getNamedChildren("contained", contained);
3710    for (int i = 0; i < contained.size(); i++) {
3711      Element we = contained.get(i);
3712      if (id.equals(we.getNamedChildValue(ID))) {
3713        return new IndexedElement(i, we, null);
3714      }
3715    }
3716    return null;
3717  }
3718
3719  public IWorkerContext getContext() {
3720    return context;
3721  }
3722
3723  private List<ElementDefinition> getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException {
3724    List<ElementDefinition> elements = new ArrayList<ElementDefinition>();
3725    if ("value".equals(discriminator) && element.hasFixed()) {
3726      elements.add(element);
3727      return elements;
3728    }
3729
3730    boolean dontFollowReference = false;
3731    
3732    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
3733      if (discriminator.equals("resolve()")) {
3734        elements.add(element);
3735        return elements;
3736      }
3737      if (discriminator.endsWith(".resolve()")) {
3738        discriminator = discriminator.substring(0, discriminator.length() - 10);
3739        dontFollowReference = true;
3740      }
3741    }
3742
3743    TypedElementDefinition ted = null;
3744    String fp = FHIRPathExpressionFixer.fixExpr(discriminator, null);
3745    ExpressionNode expr = null;
3746    try {
3747      expr = fpe.parse(fp);
3748    } catch (Exception e) {
3749      if (STACK_TRACE) e.printStackTrace();
3750      throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e);
3751    }
3752    long t2 = System.nanoTime();
3753    ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile, dontFollowReference);
3754    timeTracker.sd(t2);
3755    if (ted != null)
3756      elements.add(ted.getElement());
3757
3758    for (TypeRefComponent type : element.getType()) {
3759      for (CanonicalType p : type.getProfile()) {
3760        String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null;
3761        StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue(), profile);
3762        if (sd == null)
3763          throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE_, p));
3764        profile = sd;
3765        if (id == null)
3766          element = sd.getSnapshot().getElementFirstRep();
3767        else {
3768          element = null;
3769          for (ElementDefinition t : sd.getSnapshot().getElement()) {
3770            if (id.equals(t.getId()))
3771              element = t;
3772          }
3773          if (element == null)
3774            throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ELEMENT__IN_PROFILE_, id, p));
3775        }
3776        expr = fpe.parse(fp);
3777        t2 = System.nanoTime();
3778        ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile, dontFollowReference);
3779        timeTracker.sd(t2);
3780        if (ted != null)
3781          elements.add(ted.getElement());
3782      }
3783    }
3784    return elements;
3785  }
3786
3787
3788  private Element getExtensionByUrl(List<Element> extensions, String urlSimple) {
3789    for (Element e : extensions) {
3790      if (urlSimple.equals(e.getNamedChildValue("url")))
3791        return e;
3792    }
3793    return null;
3794  }
3795
3796  public List<String> getExtensionDomains() {
3797    return extensionDomains;
3798  }
3799
3800  public List<ImplementationGuide> getImplementationGuides() {
3801    return igs;
3802  }
3803
3804  private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list, Resource src) {
3805    for (TypeRefComponent tr : list) {
3806      String url = tr.getWorkingCode();
3807      if (!Utilities.isAbsoluteUrl(url))
3808        url = "http://hl7.org/fhir/StructureDefinition/" + url;
3809      long t = System.nanoTime();
3810      StructureDefinition sd = context.fetchResource(StructureDefinition.class, url, src);
3811      timeTracker.sd(t);
3812      if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) {
3813        return sd;
3814      }
3815      if (sd.getAbstract()) {
3816        StructureDefinition sdt = context.fetchTypeDefinition(type);
3817        StructureDefinition tt = sdt;
3818        while (tt != null) {
3819          if (tt.getBaseDefinition().equals(sd.getUrl())) {
3820            return sdt;
3821          }
3822        }
3823        
3824        
3825      }
3826    }
3827    return null;
3828  }
3829
3830  private Element getValueForDiscriminator(Object appContext, List<ValidationMessage> errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException {
3831    String p = stack.getLiteralPath() + "." + element.getName();
3832    Element focus = element;
3833    String[] dlist = discriminator.split("\\.");
3834    for (String d : dlist) {
3835      if (focus.fhirType().equals("Reference") && d.equals("reference")) {
3836        String url = focus.getChildValue("reference");
3837        if (Utilities.noString(url))
3838          throw new FHIRException(context.formatMessage(I18nConstants.NO_REFERENCE_RESOLVING_DISCRIMINATOR__FROM_, discriminator, element.getProperty().getName()));
3839        // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough?
3840        Element target = resolve(appContext, url, stack, errors, p);
3841        if (target == null)
3842          throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_, url, d, discriminator, element.getProperty().getName()));
3843        focus = target;
3844      } else if (d.equals("value") && focus.isPrimitive()) {
3845        return focus;
3846      } else {
3847        List<Element> children = focus.getChildren(d);
3848        if (children.isEmpty())
3849          throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND__RESOLVING_DISCRIMINATOR__FROM_, d, discriminator, element.getProperty().getName()));
3850        if (children.size() > 1)
3851          throw new FHIRException(context.formatMessage(I18nConstants.FOUND__ITEMS_FOR__RESOLVING_DISCRIMINATOR__FROM_, Integer.toString(children.size()), d, discriminator, element.getProperty().getName()));
3852        focus = children.get(0);
3853        p = p + "." + d;
3854      }
3855    }
3856    return focus;
3857  }
3858
3859  private CodeSystem getCodeSystem(String system) {
3860    long t = System.nanoTime();
3861    try {
3862      return context.fetchCodeSystem(system);
3863    } finally {
3864      timeTracker.tx(t, "cs "+system);
3865    }
3866  }
3867
3868  private boolean hasTime(String fmt) {
3869    return fmt.contains("T");
3870  }
3871
3872  private boolean hasTimeZone(String fmt) {
3873    return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z"));
3874  }
3875
3876  private boolean isAbsolute(String uri) {
3877    String protocol = null;
3878    String tail = null;
3879    if (uri.contains(":")) {
3880      protocol = uri.substring(0, uri.indexOf(":"));
3881      tail = uri.substring(uri.indexOf(":")+1);
3882    }
3883    if (Utilities.isToken(protocol)) {
3884      if ("file".equals(protocol)) {
3885        return tail.startsWith("/") || tail.contains(":");
3886      } else {
3887        return true;
3888      }
3889    } else {
3890      return false;
3891    }
3892  }
3893
3894  private boolean isCodeSystemReferenceValid(String uri) {
3895    return isSystemReferenceValid(uri);    
3896  }
3897
3898  private boolean isIdentifierSystemReferenceValid(String uri) {
3899    return isSystemReferenceValid(uri) || uri.startsWith("ldap:");
3900  }
3901
3902  private boolean isSystemReferenceValid(String uri) {
3903    return uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:");
3904  }
3905
3906  public boolean isAnyExtensionsAllowed() {
3907    return anyExtensionsAllowed;
3908  }
3909
3910  public boolean isErrorForUnknownProfiles() {
3911    return errorForUnknownProfiles;
3912  }
3913
3914  public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
3915    this.errorForUnknownProfiles = errorForUnknownProfiles;
3916  }
3917
3918  private boolean isParametersEntry(String path) {
3919    String[] parts = path.split("\\.");
3920    return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part"));
3921  }
3922
3923  private boolean isBundleEntry(String path) {
3924    String[] parts = path.split("\\.");
3925    return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && pathEntryHasName(parts[parts.length - 2], ENTRY);
3926  }
3927
3928  private boolean isBundleOutcome(String path) {
3929    String[] parts = path.split("\\.");
3930    return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response");
3931  }
3932
3933
3934  private static boolean pathEntryHasName(String thePathEntry, String theName) {
3935    if (thePathEntry.equals(theName)) {
3936      return true;
3937    }
3938    if (thePathEntry.length() >= theName.length() + 3) {
3939      if (thePathEntry.startsWith(theName)) {
3940        if (thePathEntry.charAt(theName.length()) == '[') {
3941          return true;
3942        }
3943      }
3944    }
3945    return false;
3946  }
3947
3948  public boolean isPrimitiveType(String code) {
3949    StructureDefinition sd = context.fetchTypeDefinition(code);
3950    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3951  }
3952
3953  private String getErrorMessage(String message) {
3954     return message != null ? " (error message = " + message + ")" : "";
3955  }
3956
3957  public boolean isSuppressLoincSnomedMessages() {
3958    return suppressLoincSnomedMessages;
3959  }
3960
3961  private boolean nameMatches(String name, String tail) {
3962    if (tail.endsWith("[x]"))
3963      return name.startsWith(tail.substring(0, tail.length() - 3));
3964    else
3965      return (name.equals(tail));
3966  }
3967
3968  private boolean passesCodeWhitespaceRules(String v) {
3969    if (!v.trim().equals(v))
3970      return false;
3971    boolean lastWasSpace = true;
3972    for (char c : v.toCharArray()) {
3973      if (c == ' ') {
3974        if (lastWasSpace)
3975          return false;
3976        else
3977          lastWasSpace = true;
3978      } else if (Character.isWhitespace(c))
3979        return false;
3980      else
3981        lastWasSpace = false;
3982    }
3983    return true;
3984  }
3985
3986  private ResolvedReference localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path, Element rootResource, Element groupingResource, Element source) {
3987    if (ref.startsWith("#")) {
3988      // work back through the parent list.
3989      // really, there should only be one level for this (contained resources cannot contain
3990      // contained resources), but we'll leave that to some other code to worry about
3991      boolean wasContained = false;
3992      NodeStack nstack = stack;
3993      while (nstack != null && nstack.getElement() != null) {
3994        if (nstack.getElement().getProperty().isResource()) {
3995          // ok, we'll try to find the contained reference
3996          if (ref.equals("#") && nstack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) {
3997            ResolvedReference rr = new ResolvedReference();
3998            rr.setResource(nstack.getElement());
3999            rr.setFocus(nstack.getElement());
4000            rr.setExternal(false);
4001            rr.setStack(nstack);
4002//            rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
4003//            System.out.println("-->"+nstack.getLiteralPath());
4004            return rr;            
4005          }
4006          if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) {
4007            wasContained = true;
4008          }
4009          IndexedElement res = getContainedById(nstack.getElement(), ref.substring(1));
4010          if (res != null) {
4011            ResolvedReference rr = new ResolvedReference();
4012            rr.setResource(nstack.getElement());
4013            rr.setFocus(res.getMatch());
4014            rr.setExternal(false);
4015            rr.setStack(nstack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
4016            rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
4017            return rr;
4018          }
4019        }
4020        if (nstack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || nstack.getElement().getSpecial() == SpecialElement.PARAMETER) {
4021          return null; // we don't try to resolve contained references across this boundary
4022        }
4023        nstack = nstack.getParent();
4024      }
4025      // try again, and work up the element parent list 
4026      if (ref.equals("#")) {
4027        Element e = stack.getElement();
4028        while (e != null) {
4029          if (e.getProperty().isResource() && (e.getSpecial() != SpecialElement.CONTAINED)) {
4030            ResolvedReference rr = new ResolvedReference();
4031            rr.setResource(e);
4032            rr.setFocus(e);
4033            rr.setExternal(false);
4034            rr.setStack(stack.push(e, -1, e.getProperty().getDefinition(), e.getProperty().getDefinition()));
4035            rr.getStack().qualifyPath(".ofType("+e.fhirType()+")");
4036            return rr;            
4037          }
4038          e = e.getParentForValidator();
4039        }
4040      }
4041      return null;
4042    } else {
4043      // work back through the parent list - if any of them are bundles, try to resolve
4044      // the resource in the bundle
4045      String fullUrl = null; // we're going to try to work this out as we go up
4046      while (stack != null && stack.getElement() != null) {
4047        if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && stack.getParent() != null && stack.getParent().getElement().getName().equals(ENTRY)) {
4048          String type = stack.getParent().getParent().getElement().getChildValue(TYPE);
4049          fullUrl = stack.getParent().getElement().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary
4050          if (fullUrl == null)
4051            rule(errors, NO_RULE_DATE, IssueType.REQUIRED, stack.getParent().getElement().line(), stack.getParent().getElement().col(), stack.getParent().getLiteralPath(),
4052              Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL);
4053        }
4054        if (BUNDLE.equals(stack.getElement().getType())) {
4055          String type = stack.getElement().getChildValue(TYPE);
4056          IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type));
4057          if (res == null) {
4058            return null;
4059          } else {
4060            ResolvedReference rr = new ResolvedReference();
4061            rr.setResource(res.getMatch());
4062            rr.setFocus(res.getMatch());
4063            rr.setExternal(false);
4064            rr.setStack(stack.push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
4065              res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
4066              res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
4067            rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")");
4068            return rr;
4069          }
4070        }
4071        if (stack.getElement().getSpecial() == SpecialElement.PARAMETER && stack.getParent() != null) {
4072          NodeStack tgt = findInParams(stack.getParent().getParent(), ref);
4073          if (tgt != null) {
4074            ResolvedReference rr = new ResolvedReference();
4075            rr.setResource(tgt.getElement());
4076            rr.setFocus(tgt.getElement());
4077            rr.setExternal(false);
4078            rr.setStack(tgt);
4079            rr.getStack().qualifyPath(".ofType("+tgt.getElement().fhirType()+")");
4080            return rr;            
4081          }
4082        }
4083        stack = stack.getParent();
4084      }
4085      // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity.
4086      if (groupingResource != null && BUNDLE.equals(groupingResource.fhirType())) { // it could also be a Parameters resource - that case isn't handled yet
4087        String type = groupingResource.getChildValue(TYPE);
4088        Element entry = getEntryForSource(groupingResource, source);
4089        fullUrl = entry.getChildValue(FULL_URL);
4090        IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type));
4091        if (res == null) {
4092          return null;
4093        } else {
4094          ResolvedReference rr = new ResolvedReference();
4095          rr.setResource(res.getMatch());
4096          rr.setFocus(res.getMatch());
4097          rr.setExternal(false);
4098          rr.setStack(new NodeStack(context, null, rootResource, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
4099            res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
4100            res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
4101          rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")");
4102          return rr;
4103        }
4104      }
4105    }
4106    return null;
4107  }
4108
4109  private NodeStack findInParams(NodeStack params, String ref) {
4110    int i = 0;
4111    for (Element child : params.getElement().getChildren("parameter")) {
4112      NodeStack p = params.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
4113      if (child.hasChild("resource")) {
4114        Element res = child.getNamedChild("resource");
4115        if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
4116          return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
4117        }
4118      }
4119      NodeStack pc = findInParamParts(p, child, ref);
4120      if (pc != null) {
4121        return pc;
4122      }
4123    }
4124    return null;
4125  }
4126
4127  private NodeStack findInParamParts(NodeStack pp, Element param, String ref) {
4128    int i = 0;
4129    for (Element child : param.getChildren("part")) {
4130      NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
4131      if (child.hasChild("resource")) {
4132        Element res = child.getNamedChild("resource");
4133        if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
4134          return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
4135        }
4136      }
4137      NodeStack pc = findInParamParts(p, child, ref);
4138      if (pc != null) {
4139        return pc;
4140      }
4141    }
4142    return null;
4143  }
4144
4145  private Element getEntryForSource(Element bundle, Element element) {
4146    List<Element> entries = new ArrayList<Element>();
4147    bundle.getNamedChildren(ENTRY, entries);
4148    for (Element entry : entries) {
4149      if (entry.hasDescendant(element)) {
4150        return entry;
4151      }
4152    }
4153    return null;
4154  }
4155
4156  private ResolvedReference makeExternalRef(Element external, String path) {
4157    ResolvedReference res = new ResolvedReference();
4158    res.setResource(external);
4159    res.setFocus(external);
4160    res.setExternal(true);
4161    res.setStack(new NodeStack(context, external, path, validationLanguage));
4162    return res;
4163  }
4164
4165
4166  private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException {
4167    Element local = localResolve(ref, stack, errors, path, null, null, null).getFocus();
4168    if (local != null)
4169      return local;
4170    if (fetcher == null)
4171      return null;
4172    if (fetchCache.containsKey(ref)) {
4173      return fetchCache.get(ref);
4174    } else {
4175      Element res = fetcher.fetch(this, appContext, ref);
4176      setParents(res);
4177      fetchCache.put(ref, res);
4178      return res;
4179    }
4180  }
4181
4182
4183  private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) {
4184    for (ElementDefinition ed : snapshot.getElement())
4185      if (contentReference.equals("#" + ed.getId()))
4186        return ed;
4187    return null;
4188  }
4189
4190  private StructureDefinition resolveProfile(StructureDefinition profile, String pr) {
4191    if (pr.startsWith("#")) {
4192      for (Resource r : profile.getContained()) {
4193        if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition)
4194          return (StructureDefinition) r;
4195      }
4196      return null;
4197    } else {
4198      long t = System.nanoTime();
4199      StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr, profile);
4200      timeTracker.sd(t);
4201      return fr;
4202    }
4203  }
4204
4205  private ElementDefinition resolveType(String type, List<TypeRefComponent> list) {
4206    for (TypeRefComponent tr : list) {
4207      String url = tr.getWorkingCode();
4208      if (!Utilities.isAbsoluteUrl(url))
4209        url = "http://hl7.org/fhir/StructureDefinition/" + url;
4210      long t = System.nanoTime();
4211      StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
4212      timeTracker.sd(t);
4213      if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot())
4214        return sd.getSnapshot().getElement().get(0);
4215    }
4216    return null;
4217  }
4218
4219  public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) {
4220    this.anyExtensionsAllowed = anyExtensionsAllowed;
4221  }
4222
4223  public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
4224    bpWarnings = value;
4225    return this;
4226  }
4227
4228  @Override
4229  public void setCheckDisplay(CheckDisplayOption checkDisplay) {
4230    this.checkDisplay = checkDisplay;
4231  }
4232
4233  public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) {
4234    this.suppressLoincSnomedMessages = suppressLoincSnomedMessages;
4235  }
4236
4237  public IdStatus getResourceIdRule() {
4238    return resourceIdRule;
4239  }
4240
4241  public void setResourceIdRule(IdStatus resourceIdRule) {
4242    this.resourceIdRule = resourceIdRule;
4243  }
4244
4245
4246  public boolean isAllowXsiLocation() {
4247    return allowXsiLocation;
4248  }
4249
4250  public void setAllowXsiLocation(boolean allowXsiLocation) {
4251    this.allowXsiLocation = allowXsiLocation;
4252  }
4253
4254  /**
4255   * @param element - the candidate that might be in the slice
4256   * @param path    - for reporting any errors. the XPath for the element
4257   * @param slicer  - the definition of how slicing is determined
4258   * @param ed      - the slice for which to test membership
4259   * @param errors
4260   * @param stack
4261   * @param srcProfile 
4262   * @return
4263   * @throws DefinitionException
4264   * @throws DefinitionException
4265   * @throws IOException
4266   * @throws FHIRException
4267   */
4268  private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException {
4269    if (!slicer.getSlicing().hasDiscriminator())
4270      return false; // cannot validate in this case
4271
4272    ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache");
4273    if (n == null) {
4274      long t = System.nanoTime();
4275      // GG: this approach is flawed because it treats discriminators individually rather than collectively
4276      StringBuilder expression = new StringBuilder("true");
4277      boolean anyFound = false;
4278      Set<String> discriminators = new HashSet<>();
4279      for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) {
4280        String discriminator = s.getPath();
4281        discriminators.add(discriminator);
4282
4283        List<ElementDefinition> criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile);
4284        boolean found = false;
4285        for (ElementDefinition criteriaElement : criteriaElements) {
4286          found = true;
4287          if ("0".equals(criteriaElement.getMax())) {
4288            expression.append(" and " + discriminator + ".empty()");            
4289          } else if (s.getType() == DiscriminatorType.TYPE) {
4290            String type = null;
4291            if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) {
4292              discriminator = discriminator.substring(0, discriminator.indexOf('['));
4293              String lastNode = tail(discriminator);
4294              type = tail(criteriaElement.getPath()).substring(lastNode.length());
4295              type = type.substring(0, 1).toLowerCase() + type.substring(1);
4296            } else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) {
4297              if (discriminator.contains("["))
4298                discriminator = discriminator.substring(0, discriminator.indexOf('['));
4299              if (criteriaElement.hasType()) {
4300                type = criteriaElement.getType().get(0).getWorkingCode();
4301              } else if (!criteriaElement.getPath().contains(".")) {
4302                type = criteriaElement.getPath();
4303              } else {
4304                throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl()));
4305              }
4306            } else if (criteriaElement.getType().size() > 1) {
4307              throw new DefinitionException(context.formatMessagePlural(criteriaElement.getType().size(), I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES, discriminator, ed.getId(), profile.getVersionedUrl(), criteriaElement.typeSummary()));
4308            } else
4309              throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getVersionedUrl()));
4310            if (discriminator.isEmpty()) {
4311              expression.append(" and $this is " + type);
4312            } else {
4313              expression.append(" and " + discriminator + " is " + type);
4314            }
4315          } else if (s.getType() == DiscriminatorType.PROFILE) {
4316            if (criteriaElement.getType().size() == 0) {
4317              throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl()));
4318            }
4319            if (criteriaElement.getType().size() != 1) {
4320              throw new DefinitionException(context.formatMessagePlural(criteriaElement.getType().size(), I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE, criteriaElement.getId(), profile.getVersionedUrl()));
4321            }
4322            List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile();
4323            if (list.size() == 0) {
4324              throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getVersionedUrl()));
4325            } else if (list.size() > 1) {
4326              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or ");
4327              for (CanonicalType c : list) {
4328                b.append(discriminator + ".conformsTo('" + c.getValue() + "')");
4329              }
4330              expression.append(" and (" + b + ")");
4331            } else {
4332              expression.append(" and " + discriminator + ".conformsTo('" + list.get(0).getValue() + "')");
4333            }
4334          } else if (s.getType() == DiscriminatorType.EXISTS) {
4335            if (criteriaElement.hasMin() && criteriaElement.getMin() >= 1) {
4336              expression.append(" and (" + discriminator + ".exists())");
4337            } else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) {
4338              expression.append(" and (" + discriminator + ".exists().not())");
4339            } else {
4340              throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0, discriminator, ed.getId()));
4341            }
4342          } else if (criteriaElement.hasFixed()) {
4343            buildFixedExpression(ed, expression, discriminator, criteriaElement);
4344          } else if (criteriaElement.hasPattern()) {
4345            buildPattternExpression(ed, expression, discriminator, criteriaElement);
4346          } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().hasValueSet()) {
4347            expression.append(" and (" + discriminator + " memberOf '" + criteriaElement.getBinding().getValueSet() + "')");
4348          } else {
4349            found = false;
4350          }
4351          if (found)
4352            break;
4353        }
4354        if (found)
4355          anyFound = true;
4356      }
4357      if (!anyFound) {
4358          throw new DefinitionException(context.formatMessagePlural(slicer.getSlicing().getDiscriminator().size(), I18nConstants.Could_not_match_discriminator_for_slice_in_profile, discriminators, ed.getId(), profile.getVersionedUrl(), discriminators));
4359      }
4360
4361      try {
4362        n = fpe.parse(FHIRPathExpressionFixer.fixExpr(expression.toString(), null));
4363      } catch (FHIRLexerException e) {
4364        if (STACK_TRACE) e.printStackTrace();
4365        throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, expression, profile.getVersionedUrl(), path, e.getMessage()));
4366      }
4367      timeTracker.fpe(t);
4368      ed.setUserData("slice.expression.cache", n);
4369    } else {
4370    }
4371
4372    ValidatorHostContext shc = hostContext.forSlicing();
4373    boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
4374    if (!pass) {
4375      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);
4376      for (String url : shc.getSliceRecords().keySet()) {
4377        StructureDefinition sdt = context.fetchResource(StructureDefinition.class, url);
4378        slicingHint(sliceInfo, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), 
4379         context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), sdt == null ?  url : sdt.getVersionedUrl()),
4380          context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__,
4381              url,
4382              stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url))), errorSummaryForSlicingAsText(shc.getSliceRecords().get(url)));
4383      }
4384    }
4385    return pass;
4386  }
4387
4388  private boolean isBaseDefinition(String url) {
4389    boolean b = url.startsWith("http://hl7.org/fhir/") && !url.substring(40).contains("/");
4390    return b;
4391  }
4392
4393  private String descSD(String url) {
4394    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
4395    return sd == null ? url : sd.present();
4396  }
4397
4398  private boolean isProfile(ElementDefinition slicer) {
4399    if (slicer == null || !slicer.hasSlicing()) {
4400      return false;
4401    }
4402    for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) {
4403      if (t.getType() == DiscriminatorType.PROFILE) {
4404        return true;
4405      }
4406    }
4407    return false;
4408  }
4409
4410  public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
4411    String msg;
4412    boolean ok;
4413    try {
4414      long t = System.nanoTime();
4415      ok = fpe.evaluateToBoolean(hostContext.forProfile(profile), hostContext.getResource(), hostContext.getRootResource(), element, n);
4416      timeTracker.fpe(t);
4417      msg = fpe.forLog();
4418    } catch (Exception ex) {
4419      if (STACK_TRACE) ex.printStackTrace();
4420      throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_EVALUATING_SLICING_EXPRESSION_FOR_ELEMENT_IN_PROFILE__PATH__FHIRPATH___, profile.getVersionedUrl(), path, n, ex.getMessage()));
4421    }
4422    return ok;
4423  }
4424
4425  private void buildPattternExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException {
4426    DataType pattern = criteriaElement.getPattern();
4427    if (pattern instanceof CodeableConcept) {
4428      CodeableConcept cc = (CodeableConcept) pattern;
4429      expression.append(" and ");
4430      buildCodeableConceptExpression(ed, expression, discriminator, cc);
4431    } else if (pattern instanceof Coding) {
4432      Coding c = (Coding) pattern;
4433      expression.append(" and ");
4434      buildCodingExpression(ed, expression, discriminator, c);
4435    } else if (pattern instanceof BooleanType || pattern instanceof IntegerType || pattern instanceof DecimalType) {
4436      expression.append(" and ");
4437      buildPrimitiveExpression(ed, expression, discriminator, pattern, false);
4438    } else if (pattern instanceof PrimitiveType) {
4439      expression.append(" and ");
4440      buildPrimitiveExpression(ed, expression, discriminator, pattern, true);
4441    } else if (pattern instanceof Identifier) {
4442      Identifier ii = (Identifier) pattern;
4443      expression.append(" and ");
4444      buildIdentifierExpression(ed, expression, discriminator, ii);
4445    } else if (pattern instanceof HumanName) {
4446      HumanName name = (HumanName) pattern;
4447      expression.append(" and ");
4448      buildHumanNameExpression(ed, expression, discriminator, name);
4449    } else if (pattern instanceof Address) {
4450      Address add = (Address) pattern;
4451      expression.append(" and ");
4452      buildAddressExpression(ed, expression, discriminator, add);
4453    } else {
4454      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_PATTERN_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), pattern.fhirType()));
4455    }
4456  }
4457
4458  private void buildIdentifierExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Identifier ii)
4459    throws DefinitionException {
4460    if (ii.hasExtension())
4461      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4462    boolean first = true;
4463    expression.append(discriminator + ".where(");
4464    if (ii.hasSystem()) {
4465      first = false;
4466      expression.append("system = '" + ii.getSystem() + "'");
4467    }
4468    if (ii.hasValue()) {
4469      if (first)
4470        first = false;
4471      else
4472        expression.append(" and ");
4473      expression.append("value = '" + ii.getValue() + "'");
4474    }
4475    if (ii.hasUse()) {
4476      if (first)
4477        first = false;
4478      else
4479        expression.append(" and ");
4480      expression.append("use = '" + ii.getUse() + "'");
4481    }
4482    if (ii.hasType()) {
4483      if (first)
4484        first = false;
4485      else
4486        expression.append(" and ");
4487      buildCodeableConceptExpression(ed, expression, TYPE, ii.getType());
4488    }
4489    if (first) {
4490      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), ii.fhirType()));
4491    }
4492    expression.append(").exists()");
4493  }
4494
4495  private void buildHumanNameExpression(ElementDefinition ed, StringBuilder expression, String discriminator, HumanName name) throws DefinitionException {
4496    if (name.hasExtension())
4497      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4498    boolean first = true;
4499    expression.append(discriminator + ".where(");
4500    if (name.hasUse()) {
4501      first = false;
4502      expression.append("use = '" + name.getUse().toCode() + "'");
4503    }
4504    if (name.hasText()) {
4505      if (first)
4506        first = false;
4507      else
4508        expression.append(" and ");
4509      expression.append("text = '" + name.getText() + "'");
4510    }
4511    if (name.hasFamily()) {
4512      if (first)
4513        first = false;
4514      else
4515        expression.append(" and ");
4516      expression.append("family = '" + name.getFamily() + "'");
4517    }
4518    if (name.hasGiven()) {
4519      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "given"));
4520    }
4521    if (name.hasPrefix()) {
4522      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "prefix"));
4523    }
4524    if (name.hasSuffix()) {
4525      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "suffix"));
4526    }
4527    if (name.hasPeriod()) {
4528      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "period"));
4529    }
4530    if (first) {
4531      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType()));
4532    }
4533
4534    expression.append(").exists()");
4535  }
4536
4537  private void buildAddressExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Address add) throws DefinitionException {
4538    if (add.hasExtension()) {
4539      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4540    }
4541    boolean first = true;
4542    expression.append(discriminator + ".where(");
4543    if (add.hasUse()) {
4544      first = false;
4545      expression.append("use = '" + add.getUse().toCode() + "'");
4546    }
4547    if (add.hasType()) {
4548      if (first) first = false; else expression.append(" and ");
4549      expression.append("type = '" + add.getType().toCode() + "'");
4550    }
4551    if (add.hasText()) {
4552      if (first) first = false; else expression.append(" and ");
4553      expression.append("text = '" + add.getText() + "'");
4554    }
4555    if (add.hasCity()) {
4556      if (first) first = false; else expression.append(" and ");
4557      expression.append("city = '" + add.getCity() + "'");
4558    }
4559    if (add.hasDistrict()) {
4560      if (first) first = false; else expression.append(" and ");
4561      expression.append("district = '" + add.getDistrict() + "'");
4562    }
4563    if (add.hasState()) {
4564      if (first) first = false; else expression.append(" and ");
4565      expression.append("state = '" + add.getState() + "'");
4566    }
4567    if (add.hasPostalCode()) {
4568      if (first) first = false; else expression.append(" and ");
4569      expression.append("postalCode = '" + add.getPostalCode() + "'");
4570    }
4571    if (add.hasCountry()) {
4572      if (first) first = false; else expression.append(" and ");
4573      expression.append("country = '" + add.getCountry() + "'");
4574    }       
4575    if (add.hasLine()) {
4576      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "line"));
4577    }
4578    if (add.hasPeriod()) {
4579      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "period"));
4580    }
4581    if (first) {
4582      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType()));
4583    }
4584    expression.append(").exists()");
4585  }
4586
4587  private void buildCodeableConceptExpression(ElementDefinition ed, StringBuilder expression, String discriminator, CodeableConcept cc)
4588    throws DefinitionException {
4589    if (cc.hasText())
4590      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__USING_TEXT__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4591    if (!cc.hasCoding())
4592      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__MUST_HAVE_AT_LEAST_ONE_CODING__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4593    if (cc.hasExtension())
4594      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4595    boolean firstCoding = true;
4596    for (Coding c : cc.getCoding()) {
4597      if (c.hasExtension())
4598        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4599      if (firstCoding) firstCoding = false;
4600      else expression.append(" and ");
4601      expression.append(discriminator + ".coding.where(");
4602      boolean first = true;
4603      if (c.hasSystem()) {
4604        first = false;
4605        expression.append("system = '" + c.getSystem() + "'");
4606      }
4607      if (c.hasVersion()) {
4608        if (first) first = false;
4609        else expression.append(" and ");
4610        expression.append("version = '" + c.getVersion() + "'");
4611      }
4612      if (c.hasCode()) {
4613        if (first) first = false;
4614        else expression.append(" and ");
4615        expression.append("code = '" + c.getCode() + "'");
4616      }
4617      if (c.hasDisplay()) {
4618        if (first) first = false;
4619        else expression.append(" and ");
4620        expression.append("display = '" + c.getDisplay() + "'");
4621      }
4622      if (first) {
4623        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), cc.fhirType()));
4624      }
4625      expression.append(").exists()");
4626    }
4627  }
4628
4629  private void buildCodingExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Coding c)
4630    throws DefinitionException {
4631    if (c.hasExtension())
4632      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4633    expression.append(discriminator + ".where(");
4634    boolean first = true;
4635    if (c.hasSystem()) {
4636      first = false;
4637      expression.append("system = '" + c.getSystem() + "'");
4638    }
4639    if (c.hasVersion()) {
4640      if (first) first = false;
4641      else expression.append(" and ");
4642      expression.append("version = '" + c.getVersion() + "'");
4643    }
4644    if (c.hasCode()) {
4645      if (first) first = false;
4646      else expression.append(" and ");
4647      expression.append("code = '" + c.getCode() + "'");
4648    }
4649    if (c.hasDisplay()) {
4650      if (first) first = false;
4651      else expression.append(" and ");
4652      expression.append("display = '" + c.getDisplay() + "'");
4653    }
4654    if (first) {
4655      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), c.fhirType()));
4656    }
4657    expression.append(").exists()");
4658  }
4659
4660  private void buildPrimitiveExpression(ElementDefinition ed, StringBuilder expression, String discriminator, DataType p, boolean quotes) throws DefinitionException {
4661      if (p.hasExtension())
4662        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4663      if (quotes) {        
4664        expression.append(discriminator + ".where(value = '" + p.primitiveValue() + "'");
4665      } else {
4666        expression.append(discriminator + ".where(value = " + p.primitiveValue() + "");
4667      }
4668      expression.append(").exists()");
4669    }
4670
4671  private void buildFixedExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException {
4672    DataType fixed = criteriaElement.getFixed();
4673    if (fixed instanceof CodeableConcept) {
4674      CodeableConcept cc = (CodeableConcept) fixed;
4675      expression.append(" and ");
4676      buildCodeableConceptExpression(ed, expression, discriminator, cc);
4677    } else if (fixed instanceof Identifier) {
4678      Identifier ii = (Identifier) fixed;
4679      expression.append(" and ");
4680      buildIdentifierExpression(ed, expression, discriminator, ii);
4681    } else if (fixed instanceof Coding) {
4682      Coding c = (Coding) fixed;
4683      expression.append(" and ");
4684      buildCodingExpression(ed, expression, discriminator, c);
4685    } else {
4686      expression.append(" and (");
4687      if (fixed instanceof StringType) {
4688        String es = Utilities.escapeJson(fixed.primitiveValue());
4689        expression.append("'" + es + "'");
4690      } else if (fixed instanceof UriType) {
4691        expression.append("'" + ((UriType) fixed).asStringValue() + "'");
4692      } else if (fixed instanceof IntegerType) {
4693        expression.append(((IntegerType) fixed).asStringValue());
4694      } else if (fixed instanceof DecimalType) {
4695        expression.append(((IntegerType) fixed).asStringValue());
4696      } else if (fixed instanceof BooleanType) {
4697        expression.append(((BooleanType) fixed).asStringValue());
4698      } else
4699        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_VALUE_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), fixed.getClass().getName()));
4700      expression.append(" in " + discriminator + ")");
4701    }
4702  }
4703
4704  // checkSpecials = we're only going to run these tests if we are actually validating this content (as opposed to we looked it up)
4705  private boolean start(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, PercentageTracker pct, ValidationMode mode) throws FHIRException {
4706    boolean ok = true;
4707    
4708    checkLang(resource, stack);
4709    if (crumbTrails) {
4710      element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, defn.getVersionedUrl()));
4711    }
4712    boolean pctOwned = false;
4713    if (pct == null) { 
4714      // this method is reentrant, but also the right place to tell the user what is going on if it's the root. 
4715      // if we're not at the root, we don't report progress
4716      pctOwned = true;
4717      pct = new PercentageTracker(resource.countDescendents()+1, resource.fhirType(), defn.getVersionedUrl(), logProgress);
4718    }
4719    if (BUNDLE.equals(element.fhirType())) {
4720      if (debug) {
4721        System.out.println("Resolve Bundle Entries "+time());
4722      }
4723      resolveBundleReferences(element, new ArrayList<Element>());
4724    }
4725    ok = startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials(), pct, mode) && ok;
4726    if (pctOwned) {
4727      pct.done();
4728    }
4729 
4730    Element meta = element.getNamedChild(META);
4731    if (meta != null) {
4732      List<Element> profiles = new ArrayList<Element>();
4733      meta.getNamedChildren("profile", profiles);
4734      int i = 0;
4735      for (Element profile : profiles) {
4736        StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue());
4737        if (!defn.getUrl().equals(profile.primitiveValue())) {
4738          // is this a version specific reference? 
4739          VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue());
4740          if (vu != null) {
4741            if (!VersionUtilities.versionsCompatible(vu.getVersion(),  context.getVersion())) {
4742              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());
4743            } else if (vu.getUrl().equals(defn.getUrl())) {
4744              hint(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK);              
4745            } else {
4746              StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl());
4747              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;                            
4748            }
4749          } else {
4750            if (sd == null) {
4751              // we'll try fetching it directly from it's source, but this is likely to fail later even if the resolution succeeds
4752              if (fetcher == null) {
4753                warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue());
4754              } else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) {
4755                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());                
4756              } else {
4757                sd = lookupProfileReference(errors, element, stack, i, profile, sd);
4758              }
4759            }
4760            if (sd != null) {
4761              if (crumbTrails) {
4762                element.addMessage(signpost(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getVersionedUrl()));
4763              }
4764              stack.resetIds();
4765              if (pctOwned) {
4766                pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getUrl(), logProgress);
4767              }
4768              ok = startInner(hostContext, errors, resource, element, sd, stack, false, pct, mode.withSource(ProfileSource.MetaProfile)) && ok;
4769              if (pctOwned) {
4770                pct.done();
4771              }
4772              if (sd.hasExtension(ToolingExtensions.EXT_SD_DEPENDENCY)) {
4773                for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_DEPENDENCY)) {
4774                  StructureDefinition sdi = context.fetchResource(StructureDefinition.class, ext.getValue().primitiveValue());
4775                  if (sdi == null) {
4776                    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());                
4777                  } else {
4778                    if (crumbTrails) {
4779                      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()));
4780                    }
4781                    stack.resetIds();
4782                    if (pctOwned) {
4783                      pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sdi.getUrl(), logProgress);
4784                    }
4785                    ok = startInner(hostContext, errors, resource, element, sdi, stack, false, pct, mode.withSource(ProfileSource.ProfileDependency)) && ok;
4786                    if (pctOwned) {
4787                      pct.done();
4788                    }
4789                    
4790                  }
4791                }
4792              }
4793            }
4794          }
4795        }
4796        i++;
4797      }
4798    }
4799    String rt = element.fhirType();
4800    for (ImplementationGuide ig : igs) {
4801      for (ImplementationGuideGlobalComponent gl : ig.getGlobal()) {
4802        if (rt.equals(gl.getType())) {
4803          StructureDefinition sd = context.fetchResource(StructureDefinition.class, gl.getProfile(), ig);
4804          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())) {
4805            if (crumbTrails) {
4806              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()));
4807            }
4808            stack.resetIds();
4809            if (pctOwned) {
4810              pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sd.getVersionedUrl(), logProgress);
4811            }
4812            ok = startInner(hostContext, errors, resource, element, sd, stack, false, pct, mode.withSource(ProfileSource.GlobalProfile)) && ok;
4813            if (pctOwned) {
4814              pct.done();
4815            }
4816          }
4817        }
4818      }
4819    }
4820//    System.out.println("start: "+(System.currentTimeMillis()-st)+" ("+resource.fhirType()+")");
4821    return ok;
4822  }
4823
4824  private StructureDefinition lookupProfileReference(List<ValidationMessage> errors, Element element, NodeStack stack,
4825      int i, Element profile, StructureDefinition sd) {
4826    String url = profile.primitiveValue();
4827    CanonicalResourceLookupResult cr = crLookups.get(url);
4828    if (cr != null) {
4829      if (cr.error != null) {
4830        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.error);                
4831      } else {
4832        sd = (StructureDefinition) cr.resource;
4833      }
4834    } else {
4835      try {
4836        sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, url);
4837        crLookups.put(url, new CanonicalResourceLookupResult(sd));
4838      } catch (Exception e) {
4839        if (STACK_TRACE) { e.printStackTrace(); }
4840        crLookups.put(url, new CanonicalResourceLookupResult(e.getMessage()));
4841        if (e.getMessage() != null && e.getMessage().startsWith("java.net.UnknownHostException:")) {
4842          try {
4843            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());
4844          } catch (URISyntaxException e1) {
4845            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(), "??");
4846          }
4847        } else {
4848          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());
4849        }
4850      }
4851      if (sd != null) {
4852        context.cacheResource(sd);
4853      }
4854    }
4855    return sd;
4856  }
4857
4858//  private void plog(String msg) {
4859//    long n = System.currentTimeMillis();
4860//    String elapsed = Utilities.padLeft(Long.toString(n-start), ' ', 5);
4861//    String delta = Utilities.padLeft(lastlog == 0 ? "0" : Long.toString(n-lastlog), ' ', 5);
4862//    lastlog = n;
4863//    System.out.println("-- "+elapsed+" "+delta+" "+msg);
4864//  }
4865
4866  private void resolveBundleReferences(Element element, List<Element> bundles) {
4867    if (!element.hasUserData("validator.bundle.resolved")) {
4868      element.setUserData("validator.bundle.resolved", true);
4869      List<Element> list = new ArrayList<Element>();
4870      list.addAll(bundles);
4871      list.add(0, element);
4872      List<Element> entries = element.getChildrenByName(ENTRY);
4873      for (Element entry : entries) {
4874        String fu = entry.getChildValue(FULL_URL);
4875        Element r = entry.getNamedChild(RESOURCE);
4876        if (r != null) {
4877          resolveBundleReferencesInResource(list, r, fu);
4878        }
4879      }
4880    }
4881  }
4882
4883  private void resolveBundleReferencesInResource(List<Element> bundles, Element r, String fu) {
4884    if (BUNDLE.equals(r.fhirType())) {
4885      resolveBundleReferences(r, bundles);
4886    } else {
4887      for (Element child : r.getChildren()) {
4888        resolveBundleReferencesForElement(bundles, r, fu, child);
4889      }
4890    }
4891  }
4892
4893  private void resolveBundleReferencesForElement(List<Element> bundles, Element resource, String fu, Element element) {
4894    if ("Reference".equals(element.fhirType())) {
4895      String ref = element.getChildValue("reference");
4896      if (!Utilities.noString(ref)) {
4897        for (Element bundle : bundles) {
4898          List<Element> entries = bundle.getChildren(ENTRY);
4899          Element tgt = resolveInBundle(bundle, entries, ref, fu, resource.fhirType(), resource.getIdBase());
4900          if (tgt != null) {
4901            element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE));
4902            return;
4903          }
4904        }
4905      }
4906    } else {
4907      for (Element child : element.getChildren()) {
4908        resolveBundleReferencesForElement(bundles, resource, fu, child);
4909      }
4910    }
4911  }
4912
4913  public boolean startInner(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode) {    
4914    // the first piece of business is to see if we've validated this resource against this profile before.
4915    // if we have (*or if we still are*), then we'll just return our existing errors
4916    boolean ok = false;
4917    ResourceValidationTracker resTracker = getResourceTracker(element);
4918    List<ValidationMessage> cachedErrors = resTracker.getOutcomes(defn);
4919    if (cachedErrors != null) {
4920      for (ValidationMessage vm : cachedErrors) {
4921        if (!errors.contains(vm)) {
4922          errors.add(vm);
4923          ok = ok && vm.getLevel() != IssueSeverity.ERROR && vm.getLevel() != IssueSeverity.FATAL; 
4924        }
4925      }
4926      return ok;
4927    }
4928    if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getVersionedUrl())) {
4929      List<ValidationMessage> localErrors = new ArrayList<ValidationMessage>();
4930      resTracker.startValidating(defn);
4931      trackUsage(defn, hostContext, element);
4932      ok = validateElement(hostContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null, pct, mode) && ok;
4933      resTracker.storeOutcomes(defn, localErrors);
4934      for (ValidationMessage vm : localErrors) {
4935        if (!errors.contains(vm)) {
4936          errors.add(vm);
4937        }
4938      }
4939    } else {
4940      ok = false;
4941    }
4942    if (checkSpecials) {
4943      ok = checkSpecials(hostContext, errors, element, stack, checkSpecials, pct, mode) && ok;
4944      ok = validateResourceRules(errors, element, stack) && ok;
4945    }
4946    return ok;
4947  }
4948
4949  public boolean checkSpecials(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials, PercentageTracker pct, ValidationMode mode) {
4950    // specific known special validations
4951    if (element.getType().equals(BUNDLE)) {
4952      return new BundleValidator(context, serverBase, this, xverManager, jurisdiction).validateBundle(errors, element, stack, checkSpecials, hostContext, pct, mode);
4953    } else if (element.getType().equals("Observation")) {
4954      return validateObservation(errors, element, stack);
4955    } else if (element.getType().equals("Questionnaire")) {
4956      return new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager, jurisdiction).validateQuestionannaire(errors, element, element, stack);
4957    } else if (element.getType().equals("QuestionnaireResponse")) {
4958      return new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager, jurisdiction).validateQuestionannaireResponse(hostContext, errors, element, stack);
4959    } else if (element.getType().equals("Measure")) {
4960      return new MeasureValidator(context, timeTracker, xverManager, jurisdiction, this).validateMeasure(hostContext, errors, element, stack);      
4961    } else if (element.getType().equals("MeasureReport")) {
4962      return new MeasureValidator(context, timeTracker, xverManager, jurisdiction, this).validateMeasureReport(hostContext, errors, element, stack);
4963    } else if (element.getType().equals("CapabilityStatement")) {
4964      return validateCapabilityStatement(errors, element, stack);
4965    } else if (element.getType().equals("CodeSystem")) {
4966      return new CodeSystemValidator(context, timeTracker, this, xverManager, jurisdiction).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang()));
4967    } else if (element.getType().equals("SearchParameter")) {
4968      return new SearchParameterValidator(context, timeTracker, fpe, xverManager, jurisdiction).validateSearchParameter(errors, element, stack);
4969    } else if (element.getType().equals("StructureDefinition")) {
4970      return new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager, jurisdiction).validateStructureDefinition(errors, element, stack);
4971    } else if (element.getType().equals("ValueSet")) {
4972      return new ValueSetValidator(context, timeTracker, this, xverManager, jurisdiction).validateValueSet(errors, element, stack);
4973    } else {
4974      return true;
4975    }
4976  }
4977
4978  private ResourceValidationTracker getResourceTracker(Element element) {
4979    ResourceValidationTracker res = resourceTracker.get(element);
4980    if (res == null) {
4981      res = new ResourceValidationTracker();
4982      resourceTracker.put(element, res);
4983    }
4984    return res;
4985  }
4986
4987  private void checkLang(Element resource, NodeStack stack) {
4988    String lang = resource.getNamedChildValue("language");
4989    if (!Utilities.noString(lang))
4990      stack.setWorkingLang(lang);
4991  }
4992
4993  private boolean validateResourceRules(List<ValidationMessage> errors, Element element, NodeStack stack) {
4994    boolean ok= true;
4995    String lang = element.getNamedChildValue("language");
4996    Element text = element.getNamedChild("text");
4997    if (text != null) {
4998      Element div = text.getNamedChild("div");
4999      if (lang != null && div != null) {
5000        XhtmlNode xhtml = div.getXhtml();
5001        String l = xhtml.getAttribute("lang");
5002        String xl = xhtml.getAttribute("xml:lang");
5003        if (l == null && xl == null) {
5004          warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING1);
5005        } else {
5006          if (l == null) {
5007            warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING2);
5008          } else if (!l.equals(lang)) {
5009            warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT1, lang, l);
5010          }
5011          if (xl == null) {
5012            warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING3);
5013          } else if (!xl.equals(lang)) {
5014            warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT2, lang, xl);
5015          }
5016        }
5017      }
5018    }
5019    // security tags are a set (system|code)
5020    Element meta = element.getNamedChild(META);
5021    if (meta != null) {
5022      Set<String> tags = new HashSet<>();
5023      List<Element> list = new ArrayList<>();
5024      meta.getNamedChildren("security", list);
5025      int i = 0;
5026      for (Element e : list) {
5027        String s = e.getNamedChildValue("system") + "#" + e.getNamedChildValue("code");
5028        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;
5029        tags.add(s);
5030        i++;
5031      }
5032    }
5033    return ok;
5034  }
5035
5036  private boolean validateCapabilityStatement(List<ValidationMessage> errors, Element cs, NodeStack stack) {
5037    boolean ok = true;
5038    int iRest = 0;
5039    for (Element rest : cs.getChildrenByName("rest")) {
5040      int iResource = 0;
5041      for (Element resource : rest.getChildrenByName(RESOURCE)) {
5042        int iSP = 0;
5043        for (Element searchParam : resource.getChildrenByName("searchParam")) {
5044          String ref = searchParam.getChildValue("definition");
5045          String type = searchParam.getChildValue(TYPE);
5046          if (!Utilities.noString(ref)) {
5047            SearchParameter sp = context.fetchResource(SearchParameter.class, ref);
5048            if (sp != null) {
5049              ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, searchParam.line(), searchParam.col(), stack.getLiteralPath() + ".rest[" + iRest + "].resource[" + iResource + "].searchParam[" + iSP + "]",
5050                sp.getType().toCode().equals(type), I18nConstants.CAPABALITYSTATEMENT_CS_SP_WRONGTYPE, sp.getVersionedUrl(), sp.getType().toCode(), type) && ok;
5051            }
5052          }
5053          iSP++;
5054        }
5055        iResource++;
5056      }
5057      iRest++;
5058    }
5059    return ok;
5060  }
5061 
5062  private boolean validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path,
5063                                ElementDefinition child, ElementDefinition context, Element resource,
5064                                Element element, NodeStack stack, IdStatus idstatus, StructureDefinition parentProfile, PercentageTracker pct, ValidationMode mode) throws FHIRException {
5065    boolean ok = true;
5066
5067    if (element.isNull()) {
5068      if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), ToolingExtensions.readBooleanExtension(child, ToolingExtensions.EXT_JSON_NULLABLE),
5069          I18nConstants.ELEMENT_CANNOT_BE_NULL)) {
5070        // nothing else to validate?
5071      } else {
5072        ok = false;
5073      }
5074
5075    } else {
5076      SpecialElement special = element.getSpecial();
5077
5078      ContainedReferenceValidationPolicy containedValidationPolicy = getPolicyAdvisor() == null ?
5079          ContainedReferenceValidationPolicy.CHECK_VALID : getPolicyAdvisor().policyForContained(this,
5080              hostContext, context.fhirType(), context.getId(), special, path, parentProfile.getUrl());
5081
5082      if (containedValidationPolicy.ignore()) {
5083        return ok;
5084      }
5085
5086      String resourceName = element.getType();
5087      TypeRefComponent typeForResource = null;
5088      CommaSeparatedStringBuilder bt = new CommaSeparatedStringBuilder();
5089
5090      // Iterate through all possible types
5091      for (TypeRefComponent type : child.getType()) {
5092        bt.append(type.getCode());
5093        if (type.getCode().equals("Resource") || type.getCode().equals(resourceName) ) {
5094          typeForResource = type;
5095          break;
5096        }
5097      }
5098
5099      stack.qualifyPath(".ofType("+resourceName+")");
5100
5101      if (typeForResource == null) {
5102        ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
5103            false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName, bt.toString()) && ok;
5104      } else if (isValidResourceType(resourceName, typeForResource)) {
5105        if (containedValidationPolicy.checkValid()) {
5106          // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
5107          ValidatorHostContext hc = null;
5108          if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) {
5109            resource = element;
5110            assert Utilities.existsInList(hostContext.getResource().fhirType(), "Bundle", "Parameters") : "Containing Resource is "+hostContext.getResource().fhirType()+", expected Bundle or Parameters at "+stack.getLiteralPath();
5111            hc = hostContext.forEntry(element, hostContext.getResource()); // root becomes the grouping resource (should be either bundle or parameters)
5112          } else {
5113            hc = hostContext.forContained(element);
5114          }
5115
5116          stack.resetIds();
5117          if (special != null) {
5118            switch (special) {
5119            case BUNDLE_ENTRY:
5120            case BUNDLE_OUTCOME:
5121            case PARAMETER:
5122              idstatus = IdStatus.OPTIONAL;
5123              break;
5124            case CONTAINED:
5125              stack.setContained(true);
5126              idstatus = IdStatus.REQUIRED;
5127              break;
5128            default:
5129              break;
5130            }
5131          }
5132
5133          if (typeForResource.getProfile().size() == 1) {
5134            long t = System.nanoTime();
5135            StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, typeForResource.getProfile().get(0).asStringValue(), parentProfile);
5136            timeTracker.sd(t);
5137            trackUsage(profile, hostContext, element);
5138            if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
5139                profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL, special.toHuman(), resourceName, typeForResource.getProfile().get(0).asStringValue())) {
5140              ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode) && ok;
5141            } else {
5142              ok = false;
5143            }
5144          } else if (typeForResource.getProfile().isEmpty()) {
5145            long t = System.nanoTime();
5146            StructureDefinition profile = this.context.fetchResource(StructureDefinition.class,
5147                "http://hl7.org/fhir/StructureDefinition/" + resourceName);
5148            timeTracker.sd(t);
5149            trackUsage(profile, hostContext, element);
5150            if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
5151                profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), resourceName)) {
5152              ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode) && ok;
5153            } else {
5154              ok = false;
5155            }
5156          } else {
5157            CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
5158            for (CanonicalType u : typeForResource.getProfile()) {
5159              b.append(u.asStringValue());
5160            }
5161            ok = rulePlural(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
5162                false, typeForResource.getProfile().size(), I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, special.toHuman(), typeForResource.getCode(), b.toString()) && ok;
5163          }
5164        }
5165      } else {
5166        List<String> types = new ArrayList<>();
5167        for (UriType u : typeForResource.getProfile()) {
5168          StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue(), parentProfile);
5169          if (sd != null && !types.contains(sd.getType())) {
5170            types.add(sd.getType());
5171          }
5172        }
5173        if (types.size() == 1) {
5174          ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
5175              false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE2, resourceName, types.get(0)) && ok;
5176          
5177        } else {
5178          ok = rulePlural(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
5179              false, types.size(), I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE3, resourceName, types) && ok;
5180        }
5181      }
5182    }
5183    return ok;
5184  }
5185
5186  private boolean isValidResourceType(String type, TypeRefComponent def) {
5187    if (!def.hasProfile() && def.getCode().equals("Resource")) {
5188      return true;
5189    }
5190    if (def.getCode().equals(type)) {
5191      return true;
5192    }
5193    List<StructureDefinition> list = new ArrayList<>();
5194    for (UriType u : def.getProfile()) {
5195      StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue());
5196      if (sdt != null) {
5197        list.add(sdt);
5198      }
5199    }
5200
5201    StructureDefinition sdt = context.fetchTypeDefinition(type);
5202    while (sdt != null) {
5203      if (def.getWorkingCode().equals("Resource")) {
5204        for (StructureDefinition sd : list) {
5205          if (sd.getUrl().equals(sdt.getUrl())) {
5206            return true;
5207          }
5208          if (sd.getType().equals(sdt.getType())) {
5209            return true;
5210          }
5211        }
5212      }
5213      sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition(), sdt);
5214    }
5215    return false;
5216  }
5217
5218
5219  private boolean validateElement(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
5220    Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl, PercentageTracker pct, ValidationMode mode) throws FHIRException {
5221
5222    pct.seeElement(element);
5223    
5224    String id = element.getChildValue("id");
5225    if (!Utilities.noString(id)) {
5226      if (stack.getIds().containsKey(id) && stack.getIds().get(id) != element) {
5227        rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id);
5228      }
5229      if (!stack.isResetPoint()) {
5230        stack.getIds().put(id, element);
5231      }
5232    }
5233    if (definition.getPath().equals("StructureDefinition.snapshot")) {
5234      // work around a known issue in the spec, that ids are duplicated in snapshot and differential 
5235      stack.resetIds();
5236    }
5237    ValidationInfo vi = element.addDefinition(profile, definition, mode);
5238    boolean ok = true;
5239    
5240    // check type invariants
5241    ok = checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false) & ok;
5242    if (definition.getFixed() != null) {
5243      ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getVersionedUrl(), definition.getSliceName(), null, false) && ok;
5244    } 
5245    if (definition.getPattern() != null) {
5246      ok = checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getVersionedUrl(), definition.getSliceName(), null, true) && ok;
5247    }
5248    
5249    // get the list of direct defined children, including slices
5250    SourcedChildDefinitions childDefinitions = profileUtilities.getChildMap(profile, definition);
5251    if (childDefinitions.getList().isEmpty()) {
5252      if (actualType == null) {
5253        vi.setValid(false);
5254        return false; // there'll be an error elsewhere in this case, and we're going to stop.
5255      }
5256      childDefinitions = getActualTypeChildren(hostContext, element, actualType);
5257    } else if (definition.getType().size() > 1) {
5258      // this only happens when the profile constrains the abstract children but leaves th choice open.
5259      if (actualType == null) {
5260        vi.setValid(false);
5261        return false; // there'll be an error elsewhere in this case, and we're going to stop.
5262      }
5263      SourcedChildDefinitions typeChildDefinitions = getActualTypeChildren(hostContext, element, actualType);
5264      // 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)
5265      childDefinitions = mergeChildLists(childDefinitions, typeChildDefinitions, definition.getPath(), actualType);
5266    }
5267
5268    List<ElementInfo> children = listChildren(element, stack);
5269    List<String> problematicPaths = assignChildren(hostContext, errors, profile, resource, stack, childDefinitions, children);
5270
5271    ok = checkCardinalities(errors, profile, element, stack, childDefinitions, children, problematicPaths) && ok;
5272    // 4. check order if any slices are ordered. (todo)
5273        
5274    // 5. inspect each child for validity
5275    for (ElementInfo ei : children) {
5276      ok = checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, pct, mode) && ok;
5277    }
5278    vi.setValid(ok);
5279    return ok;
5280  }
5281
5282  private SourcedChildDefinitions mergeChildLists(SourcedChildDefinitions source, SourcedChildDefinitions additional, String masterPath, String typePath) {
5283    SourcedChildDefinitions res = new SourcedChildDefinitions(additional.getSource(), new ArrayList<>());
5284    res.getList().addAll(source.getList());
5285    for (ElementDefinition ed : additional.getList()) {
5286      boolean inMaster = false;
5287      for (ElementDefinition t : source.getList()) {
5288        String tp = masterPath + ed.getPath().substring(typePath.length());
5289        if (t.getPath().equals(tp)) {
5290          inMaster = true;
5291        }
5292      }
5293      if (!inMaster) {
5294        res.getList().add(ed);
5295      }
5296    }
5297    return res;
5298  }
5299
5300  // todo: the element definition in context might assign a constrained profile for the type?
5301  public SourcedChildDefinitions getActualTypeChildren(ValidatorHostContext hostContext, Element element, String actualType) {
5302    SourcedChildDefinitions childDefinitions;
5303    StructureDefinition dt = null;
5304    if (isAbsolute(actualType))
5305      dt = this.context.fetchResource(StructureDefinition.class, actualType);
5306    else
5307      dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType);
5308    if (dt == null)
5309      throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType));
5310    trackUsage(dt, hostContext, element);
5311
5312    childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0));
5313    return childDefinitions;
5314  }
5315
5316  public boolean checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition,
5317    Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, PercentageTracker pct, ValidationMode mode)
5318    throws FHIRException, DefinitionException {
5319    boolean ok = true;
5320    
5321    if (debug && ei.definition != null && ei.slice != null) {
5322      System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against both "+ei.definition.getId()+" and "+ei.slice.getId());
5323    }
5324    if (ei.definition != null) {
5325      if (debug) {
5326        System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against defn "+ei.definition.getId()+" from "+profile.getVersionedUrl()+time());
5327      }
5328      ok = checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.definition, false, pct, mode) && ok;
5329    }
5330    if (ei.slice != null) {
5331      if (debug) {
5332        System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against slice "+ei.slice.getId()+time());
5333      }
5334      ok = checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true, pct, mode) && ok;
5335    }
5336    return ok;
5337  }
5338
5339  private String time() {
5340    long t = System.currentTimeMillis();
5341    String s = " "+(t - start)+"ms";
5342    start = t;
5343    return s; 
5344  }
5345
5346  public boolean checkChildByDefinition(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile,
5347      ElementDefinition definition, Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept,
5348      boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, ElementDefinition checkDefn, boolean isSlice, PercentageTracker pct, ValidationMode mode) {
5349    boolean ok = true;
5350    List<String> profiles = new ArrayList<String>();
5351    String type = null;
5352    ElementDefinition typeDefn = null;
5353    checkMustSupport(profile, ei);
5354    long s = System.currentTimeMillis();
5355
5356    if (checkDefn.getType().size() == 1 && !"*".equals(checkDefn.getType().get(0).getWorkingCode()) && !"Element".equals(checkDefn.getType().get(0).getWorkingCode())
5357      && !"BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode())) {
5358      type = checkDefn.getType().get(0).getWorkingCode();
5359      String stype = ei.getElement().fhirType();
5360      if (!stype.equals(type)) {
5361        if (checkDefn.isChoice()) {
5362          if (extensionUrl != null && !isAbsolute(extensionUrl)) {
5363            ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), false, I18nConstants.EXTENSION_PROF_TYPE, profile.getVersionedUrl(), type, stype) && ok;
5364          } else if (!isAbstractType(type) && !"Extension".equals(profile.getType())) {
5365            ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), stype.equals(type), I18nConstants.EXTENSION_PROF_TYPE, profile.getVersionedUrl(), type, stype) && ok;                  
5366         }
5367        } else if (!isAbstractType(type)) {
5368          ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), stype.equals(type) || 
5369            (Utilities.existsInList(type, "string", "id") && Utilities.existsInList(stype, "string", "id")), // work around a r4 problem with id/string
5370            I18nConstants.EXTENSION_PROF_TYPE, profile.getVersionedUrl(), type, stype) && ok;
5371        } else if (!isResource(type)) {
5372//          System.out.println("update type "+type+" to "+stype+"?");
5373          type = stype;
5374        } else {
5375          // this will be sorted out in contains ... System.out.println("update type "+type+" to "+stype+"?");
5376        }
5377      }
5378
5379      // Excluding reference is a kludge to get around versioning issues
5380      if (checkDefn.getType().get(0).hasProfile()) {
5381        for (CanonicalType p : checkDefn.getType().get(0).getProfile()) {
5382          profiles.add(p.getValue());
5383        }
5384      }
5385    } else if (checkDefn.getType().size() == 1 && "*".equals(checkDefn.getType().get(0).getWorkingCode())) {
5386      String prefix = tail(checkDefn.getPath());
5387      assert prefix.endsWith("[x]");
5388      type = ei.getName().substring(prefix.length() - 3);
5389      if (isPrimitiveType(type))
5390        type = Utilities.uncapitalize(type);
5391      if (checkDefn.getType().get(0).hasProfile()) {
5392        for (CanonicalType p : checkDefn.getType().get(0).getProfile()) {
5393          profiles.add(p.getValue());
5394        }
5395      }
5396    } else if (checkDefn.getType().size() > 1) {
5397
5398      String prefix = tail(checkDefn.getPath());
5399      assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB();
5400
5401      if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
5402        type = ei.getElement().getType();
5403      } else if (ei.getElement().isResource()) {
5404        type = ei.getElement().fhirType();            
5405      } else {
5406        prefix = prefix.substring(0, prefix.length() - 3);
5407        for (TypeRefComponent t : checkDefn.getType())
5408          if ((prefix + Utilities.capitalize(t.getWorkingCode())).equals(ei.getName())) {
5409            type = t.getWorkingCode();
5410            // Excluding reference is a kludge to get around versioning issues
5411            if (t.hasProfile() && !type.equals("Reference"))
5412              profiles.add(t.getProfile().get(0).getValue());
5413          }
5414      }
5415      if (type == null) {
5416        TypeRefComponent trc = checkDefn.getType().get(0);
5417        if (trc.getWorkingCode().equals("Reference"))
5418          type = "Reference";
5419        else
5420          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;
5421      }
5422    } else if (checkDefn.getContentReference() != null) {
5423      typeDefn = resolveNameReference(profile.getSnapshot(), checkDefn.getContentReference());
5424      
5425    } else if (checkDefn.getType().size() == 1 && ("Element".equals(checkDefn.getType().get(0).getWorkingCode()) || "BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode()))) {
5426      if (checkDefn.getType().get(0).hasProfile()) {
5427        CanonicalType pu = checkDefn.getType().get(0).getProfile().get(0);
5428        if (pu.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT))
5429          profiles.add(pu.getValue() + "#" + pu.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT));
5430        else
5431          profiles.add(pu.getValue());
5432      }
5433    }
5434
5435    if (type != null) {
5436      if (type.startsWith("@")) {
5437        checkDefn = findElement(profile, type.substring(1));
5438        if (isSlice) {
5439          ei.slice = ei.definition;
5440        } else {
5441          ei.definition = ei.definition;            
5442        }
5443        type = null;
5444      }
5445    }
5446    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()));
5447    if (debug) {
5448      System.out.println("  check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getVersionedUrl()+time());
5449    }
5450    String localStackLiteralPath = localStack.getLiteralPath();
5451    String eiPath = ei.getPath();
5452    if (!eiPath.equals(localStackLiteralPath)) {
5453      assert (eiPath.equals(localStackLiteralPath)) : "ei.path: " + ei.getPath() + "  -  localStack.getLiteralPath: " + localStackLiteralPath;
5454    }
5455    boolean thisIsCodeableConcept = false;
5456    String thisExtension = null;
5457    boolean checkDisplay = true;
5458
5459    SpecialElement special = ei.getElement().getSpecial();
5460    if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) {
5461      ok = checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, ei.getElement(), ei.getElement(), localStack, false) && ok;
5462    } else {
5463      ok = checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, resource, ei.getElement(), localStack, false) && ok;
5464    }
5465
5466    ei.getElement().markValidation(profile, checkDefn);
5467    boolean elementValidated = false;
5468    if (type != null) {
5469      if (isPrimitiveType(type)) {
5470        ok = checkPrimitive(hostContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, stack) && ok;
5471      } else {
5472        if (checkDefn.hasFixed()) {
5473          ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, false) && ok;
5474        }
5475        if (checkDefn.hasPattern()) {
5476          ok = checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getVersionedUrl(), checkDefn.getSliceName(), null, true) && ok;
5477        }
5478      }
5479      if (type.equals("Identifier")) {
5480        ok = checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn);
5481      } else if (type.equals("Coding")) {
5482        ok = checkCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack);
5483      } else if (type.equals("Quantity")) {
5484        ok = checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack);
5485      } else if (type.equals("Attachment")) {
5486        ok = checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack);
5487      } else if (type.equals("CodeableConcept")) {
5488        ok = checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack);
5489        thisIsCodeableConcept = true;
5490      } else if (type.equals("Reference")) {
5491        ok = checkReference(hostContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, actualType, localStack, pct, mode) && ok;
5492        // 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
5493      } else if (type.equals("Extension")) {
5494        Element eurl = ei.getElement().getNamedChild("url");
5495        if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), eurl != null, I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) {
5496          String url = eurl.primitiveValue();
5497          thisExtension = url;
5498          if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), !Utilities.noString(url), I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) {
5499            if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.getPath(), (extensionUrl != null) || Utilities.isAbsoluteUrl(url), I18nConstants.EXTENSION_EXT_URL_ABSOLUTE)) {
5500              checkExtension(hostContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl, pct, mode);
5501            } else {
5502              ok = false;
5503            }
5504          } else {
5505            ok = false;
5506          }
5507        }
5508      } else if (type.equals("Resource") || isResource(type)) {
5509        ok = validateContains(hostContext, errors, ei.getPath(), checkDefn, definition, resource, ei.getElement(),
5510          localStack, idStatusForEntry(element, ei), profile, pct, mode) && ok; // if
5511        elementValidated = true;
5512        // (str.matches(".*([.,/])work\\1$"))
5513      } else if (Utilities.isAbsoluteUrl(type)) {
5514        StructureDefinition defn = context.fetchTypeDefinition(type);
5515        if (defn != null && hasMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep())) {
5516          List<String> txtype = getMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep());
5517          if (txtype.contains("CodeableConcept")) {
5518            ok = checkTerminologyCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack, defn) && ok;
5519            thisIsCodeableConcept = true;
5520          } else if (txtype.contains("Coding")) {
5521            ok = checkTerminologyCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack, defn) && ok;
5522          }
5523        }
5524      }
5525    } else {
5526      if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), checkDefn != null, I18nConstants.VALIDATION_VAL_CONTENT_UNKNOWN, ei.getName())) {
5527        ok = validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, false, true, null, pct, mode) && ok;
5528      } else {
5529        ok = false;
5530      }
5531    }
5532    StructureDefinition p = null;
5533    String tail = null;
5534    if (profiles.isEmpty()) {
5535      if (type != null) {
5536        p = getProfileForType(type, checkDefn.getType(), profile);
5537
5538        // If dealing with a primitive type, then we need to check the current child against
5539        // the invariants (constraints) on the current element, because otherwise it only gets
5540        // checked against the primary type's invariants: LLoyd
5541        //if (p.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
5542        //  checkInvariants(hostContext, errors, ei.path, profile, ei.definition, null, null, resource, ei.element);
5543        //}
5544
5545        ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_NOTYPE, type) && ok;
5546      }
5547    } else if (profiles.size() == 1) {
5548      String url = profiles.get(0);
5549      if (url.contains("#")) {
5550        tail = url.substring(url.indexOf("#") + 1);
5551        url = url.substring(0, url.indexOf("#"));
5552      }
5553      p = this.context.fetchResource(StructureDefinition.class, url, profile);
5554      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;
5555    } else {
5556      elementValidated = true;
5557      HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>();
5558      HashMap<String, List<ValidationMessage>> badProfiles = new HashMap<String, List<ValidationMessage>>();
5559      for (String typeProfile : profiles) {
5560        String url = typeProfile;
5561        tail = null;
5562        if (url.contains("#")) {
5563          tail = url.substring(url.indexOf("#") + 1);
5564          url = url.substring(0, url.indexOf("#"));
5565        }
5566        p = this.context.fetchResource(StructureDefinition.class, typeProfile);
5567        if (rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, typeProfile)) {
5568          List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
5569          ok = validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension, pct, mode) && ok;
5570          if (hasErrors(profileErrors))
5571            badProfiles.put(typeProfile, profileErrors);
5572          else
5573            goodProfiles.put(typeProfile, profileErrors);
5574        }
5575      }
5576      if (goodProfiles.size() == 1) {
5577        errors.addAll(goodProfiles.values().iterator().next());
5578      } else if (goodProfiles.size() == 0) {
5579        ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOMATCH, StringUtils.join("; ", profiles)) && ok;
5580        for (String m : badProfiles.keySet()) {
5581          p = this.context.fetchResource(StructureDefinition.class, m);
5582          for (ValidationMessage message : badProfiles.get(m)) {
5583            message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])");
5584            errors.add(message);
5585          }
5586        }
5587      } else {
5588        warningPlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, goodProfiles.size(), I18nConstants.VALIDATION_VAL_PROFILE_MULTIPLEMATCHES, ResourceUtilities.listStrings(goodProfiles.keySet()));
5589        for (String m : goodProfiles.keySet()) {
5590          p = this.context.fetchResource(StructureDefinition.class, m);
5591          for (ValidationMessage message : goodProfiles.get(m)) {
5592            message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])");
5593            errors.add(message);
5594          }
5595        }
5596      }
5597    }
5598    if (p != null) {
5599      trackUsage(p, hostContext, element);
5600
5601      if (!elementValidated) {
5602        if (ei.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || ei.getElement().getSpecial() == SpecialElement.BUNDLE_OUTCOME || ei.getElement().getSpecial() == SpecialElement.PARAMETER)
5603          ok = validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, ei.getElement(), ei.getElement(), type, localStack.resetIds(), thisIsCodeableConcept, checkDisplay, thisExtension, pct, mode) && ok;
5604        else
5605          ok = validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension, pct, mode) && ok;
5606      }
5607      int index = profile.getSnapshot().getElement().indexOf(checkDefn);
5608      if (index < profile.getSnapshot().getElement().size() - 1) {
5609        String nextPath = profile.getSnapshot().getElement().get(index + 1).getPath();
5610        if (!nextPath.equals(checkDefn.getPath()) && nextPath.startsWith(checkDefn.getPath()))
5611          ok = validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension, pct, mode) && ok;
5612      }
5613    }
5614    return ok;
5615  }
5616
5617  private boolean isAbstractType(String type) {
5618    StructureDefinition sd = context.fetchTypeDefinition(type);
5619    return sd != null && sd.getAbstract();
5620  }
5621
5622  private boolean isResourceAndTypes(ElementDefinition ed) {
5623    if (!Utilities.existsInList(ed.getBase().getPath(), "Bundle.entry.resource", "Bundle.entry.response.outcome", "DomainResource.contained", "Parameters.parameter.resource", "Parameters.parameter.part.resource")) {
5624      return false;
5625    }
5626    for (TypeRefComponent tr : ed.getType()) {
5627      if (!isResource(tr.getCode())) {
5628        return false;
5629      }
5630    }
5631    return true;
5632  }
5633
5634  private boolean isResource(String type) {
5635    StructureDefinition sd = context.fetchTypeDefinition(type);
5636    return sd != null && sd.getKind().equals(StructureDefinitionKind.RESOURCE);
5637  }
5638
5639  private void trackUsage(StructureDefinition profile, ValidatorHostContext hostContext, Element element) {
5640    if (tracker != null) {
5641      tracker.recordProfileUsage(profile, hostContext.getAppContext(), element);
5642    }
5643  }
5644
5645  private boolean hasMapping(String url, StructureDefinition defn, ElementDefinition elem) {
5646    String id = null;
5647    for (StructureDefinitionMappingComponent m : defn.getMapping()) {
5648      if (url.equals(m.getUri())) {
5649        id = m.getIdentity();
5650        break;
5651      }
5652    }
5653    if (id != null) {
5654      for (ElementDefinitionMappingComponent m : elem.getMapping()) {
5655        if (id.equals(m.getIdentity())) {
5656          return true;
5657        }
5658      }
5659
5660    }
5661    return false;
5662  }
5663
5664  private List<String> getMapping(String url, StructureDefinition defn, ElementDefinition elem) {
5665    List<String> res = new ArrayList<>();
5666    String id = null;
5667    for (StructureDefinitionMappingComponent m : defn.getMapping()) {
5668      if (url.equals(m.getUri())) {
5669        id = m.getIdentity();
5670        break;
5671      }
5672    }
5673    if (id != null) {
5674      for (ElementDefinitionMappingComponent m : elem.getMapping()) {
5675        if (id.equals(m.getIdentity())) {
5676          res.add(m.getMap());
5677        }
5678      }
5679    }
5680    return res;
5681  }
5682
5683  public void checkMustSupport(StructureDefinition profile, ElementInfo ei) {
5684    String usesMustSupport = profile.getUserString("usesMustSupport");
5685    if (usesMustSupport == null) {
5686      usesMustSupport = "N";
5687      for (ElementDefinition pe : profile.getSnapshot().getElement()) {
5688        if (pe.getMustSupport()) {
5689          usesMustSupport = "Y";
5690          break;
5691        }
5692      }
5693      profile.setUserData("usesMustSupport", usesMustSupport);
5694    }
5695    if (usesMustSupport.equals("Y")) {
5696      String elementSupported = ei.getElement().getUserString("elementSupported");
5697      if (elementSupported == null || ei.definition.getMustSupport())
5698        if (ei.definition.getMustSupport()) {
5699          ei.getElement().setUserData("elementSupported", "Y");
5700        }
5701    }
5702  }
5703
5704  public boolean checkCardinalities(List<ValidationMessage> errors, StructureDefinition profile, Element element, NodeStack stack,
5705      SourcedChildDefinitions childDefinitions, List<ElementInfo> children, List<String> problematicPaths) throws DefinitionException {
5706    boolean ok = true;
5707    // 3. report any definitions that have a cardinality problem
5708    for (ElementDefinition ed : childDefinitions.getList()) {
5709      if (ed.getRepresentation().isEmpty()) { // ignore xml attributes
5710        int count = 0;
5711        List<ElementDefinition> slices = null;
5712        if (ed.hasSlicing())
5713          slices = profileUtilities.getSliceList(profile, ed);
5714        for (ElementInfo ei : children)
5715          if (ei.definition == ed)
5716            count++;
5717          else if (slices != null) {
5718            for (ElementDefinition sed : slices) {
5719              if (ei.definition == sed) {
5720                count++;
5721                break;
5722              }
5723            }
5724          }
5725        if (ed.getMin() > 0) {
5726          if (problematicPaths.contains(ed.getPath()))
5727            hintPlural(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), count, I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMIN, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin()));
5728          else {
5729            if (count < ed.getMin()) {
5730              ok = rulePlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, count, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())) && ok;
5731            }
5732          }
5733        }
5734        if (ed.hasMax() && !ed.getMax().equals("*")) {
5735          if (problematicPaths.contains(ed.getPath()))
5736            hintPlural(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), count, I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMAX, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), ed.getMax());
5737          else if (count > Integer.parseInt(ed.getMax())) {
5738            ok = rulePlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, count, I18nConstants.VALIDATION_VAL_PROFILE_MAXIMUM, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), ed.getMax(), Integer.toString(count)) && ok;
5739          }
5740        }
5741      }
5742    }
5743    return ok;
5744  }
5745
5746  public List<String> assignChildren(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, Element resource,
5747    NodeStack stack, SourcedChildDefinitions childDefinitions, List<ElementInfo> children) throws DefinitionException {
5748    // 2. assign children to a definition
5749    // for each definition, for each child, check whether it belongs in the slice
5750    ElementDefinition slicer = null;
5751    boolean unsupportedSlicing = false;
5752    List<String> problematicPaths = new ArrayList<String>();
5753    String slicingPath = null;
5754    int sliceOffset = 0;
5755    for (int i = 0; i < childDefinitions.getList().size(); i++) {
5756      ElementDefinition ed = childDefinitions.getList().get(i);
5757      boolean childUnsupportedSlicing = false;
5758      boolean process = true;
5759      if (ed.hasSlicing() && !ed.getSlicing().getOrdered()) {
5760        slicingPath = ed.getPath();
5761      } else if (slicingPath != null && ed.getPath().equals(slicingPath)) {
5762        ; // nothing
5763      } else if (slicingPath != null && !ed.getPath().startsWith(slicingPath)) {
5764        slicingPath = null;
5765      }
5766      // where are we with slicing
5767      if (ed.hasSlicing()) {
5768        if (slicer != null && slicer.getPath().equals(ed.getPath())) {
5769          String errorContext = "profile " + profile.getVersionedUrl();
5770          if (!resource.getChildValue(ID).isEmpty()) {
5771            errorContext += "; instance " + resource.getChildValue("id");
5772          }
5773          throw new DefinitionException(context.formatMessage(I18nConstants.SLICE_ENCOUNTERED_MIDWAY_THROUGH_SET_PATH___ID___, slicer.getPath(), slicer.getId(), errorContext));
5774        }
5775        slicer = ed;
5776        process = false;
5777        sliceOffset = i;
5778      } else if (slicer != null && !slicer.getPath().equals(ed.getPath()))
5779        slicer = null;
5780
5781      for (ElementInfo ei : children) {
5782        if (ei.sliceInfo == null) {
5783          ei.sliceInfo = new ArrayList<>();
5784        }
5785        unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei);
5786      }
5787    }
5788    int last = -1;
5789    ElementInfo lastei = null;
5790    int lastSlice = -1;
5791    for (ElementInfo ei : children) {
5792      String sliceInfo = "";
5793      if (slicer != null) {
5794        sliceInfo = " (slice: " + slicer.getPath() + ")";
5795      }
5796      if (!unsupportedSlicing) {
5797        if (ei.additionalSlice && ei.definition != null) {
5798          if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) ||
5799            ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
5800            slicingHint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo), 
5801              context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
5802                profile == null ? "" : "defined in the profile " + profile.getVersionedUrl()),
5803              context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : context.formatMessage(I18nConstants.DEFINED_IN_THE_PROFILE) + " "+profile.getVersionedUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo),
5804              errorSummaryForSlicingAsText(ei.sliceInfo));
5805          } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) {
5806            rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo));
5807          }
5808        } else {
5809          // Don't raise this if we're in an abstract profile, like Resource
5810          if (!childDefinitions.getSource().getAbstract()) {
5811            rule(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.getPath(), (ei.definition != null), I18nConstants.VALIDATION_VAL_PROFILE_NOTALLOWED, profile.getVersionedUrl());
5812          }
5813        }
5814      }
5815      // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements
5816      boolean isXmlAttr = false;
5817      if (ei.definition != null) {
5818        for (Enumeration<PropertyRepresentation> r : ei.definition.getRepresentation()) {
5819          if (r.getValue() == PropertyRepresentation.XMLATTR) {
5820            isXmlAttr = true;
5821            break;
5822          }
5823        }
5824      }
5825
5826      if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) {
5827        boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr;
5828        rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getVersionedUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName());
5829      }
5830      if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) {
5831        rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, I18nConstants.VALIDATION_VAL_PROFILE_SLICEORDER, profile.getVersionedUrl(), ei.getName());
5832      }
5833      if (ei.definition == null || !isXmlAttr) {
5834        last = ei.index;
5835        lastei = ei;
5836      }
5837      if (ei.slice != null) {
5838        lastSlice = ei.sliceindex;
5839      } else {
5840        lastSlice = -1;
5841      }
5842    }
5843    return problematicPaths;
5844  }
5845
5846
5847  public List<ElementInfo> listChildren(Element element, NodeStack stack) {
5848    // 1. List the children, and remember their exact path (convenience)
5849    List<ElementInfo> children = new ArrayList<ElementInfo>();
5850    ChildIterator iter = new ChildIterator(this, stack.getLiteralPath(), element);
5851    while (iter.next()) {
5852      children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count()));
5853    }
5854    return children;
5855  }
5856
5857  public boolean checkInvariants(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, Element resource, Element element, NodeStack stack, boolean onlyNonInherited) throws FHIRException {
5858    return checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited);
5859  }
5860
5861  public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack,
5862    ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed,
5863    boolean childUnsupportedSlicing, ElementInfo ei) {
5864    boolean match = false;
5865    if (slicer == null || slicer == ed) {
5866      match = nameMatches(ei.getName(), tail(ed.getPath()));
5867    } else {
5868      if (nameMatches(ei.getName(), tail(ed.getPath())))
5869        try {
5870//          System.out.println("match slices for "+stack.getLiteralPath()+": "+slicer.getId()+" = "+slicingSummary(slicer.getSlicing()));
5871          match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile);
5872          if (match) {
5873            ei.slice = slicer;
5874
5875            // Since a defined slice was found, this is not an additional (undefined) slice.
5876            ei.additionalSlice = false;
5877          } else if (ei.slice == null) {
5878            // if the specified slice is undefined, keep track of the fact this is an additional (undefined) slice, but only if a slice wasn't found previously
5879            ei.additionalSlice = true;
5880          }
5881        } catch (FHIRException e) {
5882          rule(errors, NO_RULE_DATE, IssueType.PROCESSING, ei.line(), ei.col(), ei.getPath(), false,  I18nConstants.SLICING_CANNOT_BE_EVALUATED, e.getMessage());
5883          unsupportedSlicing = true;
5884          childUnsupportedSlicing = true;
5885        }
5886    }
5887    if (match) {
5888      boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", "")));
5889      if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), isOk, I18nConstants.VALIDATION_VAL_PROFILE_MATCHMULTIPLE, profile.getVersionedUrl(), (ei.definition == null || !ei.definition.hasSliceName() ? "" : ei.definition.getSliceName()), (ed.hasSliceName() ? ed.getSliceName() : ""))) {
5890        ei.definition = ed;
5891        if (ei.slice == null) {
5892          ei.index = i;
5893        } else {
5894          ei.index = sliceOffset;
5895          ei.sliceindex = i - (sliceOffset + 1);
5896        }
5897      }
5898    } else if (childUnsupportedSlicing) {
5899      problematicPaths.add(ed.getPath());
5900    }
5901    return unsupportedSlicing;
5902  }
5903
5904  private String slicingSummary(ElementDefinitionSlicingComponent slicing) {
5905    StringBuilder b = new StringBuilder();
5906    b.append('[');
5907    boolean first = true;
5908    for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) {
5909      if (first) first = false; else b.append(","); 
5910      b.append(t.getType().toCode());
5911      b.append(":");
5912      b.append(t.getPath());
5913    }
5914    b.append(']');
5915    b.append(slicing.getOrdered() ? ";ordered" : "");
5916    b.append(slicing.getRules().toString());
5917    return b.toString();
5918  }
5919
5920  private ElementDefinition getElementByTail(StructureDefinition p, String tail) throws DefinitionException {
5921    if (tail == null)
5922      return p.getSnapshot().getElement().get(0);
5923    for (ElementDefinition t : p.getSnapshot().getElement()) {
5924      if (tail.equals(t.getId()))
5925        return t;
5926    }
5927    throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_WITH_ID_, tail));
5928  }
5929
5930  private IdStatus idStatusForEntry(Element ep, ElementInfo ei) {
5931    if (ei.getDefinition().hasExtension(ToolingExtensions.EXT_ID_EXPECTATION)) {
5932      return IdStatus.fromCode(ToolingExtensions.readStringExtension(ei.getDefinition(),ToolingExtensions.EXT_ID_EXPECTATION));
5933    } else if (isBundleEntry(ei.getPath())) {
5934      Element req = ep.getNamedChild("request");
5935      Element resp = ep.getNamedChild("response");
5936      Element fullUrl = ep.getNamedChild(FULL_URL);
5937      Element method = null;
5938      Element url = null;
5939      if (req != null) {
5940        method = req.getNamedChild("method");
5941        url = req.getNamedChild("url");
5942      }
5943      if (resp != null) {
5944        return IdStatus.OPTIONAL;
5945      }
5946      if (method == null) {
5947        if (fullUrl == null)
5948          return IdStatus.REQUIRED;
5949        else if (fullUrl.primitiveValue().startsWith("urn:uuid:") || fullUrl.primitiveValue().startsWith("urn:oid:"))
5950          return IdStatus.OPTIONAL;
5951        else
5952          return IdStatus.REQUIRED;
5953      } else {
5954        String s = method.primitiveValue();
5955        if (s.equals("PUT")) {
5956          if (url == null)
5957            return IdStatus.REQUIRED;
5958          else
5959            return IdStatus.OPTIONAL; // or maybe prohibited? not clear
5960        } else if (s.equals("POST"))
5961          return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102
5962        else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource
5963          return IdStatus.OPTIONAL;
5964      }
5965    } else if (isParametersEntry(ei.getPath()) || isBundleOutcome(ei.getPath()))
5966      return IdStatus.OPTIONAL;
5967    else
5968      return IdStatus.REQUIRED;
5969  }
5970
5971  private boolean checkInvariants(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element, boolean onlyNonInherited) throws FHIRException, FHIRException {
5972    if (noInvariantChecks)
5973      return true;
5974
5975    boolean ok = true;
5976    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
5977      if (inv.hasExpression() && (!onlyNonInherited || !inv.hasSource() || (!isInheritedProfile(profile, inv.getSource()) && !isInheritedProfile(ed.getType(), inv.getSource())) )) {
5978        @SuppressWarnings("unchecked")
5979        Map<String, List<ValidationMessage>> invMap = executionId.equals(element.getUserString(EXECUTION_ID)) ? (Map<String, List<ValidationMessage>>) element.getUserData(EXECUTED_CONSTRAINT_LIST) : null;
5980        if (invMap == null) {
5981          invMap = new HashMap<>();
5982          element.setUserData(EXECUTED_CONSTRAINT_LIST, invMap);
5983          element.setUserData(EXECUTION_ID, executionId);
5984        }
5985        List<ValidationMessage> invErrors = null;
5986        // We key based on inv.expression rather than inv.key because expressions can change in derived profiles and aren't guaranteed to be consistent across profiles.
5987        String key = FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey());
5988        if (!invMap.keySet().contains(key)) {
5989          invErrors = new ArrayList<ValidationMessage>();
5990          invMap.put(key, invErrors);
5991          ok = checkInvariant(hostContext, invErrors, path, profile, resource, element, inv) && ok;
5992        } else {
5993          invErrors = (ArrayList<ValidationMessage>)invMap.get(key);
5994        }
5995        errors.addAll(invErrors);
5996      }
5997    }
5998    return ok;
5999  }
6000
6001  private boolean isInheritedProfile(List<TypeRefComponent> types, String source) {
6002    for (TypeRefComponent type : types) {
6003      for (CanonicalType c : type.getProfile()) {
6004        StructureDefinition sd = context.fetchResource(StructureDefinition.class, c.asStringValue());
6005        if (sd != null) {
6006          if (sd.getUrl().equals(source)) {
6007            return true;
6008          }
6009          if (isInheritedProfile(sd, source)) {
6010            return true;
6011          }
6012        }
6013      }
6014    }
6015    return false;
6016  }
6017
6018  private boolean isInheritedProfile(StructureDefinition profile, String source) {
6019    if (source.equals(profile.getUrl())) {
6020      return false;
6021    }
6022    while (profile != null) {
6023      profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition(), profile);
6024      if (profile != null) {
6025        if (source.equals(profile.getUrl())) {
6026          return true;
6027        }
6028      }
6029    }
6030    return false;
6031  }
6032
6033  public boolean checkInvariant(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, StructureDefinition profile, Element resource, Element element, ElementDefinitionConstraintComponent inv) throws FHIRException {
6034    if (debug) {
6035      System.out.println("inv "+inv.getKey()+" on "+path+" in "+resource.fhirType()+" {{ "+inv.getExpression()+" }}"+time());
6036    }
6037    ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
6038    if (n == null) {
6039      long t = System.nanoTime();
6040      try {
6041        String expr = FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey());
6042        n = fpe.parse(expr);
6043      } catch (FHIRException e) {
6044        rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getVersionedUrl(), path, e.getMessage());
6045        return false;
6046      }
6047      timeTracker.fpe(t);
6048      inv.setUserData("validator.expression.cache", n);
6049    }
6050
6051    String msg;
6052    boolean ok;
6053    try {
6054      long t = System.nanoTime();
6055       ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n);
6056      timeTracker.fpe(t);
6057      msg = fpe.forLog();
6058    } catch (Exception ex) {
6059      ok = false;
6060      msg = ex.getClass().getName()+": "+ex.getMessage();
6061      ex.printStackTrace();
6062    }
6063    if (!ok) {
6064      if (wantInvariantInMessage) {
6065        msg = msg + " (inv = " + n.toString() + ")";
6066      }
6067      if (!Utilities.noString(msg)) {
6068        msg = msg + " (log: " + msg + ")";
6069      }
6070      msg = context.formatMessage(I18nConstants.INV_FAILED, inv.getKey() + ": '" + inv.getHuman()+"'")+msg;        
6071  
6072      if (inv.hasExtension(ToolingExtensions.EXT_BEST_PRACTICE) &&
6073        ToolingExtensions.readBooleanExtension(inv, ToolingExtensions.EXT_BEST_PRACTICE)) {
6074        if (bpWarnings == BestPracticeWarningLevel.Hint)
6075          hint(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
6076        else if (bpWarnings == BestPracticeWarningLevel.Warning)
6077          warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
6078        else if (bpWarnings == BestPracticeWarningLevel.Error)
6079          rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
6080      } else if (inv.getSeverity() == ConstraintSeverity.ERROR) {
6081        rule(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
6082      } else if (inv.getSeverity() == ConstraintSeverity.WARNING) {
6083        warning(errors, NO_RULE_DATE, IssueType.INVARIANT, element.line(), element.col(), path, ok, msg);
6084      }
6085    }
6086    return ok;
6087  }
6088  
6089  private boolean validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) {
6090    boolean ok = true;
6091    // all observations should have a subject, a performer, and a time
6092
6093    ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_SUBJECT) && ok;
6094    List<Element> performers = new ArrayList<>();
6095    element.getNamedChildren("performer", performers);
6096    ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), performers.size() > 0, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER) && ok;
6097    ok = bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_AN_EFFECTIVEDATETIME_OR_AN_EFFECTIVEPERIOD) && ok;
6098    
6099    return ok;
6100  }
6101
6102  /*
6103   * The actual base entry point for internal use (re-entrant)
6104   */
6105  private boolean validateResource(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource,
6106                                Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack, PercentageTracker pct, ValidationMode mode) throws FHIRException {
6107
6108    boolean ok = true;
6109    
6110    // check here if we call validation policy here, and then change it to the new interface
6111    assert stack != null;
6112    assert resource != null;
6113    boolean rok = true;
6114    String resourceName = element.getType(); // todo: consider namespace...?
6115
6116    if (defn == null) {
6117      long t = System.nanoTime();
6118      defn = element.getProperty().getStructure();
6119      if (defn == null)
6120        defn = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
6121      timeTracker.sd(t);
6122      //check exists
6123      rok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName),
6124        defn != null, I18nConstants.VALIDATION_VAL_PROFILE_NODEFINITION, resourceName);
6125      ok = rok && ok;
6126    }
6127
6128    // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead
6129    if (!typeMatchesDefn(resourceName, defn) && resourceName.equals(BUNDLE)) {
6130      NodeStack first = getFirstEntry(stack);
6131      if (first != null && typeMatchesDefn(first.getElement().getType(), defn)) {
6132        element = first.getElement();
6133        stack = first;
6134        resourceName = element.getType(); // todo: consider namespace...?
6135        idstatus = IdStatus.OPTIONAL; // why?
6136      }
6137      // todo: validate everything in this bundle.
6138    }
6139    if (rok) {
6140      if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) {
6141        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING) && ok;
6142      } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) {
6143        ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED) && ok;
6144      }
6145      if (element.getNamedChild(ID) != null) {
6146        Element eid = element.getNamedChild(ID);
6147        if (eid.getProperty() != null && eid.getProperty().getDefinition() != null && eid.getProperty().getDefinition().getBase().getPath().equals("Resource.id")) {
6148          NodeStack ns = stack.push(eid, -1, eid.getProperty().getDefinition(), null);
6149          if (eid.primitiveValue() != null && eid.primitiveValue().length() > 64) {
6150            ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MALFORMED_LENGTH) && ok;
6151          } else {
6152            ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), FormatUtilities.isValidId(eid.primitiveValue()), I18nConstants.RESOURCE_RES_ID_MALFORMED_CHARS) && ok;
6153          }
6154        }
6155      }
6156      // validate
6157      if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), resourceName.equals(defn.getType()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE,
6158          defn.getType(), resourceName, defn.getVersionedUrl())) {
6159        ok = start(hostContext, errors, element, element, defn, stack, pct, mode); // root is both definition and type
6160      } else {
6161        ok = false;
6162      }
6163    }
6164    return ok;
6165  }
6166
6167  private boolean typeMatchesDefn(String name, StructureDefinition defn) {
6168    if (defn.getKind() == StructureDefinitionKind.LOGICAL) {
6169      return name.equals(defn.getType()) || name.equals(defn.getName()) || name.equals(defn.getId());
6170    } else {
6171      return name.matches(defn.getType());
6172    }
6173  }
6174
6175  private NodeStack getFirstEntry(NodeStack bundle) {
6176    List<Element> list = new ArrayList<Element>();
6177    bundle.getElement().getNamedChildren(ENTRY, list);
6178    if (list.isEmpty())
6179      return null;
6180    Element resource = list.get(0).getNamedChild(RESOURCE);
6181    if (resource == null)
6182      return null;
6183    else {
6184      NodeStack entry = bundle.push(list.get(0), 0, list.get(0).getProperty().getDefinition(), list.get(0).getProperty().getDefinition());
6185      return entry.push(resource, -1, resource.getProperty().getDefinition(), context.fetchTypeDefinition(resource.fhirType()).getSnapshot().getElementFirstRep());
6186    }
6187  }
6188
6189  private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException {
6190    if (criteria.hasFixed()) {
6191      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
6192      checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getVersionedUrl(), "value", null, false);
6193      return msgs.size() == 0;
6194    } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) {
6195      throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE));
6196    } else {
6197      throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__NO_FIXED_VALUE_OR_REQUIRED_VALUE_SET));
6198    }
6199  }
6200
6201  private boolean yearIsValid(String v) {
6202    if (v == null) {
6203      return false;
6204    }
6205    try {
6206      int i = Integer.parseInt(v.substring(0, Math.min(4, v.length())));
6207      return i >= 1800 && i <= thisYear() + 80;
6208    } catch (NumberFormatException e) {
6209      return false;
6210    }
6211  }
6212
6213  private int thisYear() {
6214    return Calendar.getInstance().get(Calendar.YEAR);
6215  }
6216
6217
6218  public String reportTimes() {
6219    String s = String.format("Times (ms): overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", timeTracker.getOverall() / 1000000, timeTracker.getTxTime() / 1000000, timeTracker.getSdTime() / 1000000, timeTracker.getLoadTime() / 1000000, timeTracker.getFpeTime() / 1000000);
6220    timeTracker.reset();
6221    return s;
6222  }
6223
6224  public boolean isNoBindingMsgSuppressed() {
6225    return noBindingMsgSuppressed;
6226  }
6227
6228  public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) {
6229    this.noBindingMsgSuppressed = noBindingMsgSuppressed;
6230    return this;
6231  }
6232
6233
6234  public boolean isNoTerminologyChecks() {
6235    return noTerminologyChecks;
6236  }
6237
6238  public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) {
6239    this.noTerminologyChecks = noTerminologyChecks;
6240    return this;
6241  }
6242
6243  public void checkAllInvariants() {
6244    for (StructureDefinition sd : new ContextUtilities(context).allStructures()) {
6245      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
6246        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
6247          for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
6248            if (inv.hasExpression()) {
6249              try {
6250                ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
6251                if (n == null) {
6252                  n = fpe.parse(FHIRPathExpressionFixer.fixExpr(inv.getExpression(), inv.getKey()));
6253                  inv.setUserData("validator.expression.cache", n);
6254                }
6255                fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ? sd.getType() : "DomainResource", ed.getPath(), n);
6256              } catch (Exception e) {
6257                System.out.println("Error processing structure [" + sd.getId() + "] path " + ed.getPath() + ":" + inv.getKey() + " ('" + inv.getExpression() + "'): " + e.getMessage());
6258              }
6259            }
6260          }
6261        }
6262      }
6263    }
6264  }
6265
6266
6267  public IEvaluationContext getExternalHostServices() {
6268    return externalHostServices;
6269  }
6270
6271  public String getValidationLanguage() {
6272    return validationLanguage;
6273  }
6274
6275  public void setValidationLanguage(String validationLanguage) {
6276    this.validationLanguage = validationLanguage;
6277  }
6278
6279  public boolean isDebug() {
6280    return debug;
6281  }
6282
6283  public void setDebug(boolean debug) {
6284    this.debug = debug;
6285  }
6286  private String tail(String path) {
6287    return path.substring(path.lastIndexOf(".") + 1);
6288  }
6289  private String tryParse(String ref) {
6290    String[] parts = ref.split("\\/");
6291    switch (parts.length) {
6292      case 1:
6293        return null;
6294      case 2:
6295        return checkResourceType(parts[0]);
6296      default:
6297        if (parts[parts.length - 2].equals("_history") && parts.length >= 4)
6298          return checkResourceType(parts[parts.length - 4]);
6299        else
6300          return checkResourceType(parts[parts.length - 2]);
6301    }
6302  }
6303
6304  private boolean typesAreAllReference(List<TypeRefComponent> theType) {
6305    for (TypeRefComponent typeRefComponent : theType) {
6306      if (typeRefComponent.getCode().equals("Reference") == false) {
6307        return false;
6308      }
6309    }
6310    return true;
6311  }
6312
6313
6314  public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet vs, String value, ValidationOptions options) {
6315    return context.validateCode(options, value, vs);
6316  }
6317
6318  // no delay on this one? 
6319  public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) {
6320    return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), system, version, code, checkDisplay ? display : null);
6321  }
6322
6323  public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) {
6324    if (checkMembership) {
6325      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), c, valueset);   
6326    } else {
6327      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset);
6328    }
6329  }
6330  
6331  public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc, boolean vsOnly) {
6332    if (vsOnly) {
6333      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), cc, valueset);
6334    } else {
6335      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), cc, valueset);
6336    }
6337  }
6338
6339  public boolean isSecurityChecks() {
6340    return securityChecks;
6341  }
6342
6343  public void setSecurityChecks(boolean securityChecks) {
6344    this.securityChecks = securityChecks;
6345  }
6346
6347  @Override
6348  public List<BundleValidationRule> getBundleValidationRules() {
6349    return bundleValidationRules ;
6350  }
6351
6352  @Override
6353  public boolean isValidateValueSetCodesOnTxServer() {
6354    return validateValueSetCodesOnTxServer;
6355  }
6356
6357  @Override
6358  public void setValidateValueSetCodesOnTxServer(boolean value) {
6359    this.validateValueSetCodesOnTxServer = value;    
6360  }
6361
6362  public boolean isNoCheckAggregation() {
6363    return noCheckAggregation;
6364  }
6365
6366  public void setNoCheckAggregation(boolean noCheckAggregation) {
6367    this.noCheckAggregation = noCheckAggregation;
6368  }
6369
6370 
6371  public static void setParents(Element element) {
6372    if (element != null && !element.hasParentForValidator()) {
6373      element.setParentForValidator(null);
6374      setParentsInner(element);
6375    }
6376  }
6377  
6378  public static void setParentsInner(Element element) {
6379    for (Element child : element.getChildren()) {
6380      child.setParentForValidator(element);
6381      setParentsInner(child);
6382    }
6383  }
6384
6385  public void setQuestionnaireMode(QuestionnaireMode questionnaireMode) {
6386    this.questionnaireMode = questionnaireMode;
6387  }
6388
6389  public QuestionnaireMode getQuestionnaireMode() {
6390    return questionnaireMode;
6391  }
6392
6393  public boolean isWantCheckSnapshotUnchanged() {
6394    return wantCheckSnapshotUnchanged;
6395  }
6396
6397  public void setWantCheckSnapshotUnchanged(boolean wantCheckSnapshotUnchanged) {
6398    this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged;
6399  }
6400
6401  public ValidationOptions getBaseOptions() {
6402    return baseOptions;
6403  }
6404
6405  public void setBaseOptions(ValidationOptions baseOptions) {
6406    this.baseOptions = baseOptions;
6407  }
6408
6409  public boolean isNoUnicodeBiDiControlChars() {
6410    return noUnicodeBiDiControlChars;
6411  }
6412
6413  public void setNoUnicodeBiDiControlChars(boolean noUnicodeBiDiControlChars) {
6414    this.noUnicodeBiDiControlChars = noUnicodeBiDiControlChars;
6415  }
6416
6417
6418
6419  public HtmlInMarkdownCheck getHtmlInMarkdownCheck() {
6420    return htmlInMarkdownCheck;
6421  }
6422
6423  public void setHtmlInMarkdownCheck(HtmlInMarkdownCheck htmlInMarkdownCheck) {
6424    this.htmlInMarkdownCheck = htmlInMarkdownCheck;
6425  }
6426
6427  public Coding getJurisdiction() {
6428    return jurisdiction;
6429  }
6430
6431  public IResourceValidator setJurisdiction(Coding jurisdiction) {
6432    this.jurisdiction = jurisdiction;
6433    return this;
6434  }
6435
6436  public boolean isLogProgress() {
6437    return logProgress;
6438  }
6439
6440  public void setLogProgress(boolean logProgress) {
6441    this.logProgress = logProgress;
6442  }
6443
6444  public boolean isForPublication() {
6445    return forPublication;
6446  }
6447  
6448  public void setForPublication(boolean forPublication) {
6449    this.forPublication = forPublication;
6450  }
6451
6452  
6453}