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