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