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    if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) {
1878      list.get(1).setExpression("ElementDefinition.type");
1879    }
1880    if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version".equals(extUrl)) {
1881      list.get(0).setExpression("Element"); // well, it can't be used anywhere but the list of places it can be used is quite long
1882    }
1883    if (!VersionUtilities.isThisOrLater("4.6", context.getVersion())) {
1884      if (Utilities.existsInList(extUrl, "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", "http://hl7.org/fhir/StructureDefinition/capabilitystatement-prohibited")) {
1885        list.get(0).setExpression("Element"); // well, they can't be used anywhere but the list of places they can be used is quite long        
1886      }
1887    }
1888    return list;
1889  }
1890
1891  private String stripIndexes(String path) {
1892    boolean skip = false;
1893    StringBuilder b = new StringBuilder();
1894    for (char c : path.toCharArray()) {
1895      if (skip) {
1896        if (c == ']') {
1897          skip = false;
1898        }
1899      } else if (c == '[') {
1900        skip = true;
1901      } else {
1902        b.append(c);
1903      }
1904    }
1905    return b.toString();
1906  }
1907
1908  @SuppressWarnings("rawtypes")
1909  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) {
1910    if ((fixed == null || fixed.isEmpty()) && focus == null) {
1911      ; // this is all good
1912    } else if ((fixed == null || fixed.isEmpty()) && focus != null) {
1913      rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, pattern, I18nConstants.PROFILE_VAL_NOTALLOWED, focus.getName(), (pattern ? "pattern" : "fixed value"));
1914    } else if (fixed != null && !fixed.isEmpty() && focus == null) {
1915      rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, I18nConstants.PROFILE_VAL_MISSINGELEMENT, propName, fixedSource);
1916    } else {
1917      String value = focus.primitiveValue();
1918      if (fixed instanceof org.hl7.fhir.r5.model.BooleanType)
1919        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());
1920      else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType)
1921        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());
1922      else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType)
1923        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());
1924      else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType)
1925        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());
1926      else if (fixed instanceof org.hl7.fhir.r5.model.InstantType)
1927        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());
1928      else if (fixed instanceof org.hl7.fhir.r5.model.CodeType)
1929        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());
1930      else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration)
1931        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());
1932      else if (fixed instanceof org.hl7.fhir.r5.model.StringType)
1933        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());
1934      else if (fixed instanceof org.hl7.fhir.r5.model.UriType)
1935        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());
1936      else if (fixed instanceof org.hl7.fhir.r5.model.DateType)
1937        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());
1938      else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType)
1939        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());
1940      else if (fixed instanceof org.hl7.fhir.r5.model.OidType)
1941        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());
1942      else if (fixed instanceof org.hl7.fhir.r5.model.UuidType)
1943        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());
1944      else if (fixed instanceof org.hl7.fhir.r5.model.IdType)
1945        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());
1946      else if (fixed instanceof Quantity)
1947        checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern);
1948      else if (fixed instanceof Address)
1949        checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern);
1950      else if (fixed instanceof ContactPoint)
1951        checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern);
1952      else if (fixed instanceof Attachment)
1953        checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern);
1954      else if (fixed instanceof Identifier)
1955        checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern);
1956      else if (fixed instanceof Coding)
1957        checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern);
1958      else if (fixed instanceof HumanName)
1959        checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern);
1960      else if (fixed instanceof CodeableConcept)
1961        checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern);
1962      else if (fixed instanceof Timing)
1963        checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern);
1964      else if (fixed instanceof Period)
1965        checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern);
1966      else if (fixed instanceof Range)
1967        checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern);
1968      else if (fixed instanceof Ratio)
1969        checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern);
1970      else if (fixed instanceof SampledData)
1971        checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern);
1972      else if (fixed instanceof Reference)
1973        checkReference(errors, path, focus, (Reference) fixed, fixedSource, pattern);
1974
1975      else
1976        rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, I18nConstants.INTERNAL_INT_BAD_TYPE, fixed.fhirType());
1977      List<Element> extensions = new ArrayList<Element>();
1978      focus.getNamedChildren("extension", extensions);
1979      if (fixed.getExtension().size() == 0) {
1980        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0 || pattern == true, I18nConstants.EXTENSION_EXT_FIXED_BANNED);
1981      } 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()))) {
1982        for (Extension e : fixed.getExtension()) {
1983          Element ex = getExtensionByUrl(extensions, e.getUrl());
1984          if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) {
1985            checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"), false);
1986          }
1987        }
1988      }
1989    }
1990  }
1991
1992  private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) {
1993    checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
1994    checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
1995    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
1996
1997    List<Element> parts = new ArrayList<Element>();
1998    if (!pattern || fixed.hasFamily()) {
1999      focus.getNamedChildren("family", parts);
2000      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()))) {
2001        for (int i = 0; i < parts.size(); i++)
2002          checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern);
2003      }
2004    }
2005    if (!pattern || fixed.hasGiven()) {
2006      focus.getNamedChildren("given", parts);
2007      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()))) {
2008        for (int i = 0; i < parts.size(); i++)
2009          checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern);
2010      }
2011    }
2012    if (!pattern || fixed.hasPrefix()) {
2013      focus.getNamedChildren("prefix", parts);
2014      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()))) {
2015        for (int i = 0; i < parts.size(); i++)
2016          checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern);
2017      }
2018    }
2019    if (!pattern || fixed.hasSuffix()) {
2020      focus.getNamedChildren("suffix", parts);
2021      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()))) {
2022        for (int i = 0; i < parts.size(); i++)
2023          checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern);
2024      }
2025    }
2026  }
2027
2028  private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) {
2029    String system = element.getNamedChildValue("system");
2030    rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, system == null || isIdentifierSystemReferenceValid(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM);
2031    if ("urn:ietf:rfc:3986".equals(system)) {
2032      String value = element.getNamedChildValue("value");
2033      rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, value == null || isAbsolute(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE); 
2034    }
2035  }
2036
2037  private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) {
2038    checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
2039    checkFixedValue(errors, path + ".type", focus.getNamedChild(TYPE), fixed.getType(), fixedSource, TYPE, focus, pattern);
2040    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
2041    checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
2042    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
2043    checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern);
2044  }
2045
2046  private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) {
2047    checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern);
2048    checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern);
2049  }
2050
2051  private void checkPrimitive(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException {
2052    if (isBlank(e.primitiveValue())) {
2053      if (e.primitiveValue() == null)
2054        rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT);
2055      else if (e.primitiveValue().length() == 0)
2056        rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY);
2057      else if (StringUtils.isWhitespace(e.primitiveValue()))
2058        warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS);
2059      if (context.hasBinding()) {
2060        rule(errors, IssueType.CODEINVALID, e.line(), e.col(), path, context.getBinding().getStrength() != BindingStrength.REQUIRED, I18nConstants.Terminology_TX_Code_ValueSet_MISSING);
2061      }
2062      return;
2063    } else {
2064      boolean hasBiDiControls = UnicodeUtilities.hasBiDiChars(e.primitiveValue());
2065      if (hasBiDiControls) {
2066        if (rule(errors, IssueType.CODEINVALID, e.line(), e.col(), path, !noUnicodeBiDiControlChars, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_DISALLOWED, UnicodeUtilities.replaceBiDiChars(e.primitiveValue()))) {
2067          String msg = UnicodeUtilities.checkUnicodeWellFormed(e.primitiveValue());
2068          warning(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.UNICODE_BIDI_CONTROLS_CHARS_MATCH, msg);
2069        }
2070      }
2071    }
2072    String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
2073    if (regex != null) {
2074      rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex);
2075    }
2076    if (!"xhtml".equals(type)) {
2077      if (securityChecks) {
2078        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR);
2079      } else {
2080        hint(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING);
2081      }
2082    }
2083    
2084    
2085    if (type.equals("boolean")) {
2086      rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE);
2087    }
2088    if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
2089      String url = e.primitiveValue();
2090      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID);
2091      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID);
2092      rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", ""))
2093        // work around an old invalid example in a core package
2094        || "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);
2095      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());
2096      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());
2097
2098      if (type.equals("oid")) {
2099        rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_START);
2100      }
2101      if (type.equals("uuid")) {
2102        rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT);
2103      }
2104      if (type.equals("canonical")) {
2105        rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("#") || Utilities.isAbsoluteUrl(url), I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url);        
2106      }
2107
2108      if (url != null && url.startsWith("urn:uuid:")) {
2109        rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isValidUUID(url.substring(9)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_UUID_VALID);
2110      }
2111      if (url != null && url.startsWith("urn:oid:")) {
2112        rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(url.substring(8)), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_OID_VALID);
2113      }
2114
2115      if (isCanonicalURLElement(e)) {
2116        // we get to here if this is a defining canonical URL (e.g. CodeSystem.url)
2117        // the URL must be an IRI if present
2118        rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url), 
2119            node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url);                  
2120      } else {
2121        validateReference(hostContext, errors, path, type, context, e, url);
2122      }
2123    }
2124    if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) {
2125      // work around an old issue with ElementDefinition.id
2126      if (!context.getPath().equals("ElementDefinition.id")) {
2127        rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue());
2128      }
2129    }
2130    if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
2131      if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY)) {
2132        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);
2133        if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH)) {
2134          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());
2135        }
2136      }
2137    }
2138    if (type.equals("dateTime")) {
2139      warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
2140      rule(errors, IssueType.INVALID, e.line(), e.col(), path,
2141        e.primitiveValue()
2142          .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());
2143      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ);
2144      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());
2145      try {
2146        DateTimeType dt = new DateTimeType(e.primitiveValue());
2147      } catch (Exception ex) {
2148        rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage());
2149      }
2150    }
2151    if (type.equals("time")) {
2152      rule(errors, IssueType.INVALID, e.line(), e.col(), path,
2153        e.primitiveValue()
2154          .matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID);
2155      try {
2156        TimeType dt = new TimeType(e.primitiveValue());
2157      } catch (Exception ex) {
2158        rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_TIME_VALID, ex.getMessage());
2159      }
2160    }
2161    if (type.equals("date")) {
2162      warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
2163      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);
2164      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());
2165      try {
2166        DateType dt = new DateType(e.primitiveValue());
2167      } catch (Exception ex) {
2168        rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage());
2169      }
2170    }
2171    if (type.equals("base64Binary")) {
2172      String encoded = e.primitiveValue();
2173      if (isNotBlank(encoded)) {
2174        boolean ok = isValidBase64(encoded);
2175        if (!ok) {
2176          String value = encoded.length() < 100 ? encoded : "(snip)";
2177          rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value);
2178        } else {
2179          boolean wsok = !base64HasWhitespace(encoded);
2180          if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) {
2181            rule(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR);            
2182          } else {
2183            warning(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING);            
2184          }
2185        }
2186        if (ok && context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) {
2187          int size = countBase64DecodedBytes(encoded);
2188          long def = Long.parseLong(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxSize"));
2189          rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG, size, def);
2190        }
2191
2192      }
2193    }
2194    if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
2195      if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_VALID, e.primitiveValue())) {
2196        Integer v = new Integer(e.getValue()).intValue();
2197        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() : ""));
2198        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() : ""));
2199        if (type.equals("unsignedInt"))
2200          rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0);
2201        if (type.equals("positiveInt"))
2202          rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1);
2203      }
2204    }
2205    if (type.equals("integer64")) {
2206      if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER64_VALID, e.primitiveValue())) {
2207        Long v = new Long(e.getValue()).longValue();
2208        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() : ""));
2209        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() : ""));
2210        if (type.equals("unsignedInt"))
2211          rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0);
2212        if (type.equals("positiveInt"))
2213          rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1);
2214      }
2215    }
2216    if (type.equals("decimal")) {
2217      if (e.primitiveValue() != null) {
2218        DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
2219        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())) {
2220          warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue());
2221          try {
2222            Decimal v = new Decimal(e.getValue());
2223            rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || 
2224                !context.getMaxValueIntegerType().hasValue() || checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : ""));
2225            rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || 
2226                !context.getMinValueIntegerType().hasValue() || checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : ""));
2227          } catch (Exception ex) {
2228            // should never happen?
2229          }
2230        }
2231      }
2232      if (context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) {
2233        int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0;
2234        int def = Integer.parseInt(ToolingExtensions.readStringExtension(context, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces"));
2235        rule(errors, IssueType.STRUCTURE, e.line(), e.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def);
2236      }
2237    }
2238    if (type.equals("instant")) {
2239      rule(errors, IssueType.INVALID, e.line(), e.col(), path,
2240        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());
2241      warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
2242      try {
2243        InstantType dt = new InstantType(e.primitiveValue());
2244      } catch (Exception ex) {
2245        rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage());
2246      }
2247    }
2248
2249    if (type.equals("code") && e.primitiveValue() != null) {
2250      // 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
2251      // other than single spaces in the contents
2252      rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CODE_WS, e.primitiveValue());
2253      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());
2254    }
2255
2256    if (context.hasBinding() && e.primitiveValue() != null) {
2257      checkPrimitiveBinding(hostContext, errors, path, type, context, e, profile, node);
2258    }
2259
2260    if (type.equals("xhtml")) {
2261      XhtmlNode xhtml = e.getXhtml();
2262      if (xhtml != null) { // if it is null, this is an error already noted in the parsers
2263        // check that the namespace is there and correct.
2264        String ns = xhtml.getNsDecl();
2265        rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), I18nConstants.XHTML_XHTML_NS_INVALID, ns, FormatUtilities.XHTML_NS);
2266        // check that inner namespaces are all correct
2267        checkInnerNS(errors, e, path, xhtml.getChildNodes());
2268        rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, ns);
2269        // check that no illegal elements and attributes have been used
2270        checkInnerNames(errors, e, path, xhtml.getChildNodes(), false);
2271        checkUrls(errors, e, path, xhtml.getChildNodes());
2272      }
2273    }
2274
2275    if (context.hasFixed()) {
2276      checkFixedValue(errors, path, e, context.getFixed(), profile.getUrl(), context.getSliceName(), null, false);
2277    }
2278    if (context.hasPattern()) {
2279      checkFixedValue(errors, path, e, context.getPattern(), profile.getUrl(), context.getSliceName(), null, true);
2280    }
2281
2282    // for nothing to check
2283  }
2284
2285  public void validateReference(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, String url) {
2286    // now, do we check the URI target?
2287    if (fetcher != null && !type.equals("uuid")) {
2288      boolean found;
2289      try {
2290        found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || 
2291            SpecialExtensions.isKnownExtension(url) || isXverUrl(url);
2292        if (!found) {
2293          found = fetcher.resolveURL(this, hostContext, path, url, type);
2294        }
2295      } catch (IOException e1) {
2296        found = false;
2297      }
2298      if (!found) {
2299        if (type.equals("canonical")) {
2300          ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url);
2301          if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) {
2302            rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url);
2303          } else {
2304            hint(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url);
2305          }
2306        } else {
2307          if (url.contains("hl7.org") || url.contains("fhir.org")) {
2308            rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url);
2309          } else if (url.contains("example.org") || url.contains("acme.com")) {
2310            rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url);
2311          } else {
2312            warning(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url);
2313          }
2314        }
2315      } else {
2316        if (type.equals("canonical")) {
2317          ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url);
2318          if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) {
2319            try {
2320              Resource r = null;
2321              if (url.startsWith("#")) {
2322                r = loadContainedResource(errors, path, hostContext.getRootResource(), url.substring(1), Resource.class);
2323              }
2324              if (r == null) {
2325                fetcher.fetchCanonicalResource(this, url);
2326              }
2327              if (r == null) {
2328                r = this.context.fetchResource(Resource.class, url);
2329              }
2330              if (r == null) {
2331                warning(errors, IssueType.INVALID, e.line(), e.col(), path, rp != ReferenceValidationPolicy.CHECK_VALID, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC, url);                    
2332              } 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))) {
2333                if (rp == ReferenceValidationPolicy.CHECK_VALID) {
2334                  // todo....
2335                }
2336              }
2337            } catch (Exception ex) {
2338              // won't happen 
2339            }
2340          }
2341        }            
2342      }
2343    }
2344  }
2345
2346  private Set<String> listExpectedCanonicalTypes(ElementDefinition context) {
2347    Set<String> res = new HashSet<>();
2348    TypeRefComponent tr = context.getType("canonical");
2349    if (tr != null) {
2350      for (CanonicalType p : tr.getTargetProfile()) {
2351        String url = p.getValue();
2352        StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, url);
2353        if (sd != null) {
2354          res.add(sd.getType());
2355        } else {
2356          if (url != null && url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2357            res.add(url.substring("http://hl7.org/fhir/StructureDefinition/".length()));
2358          }
2359        }
2360      }
2361    }
2362    return res;
2363  }
2364
2365  private boolean isCorrectCanonicalType(Resource r, ElementDefinition context) {
2366    TypeRefComponent tr = context.getType("canonical");
2367    if (tr != null) {
2368      for (CanonicalType p : tr.getTargetProfile()) {
2369        if (isCorrectCanonicalType(r, p)) {
2370          return true;
2371        }
2372      }
2373      if (tr.getTargetProfile().isEmpty()) {
2374        return true;
2375      }
2376    }
2377    return false;
2378  }
2379
2380  private boolean isCorrectCanonicalType(Resource r, CanonicalType p) {
2381    String url = p.getValue();
2382    String t = null;
2383    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
2384    if (sd != null) {
2385      t = sd.getType();
2386    } else if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2387      t = url.substring("http://hl7.org/fhir/StructureDefinition/".length());
2388    } else {
2389      return false;
2390    }
2391    return Utilities.existsInList(t, "Resource", "CanonicalResource") || t.equals(r.fhirType());
2392  }
2393
2394  private boolean isCanonicalURLElement(Element e) {
2395    if (e.getProperty() == null || e.getProperty().getDefinition() == null) {
2396      return false;
2397    }
2398    String path = e.getProperty().getDefinition().getBase().getPath();
2399    if (path == null) {
2400      return false;
2401    }
2402    String[] p = path.split("\\."); 
2403    if (p.length != 2) {
2404      return false;
2405    }
2406    if (!"url".equals(p[1])) {
2407      return false;
2408    }
2409    return Utilities.existsInList(p[0], VersionUtilities.getCanonicalResourceNames(context.getVersion()));
2410  }
2411
2412  private boolean containsHtmlTags(String cnt) {
2413    int i = cnt.indexOf("<");
2414    while (i > -1) {
2415      cnt = cnt.substring(i+1);
2416      i = cnt.indexOf("<");
2417      int e = cnt.indexOf(">");
2418      if (e > -1 && e < i) {
2419        String s = cnt.substring(0, e);
2420        if (s.matches(HTML_FRAGMENT_REGEX)) {
2421          return true;
2422        }
2423      }
2424    }
2425    return false;
2426  }
2427
2428  /**
2429   * Technically this is not bulletproof as some invalid base64 won't be caught,
2430   * but I think it's good enough. The original code used Java8 Base64 decoder
2431   * but I've replaced it with a regex for 2 reasons:
2432   * 1. This code will run on any version of Java
2433   * 2. This code doesn't actually decode, which is much easier on memory use for big payloads
2434   */
2435  private boolean isValidBase64(String theEncoded) {
2436    if (theEncoded == null) {
2437      return false;
2438    }
2439    int charCount = 0;
2440    boolean ok = true;
2441    for (int i = 0; i < theEncoded.length(); i++) {
2442      char nextChar = theEncoded.charAt(i);
2443      if (Character.isWhitespace(nextChar)) {
2444        continue;
2445      }
2446      if (Character.isLetterOrDigit(nextChar)) {
2447        charCount++;
2448      }
2449      if (nextChar == '/' || nextChar == '=' || nextChar == '+') {
2450        charCount++;
2451      }
2452    }
2453
2454    if (charCount > 0 && charCount % 4 != 0) {
2455      ok = false;
2456    }
2457    return ok;
2458  }
2459
2460  private boolean base64HasWhitespace(String theEncoded) {
2461    if (theEncoded == null) {
2462      return false;
2463    }
2464    for (int i = 0; i < theEncoded.length(); i++) {
2465      char nextChar = theEncoded.charAt(i);
2466      if (Character.isWhitespace(nextChar)) {
2467        return true;
2468      }
2469    }
2470    return false;
2471
2472  }
2473
2474
2475  private int countBase64DecodedBytes(String theEncoded) {
2476    Base64InputStream inputStream = new Base64InputStream(new ByteArrayInputStream(theEncoded.getBytes(StandardCharsets.UTF_8)));
2477    try {
2478      try {
2479        for (int counter = 0; ; counter++) {
2480          if (inputStream.read() == -1) {
2481            return counter;
2482          }
2483        }
2484      } finally {
2485          inputStream.close();
2486      }
2487    } catch (IOException e) {
2488      throw new IllegalStateException(e); // should not happen
2489    }
2490  }
2491
2492  private boolean isDefinitionURL(String url) {
2493    return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer",
2494      "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");
2495  }
2496
2497  private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list, boolean inPara) {
2498    for (XhtmlNode node : list) {
2499      if (node.getNodeType() == NodeType.Comment) {
2500        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL);
2501      }
2502      if (node.getNodeType() == NodeType.Element) {
2503        rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(),
2504          "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
2505          "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
2506          "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
2507          "code", "samp", "img", "map", "area"), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName());
2508        
2509        for (String an : node.getAttributes().keySet()) {
2510          boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
2511            "title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex",
2512            // tables
2513            "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
2514
2515            Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
2516              "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
2517              "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
2518              "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
2519              "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
2520            );          
2521          if (!ok) {
2522            rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName());
2523          }
2524        }
2525        
2526        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());
2527        
2528        checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName()));
2529      }
2530    }
2531  }
2532
2533  private void checkUrls(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
2534    for (XhtmlNode node : list) {
2535      if (node.getNodeType() == NodeType.Element) {
2536        if ("a".equals(node.getName())) {
2537          String msg = checkValidUrl(node.getAttribute("href"));
2538          rule(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("href"), msg);
2539        } else if ("img".equals(node.getName())) {
2540          String msg = checkValidUrl(node.getAttribute("src"));
2541          rule(errors, IssueType.INVALID, e.line(), e.col(), path, msg == null, I18nConstants.XHTML_URL_INVALID, node.getAttribute("src"), msg);
2542        }
2543        checkUrls(errors, e, path, node.getChildNodes());
2544      }
2545    }
2546  }
2547
2548  private String checkValidUrl(String value) {
2549    if (value == null) {
2550      return null;
2551    }
2552    if (Utilities.noString(value)) {
2553      return context.formatMessage(I18nConstants.XHTML_URL_EMPTY);
2554    }
2555
2556    if (value.startsWith("data:")) {
2557      String[] p = value.substring(5).split("\\,");
2558      if (p.length < 2) {
2559        return context.formatMessage(I18nConstants.XHTML_URL_DATA_NO_DATA, value);        
2560      } else if (p.length > 2) {
2561        return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID_COMMA, value);                
2562      } else if (!p[0].endsWith(";base64") || !isValidBase64(p[1])) {
2563        return context.formatMessage(I18nConstants.XHTML_URL_DATA_DATA_INVALID, value);                        
2564      } else {
2565        if (p[0].startsWith(" ")) {
2566          p[0] = p[0].trim(); 
2567        }
2568        String mMsg = checkValidMimeType(p[0].substring(0, p[0].lastIndexOf(";")));
2569        if (mMsg != null) {
2570          return context.formatMessage(I18nConstants.XHTML_URL_DATA_MIMETYPE, value, mMsg);                  
2571        }
2572      }
2573      return null;
2574    } else {
2575      Set<Character> invalidChars = new HashSet<>();
2576      for (char ch : value.toCharArray()) {
2577        if (!(Character.isDigit(ch) || Character.isAlphabetic(ch) || Utilities.existsInList(ch, ';', '?', ':', '@', '&', '=', '+', '$', '.', ',', '/', '%', '-', '_', '~', '#', '[', ']', '!', '\'', '(', ')', '*' ))) {
2578          invalidChars.add(ch);
2579        }
2580      }
2581      if (invalidChars.isEmpty()) {
2582        return null;
2583      } else {
2584        return context.formatMessage(I18nConstants.XHTML_URL_INVALID_CHARS, invalidChars.toString());
2585      }
2586    }
2587  }
2588
2589  private String checkValidMimeType(String mt) {
2590    if (!mt.matches("^(\\w+|\\*)\\/(\\w+|\\*)((;\\s*(\\w+)=\\s*(\\S+))?)$")) {
2591      return "Mime type invalid";
2592    }
2593    return null;
2594  }
2595
2596  private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
2597    for (XhtmlNode node : list) {
2598      if (node.getNodeType() == NodeType.Element) {
2599        String ns = node.getNsDecl();
2600        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);
2601        checkInnerNS(errors, e, path, node.getChildNodes());
2602      }
2603    }
2604  }
2605
2606  private void checkPrimitiveBinding(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) {
2607    // We ignore bindings that aren't on string, uri or code
2608    if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) {
2609      return;
2610    }
2611    if (noTerminologyChecks)
2612      return;
2613
2614    String value = element.primitiveValue();
2615    // System.out.println("check "+value+" in "+path);
2616
2617    // firstly, resolve the value set
2618    ElementDefinitionBindingComponent binding = elementContext.getBinding();
2619    if (binding.hasValueSet()) {
2620      ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
2621      if (vs == null) { 
2622        CodeSystem cs = context.fetchCodeSystem(binding.getValueSet());
2623        if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs == null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND_CS, describeReference(binding.getValueSet()))) {
2624          warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, I18nConstants.TERMINOLOGY_TX_VALUESET_NOTFOUND, describeReference(binding.getValueSet()));
2625        }
2626      } else {
2627        CodedContentValidationPolicy validationPolicy = getPolicyAdvisor() == null ?
2628            CodedContentValidationPolicy.VALUESET : getPolicyAdvisor().policyForCodedContent(this, hostContext, stack.getLiteralPath(), elementContext, profile, BindingKind.PRIMARY, vs, new ArrayList<>());
2629
2630        if (validationPolicy != CodedContentValidationPolicy.IGNORE) {
2631          long t = System.nanoTime();
2632          ValidationResult vr = null;
2633          if (binding.getStrength() != BindingStrength.EXAMPLE) {
2634            ValidationOptions options = baseOptions.setLanguage(stack.getWorkingLang()).guessSystem();
2635            if (validationPolicy == CodedContentValidationPolicy.CODE) {
2636              options = options.noCheckValueSetMembership();              
2637            }
2638            vr = checkCodeOnServer(stack, vs, value, options);
2639          }
2640          timeTracker.tx(t, "vc "+value+"");
2641          if (binding.getStrength() == BindingStrength.REQUIRED) {
2642            removeTrackedMessagesForLocation(errors, element, path);
2643          }
2644          if (vr != null && !vr.isOk()) {
2645            if (vr.IsNoService())
2646              txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_15, value);
2647            else if (binding.getStrength() == BindingStrength.REQUIRED)
2648              txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_16, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage()));
2649            else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
2650              if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
2651                checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack);
2652              else if (!noExtensibleWarnings && !isOkExtension(value, vs))
2653                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()));
2654            } else if (binding.getStrength() == BindingStrength.PREFERRED) {
2655              if (baseOnly) {
2656                txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_18, value, describeValueSet(binding.getValueSet()), getErrorMessage(vr.getMessage()));
2657              }
2658            }
2659          }
2660        }
2661      }
2662    } else if (!noBindingMsgSuppressed)
2663      hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), I18nConstants.TERMINOLOGY_TX_BINDING_NOSOURCE2);
2664  }
2665
2666  private boolean isOkExtension(String value, ValueSet vs) {
2667    if ("http://hl7.org/fhir/ValueSet/defined-types".equals(vs.getUrl())) {
2668      return value.startsWith("http://hl7.org/fhirpath/System.");
2669    }
2670    return false;
2671  }
2672
2673  private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) {
2674    checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
2675    checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern);
2676    checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "units", focus, pattern);
2677    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
2678    checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
2679  }
2680
2681  private void checkQuantity(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) {
2682    String value = element.hasChild("value") ? element.getNamedChild("value").getValue() : null;
2683    String unit = element.hasChild("unit") ? element.getNamedChild("unit").getValue() : null;
2684    String system = element.hasChild("system") ? element.getNamedChild("system").getValue() : null;
2685    String code = element.hasChild("code") ? element.getNamedChild("code").getValue() : null;
2686
2687    // todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet)
2688    // todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation
2689
2690    if (!Utilities.noString(value) && definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) {
2691      int dp = value.contains(".") ? value.substring(value.indexOf(".")+1).length() : 0;
2692      int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces"));
2693      rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def);
2694    }
2695
2696    if (system != null || code != null ) {
2697      checkCodedElement(errors, path, element, theProfile, definition, false, false, theStack, code, system, null, unit);
2698    }
2699
2700    if (code != null && "http://unitsofmeasure.org".equals(system)) {
2701      int b = code.indexOf("{");
2702      int e = code.indexOf("}");
2703      if (b >= 0 && e > 0 && b < e) {
2704        bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1));
2705      }
2706    }
2707
2708    if (definition.hasMinValue()) {
2709      if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE) &&
2710          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())) {
2711        Quantity min = definition.getMinValueQuantity();
2712        if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM) &&
2713            warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM) && 
2714            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()) &&
2715            warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE) &&
2716            warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE) &&
2717            rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE)) {
2718          if (code.equals(min.getCode())) {
2719            // straight value comparison
2720            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());
2721          } else if ("http://unitsofmeasure.org".equals(system)) {
2722            if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC)) {
2723              Decimal v = convertUcumValue(value, code, min.getCode());
2724              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())) {
2725                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());
2726              }
2727            }
2728          } else {
2729            warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH, code, min.getCode());
2730          }
2731        }
2732      }
2733    }
2734    
2735    if (definition.hasMaxValue()) {
2736      if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE) &&
2737          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())) {
2738        Quantity max = definition.getMaxValueQuantity();
2739        if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM) &&
2740            warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM) && 
2741            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()) &&
2742            warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE) &&
2743            warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE) &&
2744            rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE)) {
2745          if (code.equals(max.getCode())) {
2746            // straight value comparison
2747            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());
2748          } else if ("http://unitsofmeasure.org".equals(system)) {
2749            if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC)) {
2750              Decimal v = convertUcumValue(value, code, max.getCode());
2751              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())) {
2752                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());
2753              }
2754            }
2755          } else {
2756            warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH, code, max.getCode());
2757          }
2758        }
2759      }
2760    }
2761  }
2762  
2763  private Decimal convertUcumValue(String value, String code, String minCode) {
2764    try {
2765      Decimal v = new Decimal(value);
2766      return context.getUcumService().convert(v, code, minCode);
2767    } catch (Exception e) {
2768      return null;
2769    }
2770  }
2771
2772  private boolean checkDecimalMaxValue(Decimal value, BigDecimal min) {
2773    try {
2774      Decimal m = new Decimal(min.toString());
2775      return value.comparesTo(m) <= 0;
2776    } catch (Exception e) {
2777      return false; // this will be another error somewhere else?
2778    }
2779  }
2780
2781  private boolean checkDecimalMaxValue(String value, BigDecimal min) {
2782    try {
2783      BigDecimal v = new BigDecimal(value);
2784      return v.compareTo(min) <= 0;      
2785    } catch (Exception e) {
2786      return false; // this will be another error somewhere else
2787    }
2788  }
2789
2790  private boolean checkDecimalMinValue(Decimal value, BigDecimal min) {
2791    try {
2792      Decimal m = new Decimal(min.toString());
2793      return value.comparesTo(m) >= 0;
2794    } catch (Exception e) {
2795      return false; // this will be another error somewhere else?
2796    }
2797  }
2798
2799  private boolean checkDecimalMinValue(String value, BigDecimal min) {
2800    try {
2801      BigDecimal v = new BigDecimal(value);
2802      return v.compareTo(min) >= 0;      
2803    } catch (Exception e) {
2804      return false; // this will be another error somewhere else
2805    }
2806  }
2807
2808  private void checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {
2809    long size = -1;
2810    // first check size
2811    String fetchError = null;
2812    if (element.hasChild("data")) {
2813      String b64 = element.getChildValue("data");
2814      // Note: If the value isn't valid, we're not adding an error here, as the test to the
2815      // child Base64Binary will catch it and we don't want to log it twice
2816      boolean ok = isValidBase64(b64);
2817      if (ok && element.hasChild("size")) {
2818        size = countBase64DecodedBytes(b64);
2819        String sz = element.getChildValue("size");
2820        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Long.toString(size).equals(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_CORRECT, sz, size);
2821      }
2822    } else if (element.hasChild("size")) {
2823      String sz = element.getChildValue("size");
2824      if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, Utilities.isLong(sz), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz)) {
2825        size = Long.parseLong(sz);
2826        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_SIZE_INVALID, sz);
2827      }
2828    } else if (element.hasChild("url")) {
2829      String url = element.getChildValue("url"); 
2830      if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) {
2831        try {
2832          if (url.startsWith("http://") || url.startsWith("https://")) {
2833            if (fetcher == null) {
2834              fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_FETCHER, url);  
2835            } else {
2836              byte[] cnt = fetcher.fetchRaw(this, url);
2837              size = cnt.length;
2838            }
2839          } else if (url.startsWith("file:")) {
2840            size = new File(url.substring(5)).length();
2841          } else {
2842            fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_UNKNOWN_URL_SCHEME, url);          }
2843        } catch (Exception e) {
2844          if (STACK_TRACE) e.printStackTrace();
2845          fetchError = context.formatMessage(I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_URL_ERROR, url, e.getMessage());
2846        }
2847      }
2848    }
2849    if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) {
2850      if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size >= 0, fetchError)) {
2851        long def = Long.parseLong(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxSize"));
2852        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, size <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG, size, def);
2853      }
2854    }
2855    warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (element.hasChild("data") || element.hasChild("url")) || (element.hasChild("contentType") || element.hasChild("language")), 
2856          I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT);
2857  }
2858
2859  // implementation
2860
2861  private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern) {
2862    checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern);
2863    checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern);
2864
2865  }
2866
2867  private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) {
2868    checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern);
2869    checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern);
2870  }
2871
2872  private void checkReference(ValidatorHostContext hostContext,
2873                              List<ValidationMessage> errors,
2874                              String path,
2875                              Element element,
2876                              StructureDefinition profile,
2877                              ElementDefinition container,
2878                              String parentType,
2879                              NodeStack stack) throws FHIRException {
2880    Reference reference = ObjectConverter.readAsReference(element);
2881
2882    String ref = reference.getReference();
2883    if (Utilities.noString(ref)) {
2884      if (!path.contains("element.pattern")) { // this business rule doesn't apply to patterns
2885        if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) {
2886          warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path,
2887            !Utilities.noString(element.getNamedChildValue("display")), I18nConstants.REFERENCE_REF_NODISPLAY);
2888        }
2889      }
2890      return;
2891    } else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) {
2892      // special known URLs that can't be validated but are known to be valid
2893      return;
2894    }
2895    warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !isSuspiciousReference(ref), I18nConstants.REFERENCE_REF_SUSPICIOUS, ref);      
2896
2897    ResolvedReference we = localResolve(ref, stack, errors, path, hostContext.getRootResource(), hostContext.getGroupingResource(), element);
2898    String refType;
2899    if (ref.startsWith("#")) {
2900      refType = "contained";
2901    } else {
2902      if (we == null) {
2903        refType = "remote";
2904      } else {
2905        refType = "bundled";
2906      }
2907    }
2908    ReferenceValidationPolicy pol;
2909    if (refType.equals("contained") || refType.equals("bundled")) {
2910      pol = ReferenceValidationPolicy.CHECK_VALID;
2911    } else {
2912      if (policyAdvisor == null) pol = ReferenceValidationPolicy.IGNORE;
2913      else pol = policyAdvisor.policyForReference(this, hostContext.getAppContext(), path, ref);
2914    }
2915
2916    if (pol.checkExists()) {
2917      if (we == null) {
2918        if (!refType.equals("contained")) {
2919          if (fetcher == null) {
2920            throw new FHIRException(context.formatMessage(I18nConstants.RESOURCE_RESOLUTION_SERVICES_NOT_PROVIDED));
2921          } else {
2922            Element ext = null;
2923            if (fetchCache.containsKey(ref)) {
2924              ext = fetchCache.get(ref);
2925            } else {
2926              try {
2927                ext = fetcher.fetch(this, hostContext.getAppContext(), ref);
2928              } catch (IOException e) {
2929                if (STACK_TRACE) e.printStackTrace();
2930                throw new FHIRException(e);
2931              }
2932              if (ext != null) {
2933                setParents(ext);
2934                fetchCache.put(ref, ext);
2935              }
2936            }
2937            we = ext == null ? null : makeExternalRef(ext, path);
2938          }
2939        }
2940      }
2941      boolean ok = (allowExamples && (ref.contains("example.org") || ref.contains("acme.com")))
2942        || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS);
2943      rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, I18nConstants.REFERENCE_REF_CANTRESOLVE, ref);
2944    }
2945
2946    String ft;
2947    if (we != null) {
2948      ft = we.getType();
2949    } else {
2950      ft = tryParse(ref);
2951    }
2952
2953    if (reference.hasType()) { // R4 onwards...
2954      // the type has to match the specified
2955      String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType();
2956      TypeRefComponent containerType = container.getType("Reference");
2957      if (!containerType.hasTargetProfile(tu)
2958        && !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")
2959        && !containerType.getTargetProfile().isEmpty()
2960      ) {
2961        boolean matchingResource = false;
2962        for (CanonicalType target : containerType.getTargetProfile()) {
2963          StructureDefinition sd = resolveProfile(profile, target.asStringValue());
2964          if (rule(errors, IssueType.NOTFOUND, element.line(), element.col(), path, sd != null,
2965            I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, target.asStringValue())) {
2966          if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) {
2967            matchingResource = true;
2968            break;
2969          }
2970          }
2971        }
2972        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource,
2973          I18nConstants.REFERENCE_REF_WRONGTARGET, reference.getType(), container.getType("Reference").getTargetProfile());
2974
2975      }
2976      // the type has to match the actual
2977      rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path,
2978        ft == null || ft.equals(reference.getType()), I18nConstants.REFERENCE_REF_BADTARGETTYPE, reference.getType(), ft);
2979    }
2980
2981    if (we != null && pol.checkType()) {
2982      if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null,
2983        I18nConstants.REFERENCE_REF_NOTYPE)) {
2984        // we validate as much as we can. First, can we infer a type from the profile?
2985        boolean ok = false;
2986        TypeRefComponent type = getReferenceTypeRef(container.getType());
2987        if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
2988          Set<String> types = new HashSet<>();
2989          List<StructureDefinition> profiles = new ArrayList<>();
2990          for (UriType u : type.getTargetProfile()) {
2991            StructureDefinition sd = resolveProfile(profile, u.getValue());
2992            if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null,
2993              I18nConstants.REFERENCE_REF_CANTRESOLVEPROFILE, u.getValue())) {
2994              types.add(sd.getType());
2995              if (ft.equals(sd.getType())) {
2996                ok = true;
2997                profiles.add(sd);
2998              }
2999            }
3000          }
3001          if (!pol.checkValid()) {
3002            rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0,
3003              I18nConstants.REFERENCE_REF_CANTMATCHTYPE, ref, StringUtils.join("; ", type.getTargetProfile()));
3004          } else {
3005            Map<StructureDefinition, List<ValidationMessage>> badProfiles = new HashMap<>();
3006            Map<StructureDefinition, List<ValidationMessage>> goodProfiles = new HashMap<>();
3007            int goodCount = 0;
3008            for (StructureDefinition pr : profiles) {
3009              List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
3010              validateResource(we.hostContext(hostContext, pr), profileErrors, we.getResource(), we.getFocus(), pr,
3011                IdStatus.OPTIONAL, we.getStack().resetIds());
3012              if (!hasErrors(profileErrors)) {
3013                goodCount++;
3014                goodProfiles.put(pr, profileErrors);
3015                trackUsage(pr, hostContext, element);
3016              } else {
3017                badProfiles.put(pr, profileErrors);
3018              }
3019            }
3020            if (goodCount == 1) {
3021              if (showMessagesFromReferences) {
3022                for (ValidationMessage vm : goodProfiles.values().iterator().next()) {
3023                  if (!errors.contains(vm)) {
3024                    errors.add(vm);
3025                  }
3026                }
3027              }
3028
3029            } else if (goodProfiles.size() == 0) {
3030              if (!isShowMessagesFromReferences()) {
3031                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles),
3032                  I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile()));
3033                for (StructureDefinition sd : badProfiles.keySet()) {
3034                  slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, false, 
3035                    context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), 
3036                    errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
3037                }
3038              } else {
3039                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1,
3040                  I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile()));
3041                for (List<ValidationMessage> messages : badProfiles.values()) {
3042                  for (ValidationMessage vm : messages) {
3043                    if (!errors.contains(vm)) {
3044                      errors.add(vm);
3045                    }
3046                  }
3047                }
3048              }
3049            } else {
3050              if (!isShowMessagesFromReferences()) {
3051                warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,
3052                  I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
3053                for (StructureDefinition sd : badProfiles.keySet()) {
3054                  slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,
3055                    false,  context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()),
3056                      errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd)));
3057                }
3058              } else {
3059                warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,
3060                  I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet()));
3061                for (List<ValidationMessage> messages : goodProfiles.values()) {
3062                  for (ValidationMessage vm : messages) {
3063                    if (!errors.contains(vm)) {
3064                      errors.add(vm);
3065                    }
3066                  }
3067                }
3068              }
3069            }
3070          }
3071          rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok,
3072            I18nConstants.REFERENCE_REF_BADTARGETTYPE, ft, types.toString());
3073        }
3074        if (type.hasAggregation() && !noCheckAggregation) {
3075          boolean modeOk = false;
3076          CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3077          for (Enumeration<AggregationMode> mode : type.getAggregation()) {
3078            b.append(mode.getCode());
3079            if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
3080              modeOk = true;
3081            else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
3082              modeOk = true;
3083            else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote")))
3084              modeOk = true;
3085          }
3086          rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk,
3087            I18nConstants.REFERENCE_REF_AGGREGATION, refType, b.toString());
3088        }
3089      }
3090    }
3091    if (we == null) {
3092      TypeRefComponent type = getReferenceTypeRef(container.getType());
3093      boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED);
3094      rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef, I18nConstants.REFERENCE_REF_NOTFOUND_BUNDLE, ref);
3095    }
3096    if (we == null && ft != null && assumeValidRestReferences) {
3097      // if we == null, we inferred ft from the reference. if we are told to treat this as gospel
3098      TypeRefComponent type = getReferenceTypeRef(container.getType());
3099      Set<String> types = new HashSet<>();
3100      StructureDefinition sdFT = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ft);
3101      boolean ok = false;
3102      for (CanonicalType tp : type.getTargetProfile()) {
3103        StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue());
3104        if (sd != null) {
3105          types.add(sd.getType());
3106        }
3107        StructureDefinition sdF = sdFT;
3108        while (sdF != null) {
3109          if (sdF.getType().equals(sd.getType())) {
3110            ok = true;
3111            break;
3112          }
3113          sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition()) : null;
3114        }
3115      }
3116      rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || ok,
3117        I18nConstants.REFERENCE_REF_BADTARGETTYPE2, ft, ref, types);
3118
3119    }
3120    if (pol == ReferenceValidationPolicy.CHECK_VALID) {
3121      // todo....
3122    }
3123  }
3124
3125  private boolean isSuspiciousReference(String url) {
3126    if (!assumeValidRestReferences || url == null || Utilities.isAbsoluteUrl(url) || url.startsWith("#")) {
3127      return false;
3128    }
3129    String[] parts = url.split("\\/");
3130    if (parts.length == 2 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1])) {
3131      return false;
3132    }
3133    if (parts.length == 4 && context.getResourceNames().contains(parts[0]) && Utilities.isValidId(parts[1]) && "_history".equals(parts[2]) && Utilities.isValidId(parts[3])) {
3134      return false;
3135    }
3136    return true;
3137  }
3138
3139  private String asListByUrl(Collection<StructureDefinition> list) {
3140    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3141    for (StructureDefinition sd : list) {
3142      b.append(sd.getUrl());
3143    }
3144    return b.toString();
3145  }
3146
3147  private String asList(Collection<CanonicalType> list) {
3148    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3149    for (CanonicalType c : list) {
3150      b.append(c.getValue());
3151    }
3152    return b.toString();
3153  }
3154
3155  private boolean areAllBaseProfiles(List<StructureDefinition> profiles) {
3156    for (StructureDefinition sd : profiles) {
3157      if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
3158        return false;
3159      }
3160    }
3161    return true;
3162  }
3163
3164  private String errorSummaryForSlicing(List<ValidationMessage> list) {
3165    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3166    for (ValidationMessage vm : list) {
3167      if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
3168        b.append(vm.getLocation() + ": " + vm.getMessage());
3169      }
3170    }
3171    return b.toString();
3172  }
3173
3174  private String errorSummaryForSlicingAsHtml(List<ValidationMessage> list) {
3175    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3176    for (ValidationMessage vm : list) {
3177      if (vm.isSlicingHint()) {
3178        b.append("<li>" + vm.getLocation() + ": " + vm.getSliceHtml() + "</li>");
3179      } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
3180        b.append("<li>" + vm.getLocation() + ": " + vm.getHtml() + "</li>");
3181      }
3182    }
3183    return "<ul>" + b.toString() + "</ul>";
3184  }
3185
3186  private boolean isCritical(List<ValidationMessage> list) {
3187    for (ValidationMessage vm : list) {
3188      if (vm.isSlicingHint() && vm.isCriticalSignpost()) {
3189        return true;
3190      }
3191    }
3192    return false;
3193  }
3194  
3195  private String[] errorSummaryForSlicingAsText(List<ValidationMessage> list) {
3196    List<String> res = new ArrayList<String>();
3197    for (ValidationMessage vm : list) {
3198      if (vm.isSlicingHint()) {
3199        if (vm.sliceText != null) {
3200          for (String s : vm.sliceText) {
3201            res.add(vm.getLocation() + ": " + s);
3202          }
3203        } else {
3204          res.add(vm.getLocation() + ": " + vm.getMessage());
3205        }
3206      } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
3207        res.add(vm.getLocation() + ": " + vm.getHtml());
3208      }
3209    }
3210    return res.toArray(new String[0]);
3211  }
3212
3213  private TypeRefComponent getReferenceTypeRef(List<TypeRefComponent> types) {
3214    for (TypeRefComponent tr : types) {
3215      if ("Reference".equals(tr.getCode())) {
3216        return tr;
3217      }
3218    }
3219    return null;
3220  }
3221
3222  private String checkResourceType(String type) {
3223    long t = System.nanoTime();
3224    try {
3225      if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
3226        return type;
3227      else
3228        return null;
3229    } finally {
3230      timeTracker.sd(t);
3231    }
3232  }
3233
3234  private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern) {
3235    checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), fixedSource, "origin", focus, pattern);
3236    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), fixedSource, "period", focus, pattern);
3237    checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), fixedSource, "factor", focus, pattern);
3238    checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern);
3239    checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern);
3240    checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern);
3241    checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern);
3242  }
3243
3244  private void checkReference(List<ValidationMessage> errors, String path, Element focus, Reference fixed, String fixedSource, boolean pattern) {
3245    checkFixedValue(errors, path + ".reference", focus.getNamedChild("reference"), fixed.getReferenceElement_(), fixedSource, "reference", focus, pattern);
3246    checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getTypeElement(), fixedSource, "type", focus, pattern);
3247    checkFixedValue(errors, path + ".identifier", focus.getNamedChild("identifier"), fixed.getIdentifier(), fixedSource, "identifier", focus, pattern);
3248    checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern);
3249  }
3250
3251  private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) {
3252    checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), fixedSource, "value", focus, pattern);
3253
3254    List<Element> events = new ArrayList<Element>();
3255    focus.getNamedChildren("event", events);
3256    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()))) {
3257      for (int i = 0; i < events.size(); i++)
3258        checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern);
3259    }
3260  }
3261
3262  private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
3263    for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
3264      if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
3265        return true;
3266      if (codeinExpansion(c, system, code))
3267        return true;
3268    }
3269    return false;
3270  }
3271
3272  private boolean codeInExpansion(ValueSet vs, String system, String code) {
3273    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
3274      if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
3275        return true;
3276      if (codeinExpansion(c, system, code))
3277        return true;
3278    }
3279    return false;
3280  }
3281
3282  private String describeReference(String reference, CanonicalResource target) {
3283    if (reference == null && target == null)
3284      return "null";
3285    if (reference == null) {
3286      return target.getUrl();
3287    }
3288    if (target == null) {
3289      return reference;
3290    }
3291    if (reference.equals(target.getUrl())) {
3292      return reference;
3293    }
3294    return reference + "(which actually refers to " + target.getUrl() + ")";
3295  }
3296
3297  private String describeTypes(List<TypeRefComponent> types) {
3298    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3299    for (TypeRefComponent t : types) {
3300      b.append(t.getWorkingCode());
3301    }
3302    return b.toString();
3303  }
3304
3305  protected ElementDefinition findElement(StructureDefinition profile, String name) {
3306    for (ElementDefinition c : profile.getSnapshot().getElement()) {
3307      if (c.getPath().equals(name)) {
3308        return c;
3309      }
3310    }
3311    return null;
3312  }
3313
3314  public BestPracticeWarningLevel getBestPracticeWarningLevel() {
3315    return bpWarnings;
3316  }
3317
3318  @Override
3319  public CheckDisplayOption getCheckDisplay() {
3320    return checkDisplay;
3321  }
3322
3323  private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
3324    if (code.equals(c.getCode()))
3325      return c;
3326    for (ConceptDefinitionComponent g : c.getConcept()) {
3327      ConceptDefinitionComponent r = getCodeDefinition(g, code);
3328      if (r != null)
3329        return r;
3330    }
3331    return null;
3332  }
3333
3334  private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
3335    for (ConceptDefinitionComponent c : cs.getConcept()) {
3336      ConceptDefinitionComponent r = getCodeDefinition(c, code);
3337      if (r != null)
3338        return r;
3339    }
3340    return null;
3341  }
3342
3343  private IndexedElement getContainedById(Element container, String id) {
3344    List<Element> contained = new ArrayList<Element>();
3345    container.getNamedChildren("contained", contained);
3346    for (int i = 0; i < contained.size(); i++) {
3347      Element we = contained.get(i);
3348      if (id.equals(we.getNamedChildValue(ID))) {
3349        return new IndexedElement(i, we, null);
3350      }
3351    }
3352    return null;
3353  }
3354
3355  public IWorkerContext getContext() {
3356    return context;
3357  }
3358
3359  private List<ElementDefinition> getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException {
3360    List<ElementDefinition> elements = new ArrayList<ElementDefinition>();
3361    if ("value".equals(discriminator) && element.hasFixed()) {
3362      elements.add(element);
3363      return elements;
3364    }
3365
3366    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
3367      if (discriminator.equals("resolve()")) {
3368        elements.add(element);
3369        return elements;
3370      }
3371      if (discriminator.endsWith(".resolve()"))
3372        discriminator = discriminator.substring(0, discriminator.length() - 10);
3373    }
3374
3375    TypedElementDefinition ted = null;
3376    String fp = fixExpr(discriminator, null);
3377    ExpressionNode expr = null;
3378    try {
3379      expr = fpe.parse(fp);
3380    } catch (Exception e) {
3381      if (STACK_TRACE) e.printStackTrace();
3382      throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e);
3383    }
3384    long t2 = System.nanoTime();
3385    ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile);
3386    timeTracker.sd(t2);
3387    if (ted != null)
3388      elements.add(ted.getElement());
3389
3390    for (TypeRefComponent type : element.getType()) {
3391      for (CanonicalType p : type.getProfile()) {
3392        String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null;
3393        StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
3394        if (sd == null)
3395          throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE_, p));
3396        profile = sd;
3397        if (id == null)
3398          element = sd.getSnapshot().getElementFirstRep();
3399        else {
3400          element = null;
3401          for (ElementDefinition t : sd.getSnapshot().getElement()) {
3402            if (id.equals(t.getId()))
3403              element = t;
3404          }
3405          if (element == null)
3406            throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ELEMENT__IN_PROFILE_, id, p));
3407        }
3408        expr = fpe.parse(fp);
3409        t2 = System.nanoTime();
3410        ted = fpe.evaluateDefinition(expr, profile, new TypedElementDefinition(element), srcProfile);
3411        timeTracker.sd(t2);
3412        if (ted != null)
3413          elements.add(ted.getElement());
3414      }
3415    }
3416    return elements;
3417  }
3418
3419
3420  private Element getExtensionByUrl(List<Element> extensions, String urlSimple) {
3421    for (Element e : extensions) {
3422      if (urlSimple.equals(e.getNamedChildValue("url")))
3423        return e;
3424    }
3425    return null;
3426  }
3427
3428  public List<String> getExtensionDomains() {
3429    return extensionDomains;
3430  }
3431
3432  public List<ImplementationGuide> getImplementationGuides() {
3433    return igs;
3434  }
3435
3436  private StructureDefinition getProfileForType(String type, List<TypeRefComponent> list) {
3437    for (TypeRefComponent tr : list) {
3438      String url = tr.getWorkingCode();
3439      if (!Utilities.isAbsoluteUrl(url))
3440        url = "http://hl7.org/fhir/StructureDefinition/" + url;
3441      long t = System.nanoTime();
3442      StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
3443      timeTracker.sd(t);
3444      if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot())
3445        return sd;
3446    }
3447    return null;
3448  }
3449
3450  private Element getValueForDiscriminator(Object appContext, List<ValidationMessage> errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException {
3451    String p = stack.getLiteralPath() + "." + element.getName();
3452    Element focus = element;
3453    String[] dlist = discriminator.split("\\.");
3454    for (String d : dlist) {
3455      if (focus.fhirType().equals("Reference") && d.equals("reference")) {
3456        String url = focus.getChildValue("reference");
3457        if (Utilities.noString(url))
3458          throw new FHIRException(context.formatMessage(I18nConstants.NO_REFERENCE_RESOLVING_DISCRIMINATOR__FROM_, discriminator, element.getProperty().getName()));
3459        // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough?
3460        Element target = resolve(appContext, url, stack, errors, p);
3461        if (target == null)
3462          throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCE__AT__RESOLVING_DISCRIMINATOR__FROM_, url, d, discriminator, element.getProperty().getName()));
3463        focus = target;
3464      } else if (d.equals("value") && focus.isPrimitive()) {
3465        return focus;
3466      } else {
3467        List<Element> children = focus.getChildren(d);
3468        if (children.isEmpty())
3469          throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND__RESOLVING_DISCRIMINATOR__FROM_, d, discriminator, element.getProperty().getName()));
3470        if (children.size() > 1)
3471          throw new FHIRException(context.formatMessage(I18nConstants.FOUND__ITEMS_FOR__RESOLVING_DISCRIMINATOR__FROM_, Integer.toString(children.size()), d, discriminator, element.getProperty().getName()));
3472        focus = children.get(0);
3473        p = p + "." + d;
3474      }
3475    }
3476    return focus;
3477  }
3478
3479  private CodeSystem getCodeSystem(String system) {
3480    long t = System.nanoTime();
3481    try {
3482      return context.fetchCodeSystem(system);
3483    } finally {
3484      timeTracker.tx(t, "cs "+system);
3485    }
3486  }
3487
3488  private boolean hasTime(String fmt) {
3489    return fmt.contains("T");
3490  }
3491
3492  private boolean hasTimeZone(String fmt) {
3493    return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z"));
3494  }
3495
3496  private boolean isAbsolute(String uri) {
3497    String protocol = null;
3498    String tail = null;
3499    if (uri.contains(":")) {
3500      protocol = uri.substring(0, uri.indexOf(":"));
3501      tail = uri.substring(uri.indexOf(":")+1);
3502    }
3503    if (Utilities.isToken(protocol)) {
3504      if ("file".equals(protocol)) {
3505        return tail.startsWith("/") || tail.contains(":");
3506      } else {
3507        return true;
3508      }
3509    } else {
3510      return false;
3511    }
3512  }
3513
3514  private boolean isCodeSystemReferenceValid(String uri) {
3515    return isSystemReferenceValid(uri);    
3516  }
3517
3518  private boolean isIdentifierSystemReferenceValid(String uri) {
3519    return isSystemReferenceValid(uri) || uri.startsWith("ldap:");
3520  }
3521
3522  private boolean isSystemReferenceValid(String uri) {
3523    return uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:");
3524  }
3525
3526  public boolean isAnyExtensionsAllowed() {
3527    return anyExtensionsAllowed;
3528  }
3529
3530  public boolean isErrorForUnknownProfiles() {
3531    return errorForUnknownProfiles;
3532  }
3533
3534  public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
3535    this.errorForUnknownProfiles = errorForUnknownProfiles;
3536  }
3537
3538  private boolean isParametersEntry(String path) {
3539    String[] parts = path.split("\\.");
3540    return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part"));
3541  }
3542
3543  private boolean isBundleEntry(String path) {
3544    String[] parts = path.split("\\.");
3545    return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && pathEntryHasName(parts[parts.length - 2], ENTRY);
3546  }
3547
3548  private boolean isBundleOutcome(String path) {
3549    String[] parts = path.split("\\.");
3550    return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response");
3551  }
3552
3553
3554  private static boolean pathEntryHasName(String thePathEntry, String theName) {
3555    if (thePathEntry.equals(theName)) {
3556      return true;
3557    }
3558    if (thePathEntry.length() >= theName.length() + 3) {
3559      if (thePathEntry.startsWith(theName)) {
3560        if (thePathEntry.charAt(theName.length()) == '[') {
3561          return true;
3562        }
3563      }
3564    }
3565    return false;
3566  }
3567
3568  public boolean isPrimitiveType(String code) {
3569    StructureDefinition sd = context.fetchTypeDefinition(code);
3570    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3571  }
3572
3573  private String getErrorMessage(String message) {
3574    return message != null ? " (error message = " + message + ")" : "";
3575  }
3576
3577  public boolean isSuppressLoincSnomedMessages() {
3578    return suppressLoincSnomedMessages;
3579  }
3580
3581  private boolean nameMatches(String name, String tail) {
3582    if (tail.endsWith("[x]"))
3583      return name.startsWith(tail.substring(0, tail.length() - 3));
3584    else
3585      return (name.equals(tail));
3586  }
3587
3588  private boolean passesCodeWhitespaceRules(String v) {
3589    if (!v.trim().equals(v))
3590      return false;
3591    boolean lastWasSpace = true;
3592    for (char c : v.toCharArray()) {
3593      if (c == ' ') {
3594        if (lastWasSpace)
3595          return false;
3596        else
3597          lastWasSpace = true;
3598      } else if (Character.isWhitespace(c))
3599        return false;
3600      else
3601        lastWasSpace = false;
3602    }
3603    return true;
3604  }
3605
3606  private ResolvedReference localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path, Element rootResource, Element groupingResource, Element source) {
3607    if (ref.startsWith("#")) {
3608      // work back through the parent list.
3609      // really, there should only be one level for this (contained resources cannot contain
3610      // contained resources), but we'll leave that to some other code to worry about
3611      boolean wasContained = false;
3612      NodeStack nstack = stack;
3613      while (nstack != null && nstack.getElement() != null) {
3614        if (nstack.getElement().getProperty().isResource()) {
3615          // ok, we'll try to find the contained reference
3616          if (ref.equals("#") && nstack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) {
3617            ResolvedReference rr = new ResolvedReference();
3618            rr.setResource(nstack.getElement());
3619            rr.setFocus(nstack.getElement());
3620            rr.setExternal(false);
3621            rr.setStack(nstack);
3622//            rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
3623//            System.out.println("-->"+nstack.getLiteralPath());
3624            return rr;            
3625          }
3626          if (nstack.getElement().getSpecial() == SpecialElement.CONTAINED) {
3627            wasContained = true;
3628          }
3629          IndexedElement res = getContainedById(nstack.getElement(), ref.substring(1));
3630          if (res != null) {
3631            ResolvedReference rr = new ResolvedReference();
3632            rr.setResource(nstack.getElement());
3633            rr.setFocus(res.getMatch());
3634            rr.setExternal(false);
3635            rr.setStack(nstack.push(res.getMatch(), res.getIndex(), res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
3636            rr.getStack().qualifyPath(".ofType("+nstack.getElement().fhirType()+")");
3637            return rr;
3638          }
3639        }
3640        if (nstack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || nstack.getElement().getSpecial() == SpecialElement.PARAMETER) {
3641          return null; // we don't try to resolve contained references across this boundary
3642        }
3643        nstack = nstack.getParent();
3644      }
3645      // try again, and work up the element parent list 
3646      if (ref.equals("#")) {
3647        Element e = stack.getElement();
3648        while (e != null) {
3649          if (e.getProperty().isResource() && (e.getSpecial() != SpecialElement.CONTAINED)) {
3650            ResolvedReference rr = new ResolvedReference();
3651            rr.setResource(e);
3652            rr.setFocus(e);
3653            rr.setExternal(false);
3654            rr.setStack(stack.push(e, -1, e.getProperty().getDefinition(), e.getProperty().getDefinition()));
3655            rr.getStack().qualifyPath(".ofType("+e.fhirType()+")");
3656            return rr;            
3657          }
3658          e = e.getParentForValidator();
3659        }
3660      }
3661      return null;
3662    } else {
3663      // work back through the parent list - if any of them are bundles, try to resolve
3664      // the resource in the bundle
3665      String fullUrl = null; // we're going to try to work this out as we go up
3666      while (stack != null && stack.getElement() != null) {
3667        if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && stack.getParent() != null && stack.getParent().getElement().getName().equals(ENTRY)) {
3668          String type = stack.getParent().getParent().getElement().getChildValue(TYPE);
3669          fullUrl = stack.getParent().getElement().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary
3670          if (fullUrl == null)
3671            rule(errors, IssueType.REQUIRED, stack.getParent().getElement().line(), stack.getParent().getElement().col(), stack.getParent().getLiteralPath(),
3672              Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL);
3673        }
3674        if (BUNDLE.equals(stack.getElement().getType())) {
3675          String type = stack.getElement().getChildValue(TYPE);
3676          IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type));
3677          if (res == null) {
3678            return null;
3679          } else {
3680            ResolvedReference rr = new ResolvedReference();
3681            rr.setResource(res.getMatch());
3682            rr.setFocus(res.getMatch());
3683            rr.setExternal(false);
3684            rr.setStack(stack.push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
3685              res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
3686              res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
3687            rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")");
3688            return rr;
3689          }
3690        }
3691        if (stack.getElement().getSpecial() == SpecialElement.PARAMETER && stack.getParent() != null) {
3692          NodeStack tgt = findInParams(stack.getParent().getParent(), ref);
3693          if (tgt != null) {
3694            ResolvedReference rr = new ResolvedReference();
3695            rr.setResource(tgt.getElement());
3696            rr.setFocus(tgt.getElement());
3697            rr.setExternal(false);
3698            rr.setStack(tgt);
3699            rr.getStack().qualifyPath(".ofType("+tgt.getElement().fhirType()+")");
3700            return rr;            
3701          }
3702        }
3703        stack = stack.getParent();
3704      }
3705      // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity.
3706      if (groupingResource != null && BUNDLE.equals(groupingResource.fhirType())) { // it could also be a Parameters resource - that case isn't handled yet
3707        String type = groupingResource.getChildValue(TYPE);
3708        Element entry = getEntryForSource(groupingResource, source);
3709        fullUrl = entry.getChildValue(FULL_URL);
3710        IndexedElement res = getFromBundle(groupingResource, ref, fullUrl, errors, path, type, "transaction".equals(type));
3711        if (res == null) {
3712          return null;
3713        } else {
3714          ResolvedReference rr = new ResolvedReference();
3715          rr.setResource(res.getMatch());
3716          rr.setFocus(res.getMatch());
3717          rr.setExternal(false);
3718          rr.setStack(new NodeStack(context, null, rootResource, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
3719            res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
3720            res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
3721          rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")");
3722          return rr;
3723        }
3724      }
3725    }
3726    return null;
3727  }
3728
3729  private NodeStack findInParams(NodeStack params, String ref) {
3730    int i = 0;
3731    for (Element child : params.getElement().getChildren("parameter")) {
3732      NodeStack p = params.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
3733      if (child.hasChild("resource")) {
3734        Element res = child.getNamedChild("resource");
3735        if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
3736          return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
3737        }
3738      }
3739      NodeStack pc = findInParamParts(p, child, ref);
3740      if (pc != null) {
3741        return pc;
3742      }
3743    }
3744    return null;
3745  }
3746
3747  private NodeStack findInParamParts(NodeStack pp, Element param, String ref) {
3748    int i = 0;
3749    for (Element child : param.getChildren("part")) {
3750      NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
3751      if (child.hasChild("resource")) {
3752        Element res = child.getNamedChild("resource");
3753        if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
3754          return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
3755        }
3756      }
3757      NodeStack pc = findInParamParts(p, child, ref);
3758      if (pc != null) {
3759        return pc;
3760      }
3761    }
3762    return null;
3763  }
3764
3765  private Element getEntryForSource(Element bundle, Element element) {
3766    List<Element> entries = new ArrayList<Element>();
3767    bundle.getNamedChildren(ENTRY, entries);
3768    for (Element entry : entries) {
3769      if (entry.hasDescendant(element)) {
3770        return entry;
3771      }
3772    }
3773    return null;
3774  }
3775
3776  private ResolvedReference makeExternalRef(Element external, String path) {
3777    ResolvedReference res = new ResolvedReference();
3778    res.setResource(external);
3779    res.setFocus(external);
3780    res.setExternal(true);
3781    res.setStack(new NodeStack(context, external, path, validationLanguage));
3782    return res;
3783  }
3784
3785
3786  private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException {
3787    Element local = localResolve(ref, stack, errors, path, null, null, null).getFocus();
3788    if (local != null)
3789      return local;
3790    if (fetcher == null)
3791      return null;
3792    if (fetchCache.containsKey(ref)) {
3793      return fetchCache.get(ref);
3794    } else {
3795      Element res = fetcher.fetch(this, appContext, ref);
3796      setParents(res);
3797      fetchCache.put(ref, res);
3798      return res;
3799    }
3800  }
3801
3802
3803  private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) {
3804    for (ElementDefinition ed : snapshot.getElement())
3805      if (contentReference.equals("#" + ed.getId()))
3806        return ed;
3807    return null;
3808  }
3809
3810  private StructureDefinition resolveProfile(StructureDefinition profile, String pr) {
3811    if (pr.startsWith("#")) {
3812      for (Resource r : profile.getContained()) {
3813        if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition)
3814          return (StructureDefinition) r;
3815      }
3816      return null;
3817    } else {
3818      long t = System.nanoTime();
3819      StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr);
3820      timeTracker.sd(t);
3821      return fr;
3822    }
3823  }
3824
3825  private ElementDefinition resolveType(String type, List<TypeRefComponent> list) {
3826    for (TypeRefComponent tr : list) {
3827      String url = tr.getWorkingCode();
3828      if (!Utilities.isAbsoluteUrl(url))
3829        url = "http://hl7.org/fhir/StructureDefinition/" + url;
3830      long t = System.nanoTime();
3831      StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
3832      timeTracker.sd(t);
3833      if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot())
3834        return sd.getSnapshot().getElement().get(0);
3835    }
3836    return null;
3837  }
3838
3839  public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) {
3840    this.anyExtensionsAllowed = anyExtensionsAllowed;
3841  }
3842
3843  public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
3844    bpWarnings = value;
3845    return this;
3846  }
3847
3848  @Override
3849  public void setCheckDisplay(CheckDisplayOption checkDisplay) {
3850    this.checkDisplay = checkDisplay;
3851  }
3852
3853  public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) {
3854    this.suppressLoincSnomedMessages = suppressLoincSnomedMessages;
3855  }
3856
3857  public IdStatus getResourceIdRule() {
3858    return resourceIdRule;
3859  }
3860
3861  public void setResourceIdRule(IdStatus resourceIdRule) {
3862    this.resourceIdRule = resourceIdRule;
3863  }
3864
3865
3866  public boolean isAllowXsiLocation() {
3867    return allowXsiLocation;
3868  }
3869
3870  public void setAllowXsiLocation(boolean allowXsiLocation) {
3871    this.allowXsiLocation = allowXsiLocation;
3872  }
3873
3874  /**
3875   * @param element - the candidate that might be in the slice
3876   * @param path    - for reporting any errors. the XPath for the element
3877   * @param slicer  - the definition of how slicing is determined
3878   * @param ed      - the slice for which to test membership
3879   * @param errors
3880   * @param stack
3881   * @param srcProfile 
3882   * @return
3883   * @throws DefinitionException
3884   * @throws DefinitionException
3885   * @throws IOException
3886   * @throws FHIRException
3887   */
3888  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 {
3889    if (!slicer.getSlicing().hasDiscriminator())
3890      return false; // cannot validate in this case
3891
3892    ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache");
3893    if (n == null) {
3894      long t = System.nanoTime();
3895      // GG: this approach is flawed because it treats discriminators individually rather than collectively
3896      StringBuilder expression = new StringBuilder("true");
3897      boolean anyFound = false;
3898      Set<String> discriminators = new HashSet<>();
3899      for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) {
3900        String discriminator = s.getPath();
3901        discriminators.add(discriminator);
3902
3903        List<ElementDefinition> criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile);
3904        boolean found = false;
3905        for (ElementDefinition criteriaElement : criteriaElements) {
3906          found = true;
3907          if (s.getType() == DiscriminatorType.TYPE) {
3908            String type = null;
3909            if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) {
3910              discriminator = discriminator.substring(0, discriminator.indexOf('['));
3911              String lastNode = tail(discriminator);
3912              type = tail(criteriaElement.getPath()).substring(lastNode.length());
3913              type = type.substring(0, 1).toLowerCase() + type.substring(1);
3914            } else if (!criteriaElement.hasType() || criteriaElement.getType().size() == 1) {
3915              if (discriminator.contains("["))
3916                discriminator = discriminator.substring(0, discriminator.indexOf('['));
3917              if (criteriaElement.hasType()) {
3918                type = criteriaElement.getType().get(0).getWorkingCode();
3919              } else if (!criteriaElement.getPath().contains(".")) {
3920                type = criteriaElement.getPath();
3921              } else {
3922                throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getUrl()));
3923              }
3924            } else if (criteriaElement.getType().size() > 1) {
3925              throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_MULTIPLE_TYPES_, discriminator, ed.getId(), profile.getUrl(), criteriaElement.typeSummary()));
3926            } else
3927              throw new DefinitionException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_TYPE_BUT_SLICE__IN__HAS_NO_TYPES, discriminator, ed.getId(), profile.getUrl()));
3928            if (discriminator.isEmpty())
3929              expression.append(" and $this is " + type);
3930            else
3931              expression.append(" and " + discriminator + " is " + type);
3932          } else if (s.getType() == DiscriminatorType.PROFILE) {
3933            if (criteriaElement.getType().size() == 0) {
3934              throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl()));
3935            }
3936            if (criteriaElement.getType().size() != 1) {
3937              throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_ONLY_ONE_TYPE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl()));
3938            }
3939            List<CanonicalType> list = discriminator.endsWith(".resolve()") || discriminator.equals("resolve()") ? criteriaElement.getType().get(0).getTargetProfile() : criteriaElement.getType().get(0).getProfile();
3940            if (list.size() == 0) {
3941              throw new DefinitionException(context.formatMessage(I18nConstants.PROFILE_BASED_DISCRIMINATORS_MUST_HAVE_A_TYPE_WITH_A_PROFILE__IN_PROFILE_, criteriaElement.getId(), profile.getUrl()));
3942            } else if (list.size() > 1) {
3943              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(" or ");
3944              for (CanonicalType c : list) {
3945                b.append(discriminator + ".conformsTo('" + c.getValue() + "')");
3946              }
3947              expression.append(" and (" + b + ")");
3948            } else {
3949              expression.append(" and " + discriminator + ".conformsTo('" + list.get(0).getValue() + "')");
3950            }
3951          } else if (s.getType() == DiscriminatorType.EXISTS) {
3952            if (criteriaElement.hasMin() && criteriaElement.getMin() >= 1)
3953              expression.append(" and (" + discriminator + ".exists())");
3954            else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0"))
3955              expression.append(" and (" + discriminator + ".exists().not())");
3956            else
3957              throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR__IS_BASED_ON_ELEMENT_EXISTENCE_BUT_SLICE__NEITHER_SETS_MIN1_OR_MAX0, discriminator, ed.getId()));
3958          } else if (criteriaElement.hasFixed()) {
3959            buildFixedExpression(ed, expression, discriminator, criteriaElement);
3960          } else if (criteriaElement.hasPattern()) {
3961            buildPattternExpression(ed, expression, discriminator, criteriaElement);
3962          } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().hasValueSet()) {
3963            expression.append(" and (" + discriminator + " memberOf '" + criteriaElement.getBinding().getValueSet() + "')");
3964          } else {
3965            found = false;
3966          }
3967          if (found)
3968            break;
3969        }
3970        if (found)
3971          anyFound = true;
3972      }
3973      if (!anyFound) {
3974        if (slicer.getSlicing().getDiscriminator().size() > 1)
3975          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));
3976        else
3977          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));
3978      }
3979
3980      try {
3981        n = fpe.parse(fixExpr(expression.toString(), null));
3982      } catch (FHIRLexerException e) {
3983        if (STACK_TRACE) e.printStackTrace();
3984        throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, expression, profile.getUrl(), path, e.getMessage()));
3985      }
3986      timeTracker.fpe(t);
3987      ed.setUserData("slice.expression.cache", n);
3988    }
3989
3990    ValidatorHostContext shc = hostContext.forSlicing();
3991    boolean pass = evaluateSlicingExpression(shc, element, path, profile, n);
3992    if (!pass) {
3993      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);
3994      for (String url : shc.getSliceRecords().keySet()) {
3995        slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, isProfile(slicer), 
3996         context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url),
3997          context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__,
3998              url,
3999              stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url))), errorSummaryForSlicingAsText(shc.getSliceRecords().get(url)));
4000      }
4001    }
4002    return pass;
4003  }
4004
4005  private boolean isProfile(ElementDefinition slicer) {
4006    if (slicer == null || !slicer.hasSlicing()) {
4007      return false;
4008    }
4009    for (ElementDefinitionSlicingDiscriminatorComponent t : slicer.getSlicing().getDiscriminator()) {
4010      if (t.getType() == DiscriminatorType.PROFILE) {
4011        return true;
4012      }
4013    }
4014    return false;
4015  }
4016
4017  public boolean evaluateSlicingExpression(ValidatorHostContext hostContext, Element element, String path, StructureDefinition profile, ExpressionNode n) throws FHIRException {
4018    String msg;
4019    boolean ok;
4020    try {
4021      long t = System.nanoTime();
4022      ok = fpe.evaluateToBoolean(hostContext.forProfile(profile), hostContext.getResource(), hostContext.getRootResource(), element, n);
4023      timeTracker.fpe(t);
4024      msg = fpe.forLog();
4025    } catch (Exception ex) {
4026      if (STACK_TRACE) ex.printStackTrace();
4027      throw new FHIRException(context.formatMessage(I18nConstants.PROBLEM_EVALUATING_SLICING_EXPRESSION_FOR_ELEMENT_IN_PROFILE__PATH__FHIRPATH___, profile.getUrl(), path, n, ex.getMessage()));
4028    }
4029    return ok;
4030  }
4031
4032  private void buildPattternExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException {
4033    DataType pattern = criteriaElement.getPattern();
4034    if (pattern instanceof CodeableConcept) {
4035      CodeableConcept cc = (CodeableConcept) pattern;
4036      expression.append(" and ");
4037      buildCodeableConceptExpression(ed, expression, discriminator, cc);
4038    } else if (pattern instanceof Coding) {
4039      Coding c = (Coding) pattern;
4040      expression.append(" and ");
4041      buildCodingExpression(ed, expression, discriminator, c);
4042    } else if (pattern instanceof BooleanType || pattern instanceof IntegerType || pattern instanceof DecimalType) {
4043      expression.append(" and ");
4044      buildPrimitiveExpression(ed, expression, discriminator, pattern, false);
4045    } else if (pattern instanceof PrimitiveType) {
4046      expression.append(" and ");
4047      buildPrimitiveExpression(ed, expression, discriminator, pattern, true);
4048    } else if (pattern instanceof Identifier) {
4049      Identifier ii = (Identifier) pattern;
4050      expression.append(" and ");
4051      buildIdentifierExpression(ed, expression, discriminator, ii);
4052    } else if (pattern instanceof HumanName) {
4053      HumanName name = (HumanName) pattern;
4054      expression.append(" and ");
4055      buildHumanNameExpression(ed, expression, discriminator, name);
4056    } else if (pattern instanceof Address) {
4057      Address add = (Address) pattern;
4058      expression.append(" and ");
4059      buildAddressExpression(ed, expression, discriminator, add);
4060    } else {
4061      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_PATTERN_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), pattern.fhirType()));
4062    }
4063  }
4064
4065  private void buildIdentifierExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Identifier ii)
4066    throws DefinitionException {
4067    if (ii.hasExtension())
4068      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4069    boolean first = true;
4070    expression.append(discriminator + ".where(");
4071    if (ii.hasSystem()) {
4072      first = false;
4073      expression.append("system = '" + ii.getSystem() + "'");
4074    }
4075    if (ii.hasValue()) {
4076      if (first)
4077        first = false;
4078      else
4079        expression.append(" and ");
4080      expression.append("value = '" + ii.getValue() + "'");
4081    }
4082    if (ii.hasUse()) {
4083      if (first)
4084        first = false;
4085      else
4086        expression.append(" and ");
4087      expression.append("use = '" + ii.getUse() + "'");
4088    }
4089    if (ii.hasType()) {
4090      if (first)
4091        first = false;
4092      else
4093        expression.append(" and ");
4094      buildCodeableConceptExpression(ed, expression, TYPE, ii.getType());
4095    }
4096    if (first) {
4097      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), ii.fhirType()));
4098    }
4099    expression.append(").exists()");
4100  }
4101
4102  private void buildHumanNameExpression(ElementDefinition ed, StringBuilder expression, String discriminator, HumanName name) throws DefinitionException {
4103    if (name.hasExtension())
4104      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4105    boolean first = true;
4106    expression.append(discriminator + ".where(");
4107    if (name.hasUse()) {
4108      first = false;
4109      expression.append("use = '" + name.getUse().toCode() + "'");
4110    }
4111    if (name.hasText()) {
4112      if (first)
4113        first = false;
4114      else
4115        expression.append(" and ");
4116      expression.append("text = '" + name.getText() + "'");
4117    }
4118    if (name.hasFamily()) {
4119      if (first)
4120        first = false;
4121      else
4122        expression.append(" and ");
4123      expression.append("family = '" + name.getFamily() + "'");
4124    }
4125    if (name.hasGiven()) {
4126      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "given"));
4127    }
4128    if (name.hasPrefix()) {
4129      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "prefix"));
4130    }
4131    if (name.hasSuffix()) {
4132      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "suffix"));
4133    }
4134    if (name.hasPeriod()) {
4135      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType(), "period"));
4136    }
4137    if (first) {
4138      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), name.fhirType()));
4139    }
4140
4141    expression.append(").exists()");
4142  }
4143
4144  private void buildAddressExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Address add) throws DefinitionException {
4145    if (add.hasExtension()) {
4146      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4147    }
4148    boolean first = true;
4149    expression.append(discriminator + ".where(");
4150    if (add.hasUse()) {
4151      first = false;
4152      expression.append("use = '" + add.getUse().toCode() + "'");
4153    }
4154    if (add.hasType()) {
4155      if (first) first = false; else expression.append(" and ");
4156      expression.append("type = '" + add.getType().toCode() + "'");
4157    }
4158    if (add.hasText()) {
4159      if (first) first = false; else expression.append(" and ");
4160      expression.append("text = '" + add.getText() + "'");
4161    }
4162    if (add.hasCity()) {
4163      if (first) first = false; else expression.append(" and ");
4164      expression.append("city = '" + add.getCity() + "'");
4165    }
4166    if (add.hasDistrict()) {
4167      if (first) first = false; else expression.append(" and ");
4168      expression.append("district = '" + add.getDistrict() + "'");
4169    }
4170    if (add.hasState()) {
4171      if (first) first = false; else expression.append(" and ");
4172      expression.append("state = '" + add.getState() + "'");
4173    }
4174    if (add.hasPostalCode()) {
4175      if (first) first = false; else expression.append(" and ");
4176      expression.append("postalCode = '" + add.getPostalCode() + "'");
4177    }
4178    if (add.hasCountry()) {
4179      if (first) first = false; else expression.append(" and ");
4180      expression.append("country = '" + add.getCountry() + "'");
4181    }       
4182    if (add.hasLine()) {
4183      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "line"));
4184    }
4185    if (add.hasPeriod()) {
4186      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType(), "period"));
4187    }
4188    if (first) {
4189      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), add.fhirType()));
4190    }
4191    expression.append(").exists()");
4192  }
4193
4194  private void buildCodeableConceptExpression(ElementDefinition ed, StringBuilder expression, String discriminator, CodeableConcept cc)
4195    throws DefinitionException {
4196    if (cc.hasText())
4197      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__USING_TEXT__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4198    if (!cc.hasCoding())
4199      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__MUST_HAVE_AT_LEAST_ONE_CODING__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4200    if (cc.hasExtension())
4201      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4202    boolean firstCoding = true;
4203    for (Coding c : cc.getCoding()) {
4204      if (c.hasExtension())
4205        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4206      if (firstCoding) firstCoding = false;
4207      else expression.append(" and ");
4208      expression.append(discriminator + ".coding.where(");
4209      boolean first = true;
4210      if (c.hasSystem()) {
4211        first = false;
4212        expression.append("system = '" + c.getSystem() + "'");
4213      }
4214      if (c.hasVersion()) {
4215        if (first) first = false;
4216        else expression.append(" and ");
4217        expression.append("version = '" + c.getVersion() + "'");
4218      }
4219      if (c.hasCode()) {
4220        if (first) first = false;
4221        else expression.append(" and ");
4222        expression.append("code = '" + c.getCode() + "'");
4223      }
4224      if (c.hasDisplay()) {
4225        if (first) first = false;
4226        else expression.append(" and ");
4227        expression.append("display = '" + c.getDisplay() + "'");
4228      }
4229      if (first) {
4230        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), cc.fhirType()));
4231      }
4232      expression.append(").exists()");
4233    }
4234  }
4235
4236  private void buildCodingExpression(ElementDefinition ed, StringBuilder expression, String discriminator, Coding c)
4237    throws DefinitionException {
4238    if (c.hasExtension())
4239      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4240    expression.append(discriminator + ".where(");
4241    boolean first = true;
4242    if (c.hasSystem()) {
4243      first = false;
4244      expression.append("system = '" + c.getSystem() + "'");
4245    }
4246    if (c.hasVersion()) {
4247      if (first) first = false;
4248      else expression.append(" and ");
4249      expression.append("version = '" + c.getVersion() + "'");
4250    }
4251    if (c.hasCode()) {
4252      if (first) first = false;
4253      else expression.append(" and ");
4254      expression.append("code = '" + c.getCode() + "'");
4255    }
4256    if (c.hasDisplay()) {
4257      if (first) first = false;
4258      else expression.append(" and ");
4259      expression.append("display = '" + c.getDisplay() + "'");
4260    }
4261    if (first) {
4262      throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_IDENTIFIER_PATTERN_NO_PROPERTY_NOT_SUPPORTED_FOR_DISCRIMINATOR_FOR_SLICE, discriminator, ed.getId(), c.fhirType()));
4263    }
4264    expression.append(").exists()");
4265  }
4266
4267  private void buildPrimitiveExpression(ElementDefinition ed, StringBuilder expression, String discriminator, DataType p, boolean quotes) throws DefinitionException {
4268      if (p.hasExtension())
4269        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_CODEABLECONCEPT_PATTERN__EXTENSIONS_ARE_NOT_ALLOWED__FOR_DISCRIMINATOR_FOR_SLICE_, discriminator, ed.getId()));
4270      if (quotes) {        
4271        expression.append(discriminator + ".where(value = '" + p.primitiveValue() + "'");
4272      } else {
4273        expression.append(discriminator + ".where(value = " + p.primitiveValue() + "");
4274      }
4275      expression.append(").exists()");
4276    }
4277
4278  private void buildFixedExpression(ElementDefinition ed, StringBuilder expression, String discriminator, ElementDefinition criteriaElement) throws DefinitionException {
4279    DataType fixed = criteriaElement.getFixed();
4280    if (fixed instanceof CodeableConcept) {
4281      CodeableConcept cc = (CodeableConcept) fixed;
4282      expression.append(" and ");
4283      buildCodeableConceptExpression(ed, expression, discriminator, cc);
4284    } else if (fixed instanceof Identifier) {
4285      Identifier ii = (Identifier) fixed;
4286      expression.append(" and ");
4287      buildIdentifierExpression(ed, expression, discriminator, ii);
4288    } else if (fixed instanceof Coding) {
4289      Coding c = (Coding) fixed;
4290      expression.append(" and ");
4291      buildCodingExpression(ed, expression, discriminator, c);
4292    } else {
4293      expression.append(" and (");
4294      if (fixed instanceof StringType) {
4295        Gson gson = new Gson();
4296        String json = gson.toJson((StringType) fixed);
4297        String escapedString = json.substring(json.indexOf(":") + 2);
4298        escapedString = escapedString.substring(0, escapedString.indexOf(",\"myStringValue") - 1);
4299        expression.append("'" + escapedString + "'");
4300      } else if (fixed instanceof UriType) {
4301        expression.append("'" + ((UriType) fixed).asStringValue() + "'");
4302      } else if (fixed instanceof IntegerType) {
4303        expression.append(((IntegerType) fixed).asStringValue());
4304      } else if (fixed instanceof DecimalType) {
4305        expression.append(((IntegerType) fixed).asStringValue());
4306      } else if (fixed instanceof BooleanType) {
4307        expression.append(((BooleanType) fixed).asStringValue());
4308      } else
4309        throw new DefinitionException(context.formatMessage(I18nConstants.UNSUPPORTED_FIXED_VALUE_TYPE_FOR_DISCRIMINATOR_FOR_SLICE__, discriminator, ed.getId(), fixed.getClass().getName()));
4310      expression.append(" in " + discriminator + ")");
4311    }
4312  }
4313
4314  // checkSpecials = we're only going to run these tests if we are actually validating this content (as opposed to we looked it up)
4315  private void start(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack) throws FHIRException {
4316    checkLang(resource, stack);
4317    if (crumbTrails) {
4318      element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST, defn.getUrl()));
4319    }
4320
4321    if (BUNDLE.equals(element.fhirType())) {
4322      resolveBundleReferences(element, new ArrayList<Element>());
4323    }
4324    startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials());
4325
4326    Element meta = element.getNamedChild(META);
4327    if (meta != null) {
4328      List<Element> profiles = new ArrayList<Element>();
4329      meta.getNamedChildren("profile", profiles);
4330      int i = 0;
4331      for (Element profile : profiles) {
4332        StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue());
4333        if (!defn.getUrl().equals(profile.primitiveValue())) {
4334          // is this a version specific reference? 
4335          VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue());
4336          if (vu != null) {
4337            if (!VersionUtilities.versionsCompatible(vu.getVersion(),  context.getVersion())) {
4338              hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_OTHER_VERSION, vu.getVersion());
4339            } else if (vu.getUrl().equals(defn.getUrl())) {
4340              hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK);              
4341            } else {
4342              StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl());
4343              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());                            
4344            }
4345          } else {
4346            if (sd == null) {
4347              // we'll try fetching it directly from it's source, but this is likely to fail later even if the resolution succeeds
4348              if (fetcher == null) {
4349                warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue());
4350              } else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) {
4351                warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue());                
4352              } else {
4353                try {
4354                  sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, profile.primitiveValue());
4355                } catch (Exception e) {
4356                  if (STACK_TRACE) e.printStackTrace();
4357                  warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage());                
4358                }
4359                if (sd != null) {
4360                  context.cacheResource(sd);
4361                }
4362              }
4363            }
4364            if (sd != null) {
4365              if (crumbTrails) {
4366                element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl()));
4367              }
4368              stack.resetIds();
4369              startInner(hostContext, errors, resource, element, sd, stack, false);
4370            }
4371          }
4372        }
4373        i++;
4374      }
4375    }
4376    String rt = element.fhirType();
4377    for (ImplementationGuide ig : igs) {
4378      for (ImplementationGuideGlobalComponent gl : ig.getGlobal()) {
4379        if (rt.equals(gl.getType())) {
4380          StructureDefinition sd = context.fetchResource(StructureDefinition.class, gl.getProfile());
4381          if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), sd != null, I18nConstants.VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN, gl.getProfile())) {
4382            if (crumbTrails) {
4383              element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL, sd.getUrl(), ig.getUrl()));
4384            }
4385            stack.resetIds();
4386            startInner(hostContext, errors, resource, element, sd, stack, false);
4387          }
4388        }
4389      }
4390    }
4391  }
4392
4393  private void resolveBundleReferences(Element element, List<Element> bundles) {
4394    if (!element.hasUserData("validator.bundle.resolved")) {
4395      element.setUserData("validator.bundle.resolved", true);
4396      List<Element> list = new ArrayList<Element>();
4397      list.addAll(bundles);
4398      list.add(0, element);
4399      List<Element> entries = element.getChildrenByName(ENTRY);
4400      for (Element entry : entries) {
4401        String fu = entry.getChildValue(FULL_URL);
4402        Element r = entry.getNamedChild(RESOURCE);
4403        if (r != null) {
4404          resolveBundleReferencesInResource(list, r, fu);
4405        }
4406      }
4407    }
4408  }
4409
4410  private void resolveBundleReferencesInResource(List<Element> bundles, Element r, String fu) {
4411    r.setUserData("validator.bundle.resolution-resource", null);
4412    if (BUNDLE.equals(r.fhirType())) {
4413      resolveBundleReferences(r, bundles);
4414    } else {
4415      for (Element child : r.getChildren()) {
4416        resolveBundleReferencesForElement(bundles, r, fu, child);
4417      }
4418    }
4419  }
4420
4421  private void resolveBundleReferencesForElement(List<Element> bundles, Element resource, String fu, Element element) {
4422    if ("Reference".equals(element.fhirType())) {
4423      String ref = element.getChildValue("reference");
4424      if (!Utilities.noString(ref)) {
4425        for (Element bundle : bundles) {
4426          List<Element> entries = bundle.getChildren(ENTRY);
4427          Element tgt = resolveInBundle(entries, ref, fu, resource.fhirType(), resource.getIdBase());
4428          if (tgt != null) {
4429            element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE));
4430            return;
4431          }
4432        }
4433        element.setUserData("validator.bundle.resolution-failed", ref);
4434      }
4435    } else {
4436      element.setUserData("validator.bundle.resolution-noref", null);
4437      for (Element child : element.getChildren()) {
4438        resolveBundleReferencesForElement(bundles, resource, fu, child);
4439      }
4440    }
4441
4442  }
4443
4444  public void startInner(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack, boolean checkSpecials) {
4445    // the first piece of business is to see if we've validated this resource against this profile before.
4446    // if we have (*or if we still are*), then we'll just return our existing errors
4447    ResourceValidationTracker resTracker = getResourceTracker(element);
4448    List<ValidationMessage> cachedErrors = resTracker.getOutcomes(defn);
4449    if (cachedErrors != null) {
4450      for (ValidationMessage vm : cachedErrors) {
4451        if (!errors.contains(vm)) {
4452          errors.add(vm);
4453        }
4454      }
4455      return;
4456    }
4457    if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(), I18nConstants.VALIDATION_VAL_PROFILE_NOSNAPSHOT, defn.getUrl())) {
4458      List<ValidationMessage> localErrors = new ArrayList<ValidationMessage>();
4459      resTracker.startValidating(defn);
4460      trackUsage(defn, hostContext, element);
4461      validateElement(hostContext, localErrors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false, true, null);
4462      resTracker.storeOutcomes(defn, localErrors);
4463      for (ValidationMessage vm : localErrors) {
4464        if (!errors.contains(vm)) {
4465          errors.add(vm);
4466        }
4467      }
4468    }
4469    if (checkSpecials) {
4470      checkSpecials(hostContext, errors, element, stack, checkSpecials);
4471      validateResourceRules(errors, element, stack);
4472    }
4473  }
4474
4475  public void checkSpecials(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element element, NodeStack stack, boolean checkSpecials) {
4476    // specific known special validations
4477    if (element.getType().equals(BUNDLE)) {
4478      new BundleValidator(context, serverBase, this, xverManager).validateBundle(errors, element, stack, checkSpecials, hostContext);
4479    } else if (element.getType().equals("Observation")) {
4480      validateObservation(errors, element, stack);
4481    } else if (element.getType().equals("Questionnaire")) {
4482      new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaire(errors, element, element, stack);
4483    } else if (element.getType().equals("QuestionnaireResponse")) {
4484      new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager).validateQuestionannaireResponse(hostContext, errors, element, stack);
4485    } else if (element.getType().equals("Measure")) {
4486      new MeasureValidator(context, timeTracker, xverManager).validateMeasure(hostContext, errors, element, stack);      
4487    } else if (element.getType().equals("MeasureReport")) {
4488      new MeasureValidator(context, timeTracker, xverManager).validateMeasureReport(hostContext, errors, element, stack);
4489    } else if (element.getType().equals("CapabilityStatement")) {
4490      validateCapabilityStatement(errors, element, stack);
4491    } else if (element.getType().equals("CodeSystem")) {
4492      new CodeSystemValidator(context, timeTracker, xverManager).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang()));
4493    } else if (element.getType().equals("SearchParameter")) {
4494      new SearchParameterValidator(context, timeTracker, fpe, xverManager).validateSearchParameter(errors, element, stack);
4495    } else if (element.getType().equals("StructureDefinition")) {
4496      new StructureDefinitionValidator(context, timeTracker, fpe, wantCheckSnapshotUnchanged, xverManager).validateStructureDefinition(errors, element, stack);
4497    } else if (element.getType().equals("ValueSet")) {
4498      new ValueSetValidator(context, timeTracker, this, xverManager).validateValueSet(errors, element, stack);
4499    }
4500  }
4501
4502  private ResourceValidationTracker getResourceTracker(Element element) {
4503    ResourceValidationTracker res = resourceTracker.get(element);
4504    if (res == null) {
4505      res = new ResourceValidationTracker();
4506      resourceTracker.put(element, res);
4507    }
4508    return res;
4509  }
4510
4511  private void checkLang(Element resource, NodeStack stack) {
4512    String lang = resource.getNamedChildValue("language");
4513    if (!Utilities.noString(lang))
4514      stack.setWorkingLang(lang);
4515  }
4516
4517  private void validateResourceRules(List<ValidationMessage> errors, Element element, NodeStack stack) {
4518    String lang = element.getNamedChildValue("language");
4519    Element text = element.getNamedChild("text");
4520    if (text != null) {
4521      Element div = text.getNamedChild("div");
4522      if (lang != null && div != null) {
4523        XhtmlNode xhtml = div.getXhtml();
4524        String l = xhtml.getAttribute("lang");
4525        String xl = xhtml.getAttribute("xml:lang");
4526        if (l == null && xl == null) {
4527          warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING1);
4528        } else {
4529          if (l == null) {
4530            warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING2);
4531          } else if (!l.equals(lang)) {
4532            warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT1, lang, l);
4533          }
4534          if (xl == null) {
4535            warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_MISSING3);
4536          } else if (!xl.equals(lang)) {
4537            warning(errors, IssueType.BUSINESSRULE, div.line(), div.col(), stack.getLiteralPath(), false, I18nConstants.LANGUAGE_XHTML_LANG_DIFFERENT2, lang, xl);
4538          }
4539        }
4540      }
4541    }
4542    // security tags are a set (system|code)
4543    Element meta = element.getNamedChild(META);
4544    if (meta != null) {
4545      Set<String> tags = new HashSet<>();
4546      List<Element> list = new ArrayList<>();
4547      meta.getNamedChildren("security", list);
4548      int i = 0;
4549      for (Element e : list) {
4550        String s = e.getNamedChildValue("system") + "#" + e.getNamedChildValue("code");
4551        rule(errors, IssueType.BUSINESSRULE, e.line(), e.col(), stack.getLiteralPath() + ".meta.profile[" + Integer.toString(i) + "]", !tags.contains(s), I18nConstants.META_RES_SECURITY_DUPLICATE, s);
4552        tags.add(s);
4553        i++;
4554      }
4555    }
4556  }
4557
4558  private void validateCapabilityStatement(List<ValidationMessage> errors, Element cs, NodeStack stack) {
4559    int iRest = 0;
4560    for (Element rest : cs.getChildrenByName("rest")) {
4561      int iResource = 0;
4562      for (Element resource : rest.getChildrenByName(RESOURCE)) {
4563        int iSP = 0;
4564        for (Element searchParam : resource.getChildrenByName("searchParam")) {
4565          String ref = searchParam.getChildValue("definition");
4566          String type = searchParam.getChildValue(TYPE);
4567          if (!Utilities.noString(ref)) {
4568            SearchParameter sp = context.fetchResource(SearchParameter.class, ref);
4569            if (sp != null) {
4570              rule(errors, IssueType.INVALID, searchParam.line(), searchParam.col(), stack.getLiteralPath() + ".rest[" + iRest + "].resource[" + iResource + "].searchParam[" + iSP + "]",
4571                sp.getType().toCode().equals(type), I18nConstants.CAPABALITYSTATEMENT_CS_SP_WRONGTYPE, sp.getUrl(), sp.getType().toCode(), type);
4572            }
4573          }
4574          iSP++;
4575        }
4576        iResource++;
4577      }
4578      iRest++;
4579    }
4580  }
4581 
4582  private void validateContains(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path,
4583                                ElementDefinition child, ElementDefinition context, Element resource,
4584                                Element element, NodeStack stack, IdStatus idstatus, StructureDefinition parentProfile) throws FHIRException {
4585
4586    SpecialElement special = element.getSpecial();
4587
4588    ContainedReferenceValidationPolicy containedValidationPolicy = getPolicyAdvisor() == null ?
4589      ContainedReferenceValidationPolicy.CHECK_VALID : getPolicyAdvisor().policyForContained(this,
4590      hostContext, context.fhirType(), context.getId(), special, path, parentProfile.getUrl());
4591
4592    if (containedValidationPolicy.ignore()) {
4593      return;
4594    }
4595
4596    String resourceName = element.getType();
4597    TypeRefComponent typeForResource = null;
4598    CommaSeparatedStringBuilder bt = new CommaSeparatedStringBuilder();
4599
4600    // Iterate through all possible types
4601    for (TypeRefComponent type : child.getType()) {
4602      bt.append(type.getCode());
4603      if (type.getCode().equals("Resource") || type.getCode().equals(resourceName) ) {
4604        typeForResource = type;
4605        break;
4606      }
4607    }
4608
4609    stack.qualifyPath(".ofType("+resourceName+")");
4610
4611    if (typeForResource == null) {
4612      rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
4613        false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName, bt.toString());
4614    } else if (isValidResourceType(resourceName, typeForResource)) {
4615      if (containedValidationPolicy.checkValid()) {
4616        // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
4617        ValidatorHostContext hc = null;
4618        if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) {
4619          resource = element;
4620          assert Utilities.existsInList(hostContext.getRootResource().fhirType(), "Bundle", "Parameters") : "Resource is "+hostContext.getRootResource().fhirType()+", expected Bundle or Parameters";
4621          hc = hostContext.forEntry(element, hostContext.getRootResource()); // root becomes the grouping resource (should be either bundle or parameters)
4622        } else {
4623          hc = hostContext.forContained(element);
4624        }
4625
4626        stack.resetIds();
4627        if (special != null) {
4628          switch (special) {
4629            case BUNDLE_ENTRY:
4630            case BUNDLE_OUTCOME:
4631            case PARAMETER:
4632              idstatus = IdStatus.OPTIONAL;
4633              break;
4634            case CONTAINED:
4635              stack.setContained(true);
4636              idstatus = IdStatus.REQUIRED;
4637              break;
4638            default:
4639              break;
4640          }
4641        }
4642
4643        if (typeForResource.getProfile().size() == 1) {
4644          long t = System.nanoTime();
4645          StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, typeForResource.getProfile().get(0).asStringValue());
4646          timeTracker.sd(t);
4647          trackUsage(profile, hostContext, element);
4648          if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
4649            profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL, special.toHuman(), resourceName, typeForResource.getProfile().get(0).asStringValue())) {
4650            validateResource(hc, errors, resource, element, profile, idstatus, stack);
4651          }
4652        } else if (typeForResource.getProfile().isEmpty()) {
4653          long t = System.nanoTime();
4654          StructureDefinition profile = this.context.fetchResource(StructureDefinition.class,
4655            "http://hl7.org/fhir/StructureDefinition/" + resourceName);
4656          timeTracker.sd(t);
4657          trackUsage(profile, hostContext, element);
4658          if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
4659            profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), resourceName)) {
4660            validateResource(hc, errors, resource, element, profile, idstatus, stack);
4661          }
4662        } else {
4663          CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
4664          for (CanonicalType u : typeForResource.getProfile()) {
4665            b.append(u.asStringValue());
4666          }
4667          rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
4668            false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, special.toHuman(), typeForResource.getCode(), b.toString());
4669        }
4670      }
4671    } else {
4672      List<String> types = new ArrayList<>();
4673      for (UriType u : typeForResource.getProfile()) {
4674        StructureDefinition sd = this.context.fetchResource(StructureDefinition.class, u.getValue());
4675        if (sd != null && !types.contains(sd.getType())) {
4676          types.add(sd.getType());
4677        }
4678      }
4679      if (types.size() == 1) {
4680        rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
4681          false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE2, resourceName, types.get(0));
4682      } else {
4683        rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(),
4684          false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE3, resourceName, types);
4685      }
4686    }
4687  }
4688
4689  private boolean isValidResourceType(String type, TypeRefComponent def) {
4690    if (!def.hasProfile() && def.getCode().equals("Resource")) {
4691      return true;
4692    }
4693    if (def.getCode().equals(type)) {
4694      return true;
4695    }
4696    List<StructureDefinition> list = new ArrayList<>();
4697    for (UriType u : def.getProfile()) {
4698      StructureDefinition sdt = context.fetchResource(StructureDefinition.class, u.getValue());
4699      if (sdt != null) {
4700        list.add(sdt);
4701      }
4702    }
4703
4704    StructureDefinition sdt = context.fetchTypeDefinition(type);
4705    while (sdt != null) {
4706      if (def.getWorkingCode().equals("Resource")) {
4707        for (StructureDefinition sd : list) {
4708          if (sd.getUrl().equals(sdt.getUrl())) {
4709            return true;
4710          }
4711          if (sd.getType().equals(sdt.getType())) {
4712            return true;
4713          }
4714        }
4715      }
4716      sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition());
4717    }
4718    return false;
4719  }
4720
4721
4722  private void validateElement(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
4723    Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, String extensionUrl) throws FHIRException {
4724
4725    String id = element.getChildValue("id");
4726    if (!Utilities.noString(id)) {
4727      if (stack.getIds().containsKey(id) && stack.getIds().get(id) != element) {
4728        rule(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.DUPLICATE_ID, id);
4729      }
4730      if (!stack.isResetPoint()) {
4731        stack.getIds().put(id, element);
4732      }
4733    }
4734    if (definition.getPath().equals("StructureDefinition.snapshot")) {
4735      // work around a known issue in the spec, that ids are duplicated in snapshot and differential 
4736      stack.resetIds();
4737    }
4738    
4739    // check type invariants
4740    checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false);
4741    if (definition.getFixed() != null) {
4742      checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null, false);
4743    } 
4744    if (definition.getPattern() != null) {
4745      checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getUrl(), definition.getSliceName(), null, true);
4746    } 
4747
4748    // get the list of direct defined children, including slices
4749    List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(profile, definition);
4750    if (childDefinitions.isEmpty()) {
4751      if (actualType == null)
4752        return; // there'll be an error elsewhere in this case, and we're going to stop.
4753      childDefinitions = getActualTypeChildren(hostContext, element, actualType);
4754    } else if (definition.getType().size() > 1) {
4755      // this only happens when the profile constrains the abstract children but leaves th choice open.
4756      if (actualType == null)
4757        return; // there'll be an error elsewhere in this case, and we're going to stop.
4758      List<ElementDefinition> typeChildDefinitions = getActualTypeChildren(hostContext, element, actualType);
4759      // 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)
4760      mergeChildLists(childDefinitions, typeChildDefinitions, definition.getPath(), actualType);
4761    }
4762
4763    List<ElementInfo> children = listChildren(element, stack);
4764    List<String> problematicPaths = assignChildren(hostContext, errors, profile, resource, stack, childDefinitions, children);
4765
4766    checkCardinalities(errors, profile, element, stack, childDefinitions, children, problematicPaths);
4767    // 4. check order if any slices are ordered. (todo)
4768
4769    // 5. inspect each child for validity
4770    for (ElementInfo ei : children) {
4771      checkChild(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl);
4772    }
4773  }
4774
4775  private void mergeChildLists(List<ElementDefinition> master, List<ElementDefinition> additional, String masterPath, String typePath) {
4776    for (ElementDefinition ed : additional) {
4777      boolean inMaster = false;
4778      for (ElementDefinition t : master) {
4779        String tp = masterPath + ed.getPath().substring(typePath.length());
4780        if (t.getPath().equals(tp)) {
4781          inMaster = true;
4782        }
4783      }
4784      if (!inMaster) {
4785        master.add(ed);
4786      }
4787    }
4788
4789
4790  }
4791
4792  // todo: the element definition in context might assign a constrained profile for the type?
4793  public List<ElementDefinition> getActualTypeChildren(ValidatorHostContext hostContext, Element element, String actualType) {
4794    List<ElementDefinition> childDefinitions;
4795    StructureDefinition dt = null;
4796    if (isAbsolute(actualType))
4797      dt = this.context.fetchResource(StructureDefinition.class, actualType);
4798    else
4799      dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType);
4800    if (dt == null)
4801      throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_ACTUAL_TYPE_, actualType));
4802    trackUsage(dt, hostContext, element);
4803
4804    childDefinitions = profileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0));
4805    return childDefinitions;
4806  }
4807
4808  public void checkChild(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition,
4809    Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl)
4810    throws FHIRException, DefinitionException {
4811
4812    if (debug && ei.definition != null && ei.slice != null) {
4813      System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against both "+ei.definition.getId()+" and "+ei.slice.getId());
4814    }
4815    if (ei.definition != null) {
4816      if (debug) {
4817        System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against defn "+ei.definition.getId()+" from "+profile.getUrl());
4818      }
4819      checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.definition, false);
4820    }
4821    if (ei.slice != null) {
4822      if (debug) {
4823        System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against slice "+ei.slice.getId());
4824      }
4825      checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true);
4826    }
4827  }
4828
4829  public void checkChildByDefinition(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile,
4830      ElementDefinition definition, Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept,
4831      boolean checkDisplayInContext, ElementInfo ei, String extensionUrl, ElementDefinition checkDefn, boolean isSlice) {
4832    List<String> profiles = new ArrayList<String>();
4833    String type = null;
4834    ElementDefinition typeDefn = null;
4835    checkMustSupport(profile, ei);
4836
4837    if (checkDefn.getType().size() == 1 && !"*".equals(checkDefn.getType().get(0).getWorkingCode()) && !"Element".equals(checkDefn.getType().get(0).getWorkingCode())
4838      && !"BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode())) {
4839      type = checkDefn.getType().get(0).getWorkingCode();
4840      String stype = ei.getElement().fhirType();
4841      if (checkDefn.isChoice() && !stype.equals(type)) {
4842        if ("Extension".equals(profile.getType())) {
4843          // error will be raised elsewhere
4844        } else {
4845          rule(errors, IssueType.STRUCTURE, element.line(), element.col(), ei.getPath(), false, I18nConstants.EXTENSION_PROF_TYPE, profile.getUrl(), type, stype);
4846        }
4847      }
4848
4849      // Excluding reference is a kludge to get around versioning issues
4850      if (checkDefn.getType().get(0).hasProfile()) {
4851        for (CanonicalType p : checkDefn.getType().get(0).getProfile()) {
4852          profiles.add(p.getValue());
4853        }
4854      }
4855    } else if (checkDefn.getType().size() == 1 && "*".equals(checkDefn.getType().get(0).getWorkingCode())) {
4856      String prefix = tail(checkDefn.getPath());
4857      assert prefix.endsWith("[x]");
4858      type = ei.getName().substring(prefix.length() - 3);
4859      if (isPrimitiveType(type))
4860        type = Utilities.uncapitalize(type);
4861      if (checkDefn.getType().get(0).hasProfile()) {
4862        for (CanonicalType p : checkDefn.getType().get(0).getProfile()) {
4863          profiles.add(p.getValue());
4864        }
4865      }
4866    } else if (checkDefn.getType().size() > 1) {
4867
4868      String prefix = tail(checkDefn.getPath());
4869      assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB();
4870
4871      if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
4872        type = ei.getElement().getType();
4873      } else if (ei.getElement().isResource()) {
4874        type = ei.getElement().fhirType();            
4875      } else {
4876        prefix = prefix.substring(0, prefix.length() - 3);
4877        for (TypeRefComponent t : checkDefn.getType())
4878          if ((prefix + Utilities.capitalize(t.getWorkingCode())).equals(ei.getName())) {
4879            type = t.getWorkingCode();
4880            // Excluding reference is a kludge to get around versioning issues
4881            if (t.hasProfile() && !type.equals("Reference"))
4882              profiles.add(t.getProfile().get(0).getValue());
4883          }
4884      }
4885      if (type == null) {
4886        TypeRefComponent trc = checkDefn.getType().get(0);
4887        if (trc.getWorkingCode().equals("Reference"))
4888          type = "Reference";
4889        else
4890          rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTYPE, ei.getName(), describeTypes(checkDefn.getType()));
4891      }
4892    } else if (checkDefn.getContentReference() != null) {
4893      typeDefn = resolveNameReference(profile.getSnapshot(), checkDefn.getContentReference());
4894      
4895    } else if (checkDefn.getType().size() == 1 && ("Element".equals(checkDefn.getType().get(0).getWorkingCode()) || "BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode()))) {
4896      if (checkDefn.getType().get(0).hasProfile()) {
4897        CanonicalType pu = checkDefn.getType().get(0).getProfile().get(0);
4898        if (pu.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT))
4899          profiles.add(pu.getValue() + "#" + pu.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT));
4900        else
4901          profiles.add(pu.getValue());
4902      }
4903    }
4904
4905    if (type != null) {
4906      if (type.startsWith("@")) {
4907        checkDefn = findElement(profile, type.substring(1));
4908        if (isSlice) {
4909          ei.slice = ei.definition;
4910        } else {
4911          ei.definition = ei.definition;            
4912        }
4913        type = null;
4914      }
4915    }
4916    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()));
4917//      if (debug) {
4918//        System.out.println("  check " + localStack.getLiteralPath()+" against "+ei.getDefinition().getId()+" in profile "+profile.getUrl());
4919//      }
4920    String localStackLiteralPath = localStack.getLiteralPath();
4921    String eiPath = ei.getPath();
4922    if (!eiPath.equals(localStackLiteralPath)) {
4923      assert (eiPath.equals(localStackLiteralPath)) : "ei.path: " + ei.getPath() + "  -  localStack.getLiteralPath: " + localStackLiteralPath;
4924    }
4925    boolean thisIsCodeableConcept = false;
4926    String thisExtension = null;
4927    boolean checkDisplay = true;
4928
4929    SpecialElement special = ei.getElement().getSpecial();
4930    if (special == SpecialElement.BUNDLE_ENTRY || special == SpecialElement.BUNDLE_OUTCOME || special == SpecialElement.PARAMETER) {
4931      checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, ei.getElement(), ei.getElement(), localStack, false);
4932    } else {
4933      checkInvariants(hostContext, errors, profile, typeDefn != null ? typeDefn : checkDefn, resource, ei.getElement(), localStack, false);
4934    }
4935
4936    ei.getElement().markValidation(profile, checkDefn);
4937    boolean elementValidated = false;
4938    if (type != null) {
4939      if (isPrimitiveType(type)) {
4940        checkPrimitive(hostContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, stack);
4941      } else {
4942        if (checkDefn.hasFixed()) {
4943          checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getUrl(), checkDefn.getSliceName(), null, false);
4944        }
4945        if (checkDefn.hasPattern()) {
4946          checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getUrl(), checkDefn.getSliceName(), null, true);
4947        }
4948      }
4949      if (type.equals("Identifier")) {
4950        checkIdentifier(errors, ei.getPath(), ei.getElement(), checkDefn);
4951      } else if (type.equals("Coding")) {
4952        checkCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack);
4953      } else if (type.equals("Quantity")) {
4954        checkQuantity(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack);
4955      } else if (type.equals("Attachment")) {
4956        checkAttachment(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack);
4957      } else if (type.equals("CodeableConcept")) {
4958        checkDisplay = checkCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack);
4959        thisIsCodeableConcept = true;
4960      } else if (type.equals("Reference")) {
4961        checkReference(hostContext, errors, ei.getPath(), ei.getElement(), profile, checkDefn, actualType, localStack);
4962        // 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
4963      } else if (type.equals("Extension")) {
4964        Element eurl = ei.getElement().getNamedChild("url");
4965        if (rule(errors, IssueType.INVALID, ei.getPath(), eurl != null, I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) {
4966          String url = eurl.primitiveValue();
4967          thisExtension = url;
4968          if (rule(errors, IssueType.INVALID, ei.getPath(), !Utilities.noString(url), I18nConstants.EXTENSION_EXT_URL_NOTFOUND)) {
4969            if (rule(errors, IssueType.INVALID, ei.getPath(), (extensionUrl != null) || Utilities.isAbsoluteUrl(url), I18nConstants.EXTENSION_EXT_URL_ABSOLUTE)) {
4970              checkExtension(hostContext, errors, ei.getPath(), resource, element, ei.getElement(), checkDefn, profile, localStack, stack, extensionUrl);
4971            }
4972          }
4973        }
4974      } else if (type.equals("Resource") || isResource(type)) {
4975        validateContains(hostContext, errors, ei.getPath(), checkDefn, definition, resource, ei.getElement(),
4976          localStack, idStatusForEntry(element, ei), profile); // if
4977        elementValidated = true;
4978        // (str.matches(".*([.,/])work\\1$"))
4979      } else if (Utilities.isAbsoluteUrl(type)) {
4980        StructureDefinition defn = context.fetchTypeDefinition(type);
4981        if (defn != null && hasMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep())) {
4982          List<String> txtype = getMapping("http://hl7.org/fhir/terminology-pattern", defn, defn.getSnapshot().getElementFirstRep());
4983          if (txtype.contains("CodeableConcept")) {
4984            checkTerminologyCodeableConcept(errors, ei.getPath(), ei.getElement(), profile, checkDefn, stack, defn);
4985            thisIsCodeableConcept = true;
4986          } else if (txtype.contains("Coding")) {
4987            checkTerminologyCoding(errors, ei.getPath(), ei.getElement(), profile, checkDefn, inCodeableConcept, checkDisplayInContext, stack, defn);
4988          }
4989        }
4990      }
4991    } else {
4992      if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), checkDefn != null, I18nConstants.VALIDATION_VAL_CONTENT_UNKNOWN, ei.getName()))
4993        validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, false, true, null);
4994    }
4995    StructureDefinition p = null;
4996    String tail = null;
4997    if (profiles.isEmpty()) {
4998      if (type != null) {
4999        p = getProfileForType(type, checkDefn.getType());
5000
5001        // If dealing with a primitive type, then we need to check the current child against
5002        // the invariants (constraints) on the current element, because otherwise it only gets
5003        // checked against the primary type's invariants: LLoyd
5004        //if (p.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
5005        //  checkInvariants(hostContext, errors, ei.path, profile, ei.definition, null, null, resource, ei.element);
5006        //}
5007
5008        rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_NOTYPE, type);
5009      }
5010    } else if (profiles.size() == 1) {
5011      String url = profiles.get(0);
5012      if (url.contains("#")) {
5013        tail = url.substring(url.indexOf("#") + 1);
5014        url = url.substring(0, url.indexOf("#"));
5015      }
5016      p = this.context.fetchResource(StructureDefinition.class, url);
5017      rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, profiles.get(0));
5018    } else {
5019      elementValidated = true;
5020      HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>();
5021      HashMap<String, List<ValidationMessage>> badProfiles = new HashMap<String, List<ValidationMessage>>();
5022      for (String typeProfile : profiles) {
5023        String url = typeProfile;
5024        tail = null;
5025        if (url.contains("#")) {
5026          tail = url.substring(url.indexOf("#") + 1);
5027          url = url.substring(0, url.indexOf("#"));
5028        }
5029        p = this.context.fetchResource(StructureDefinition.class, typeProfile);
5030        if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), p != null, I18nConstants.VALIDATION_VAL_UNKNOWN_PROFILE, typeProfile)) {
5031          List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
5032          validateElement(hostContext, profileErrors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
5033          if (hasErrors(profileErrors))
5034            badProfiles.put(typeProfile, profileErrors);
5035          else
5036            goodProfiles.put(typeProfile, profileErrors);
5037        }
5038      }
5039      if (goodProfiles.size() == 1) {
5040        errors.addAll(goodProfiles.values().iterator().next());
5041      } else if (goodProfiles.size() == 0) {
5042        rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOMATCH, StringUtils.join("; ", profiles));
5043        for (String m : badProfiles.keySet()) {
5044          p = this.context.fetchResource(StructureDefinition.class, m);
5045          for (ValidationMessage message : badProfiles.get(m)) {
5046            message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])");
5047            errors.add(message);
5048          }
5049        }
5050      } else {
5051        warning(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MULTIPLEMATCHES, StringUtils.join("; ", goodProfiles.keySet()));
5052        for (String m : goodProfiles.keySet()) {
5053          p = this.context.fetchResource(StructureDefinition.class, m);
5054          for (ValidationMessage message : goodProfiles.get(m)) {
5055            message.setMessage(message.getMessage() + " (validating against " + p.getUrl() + (p.hasVersion() ? "|" + p.getVersion() : "") + " [" + p.getName() + "])");
5056            errors.add(message);
5057          }
5058        }
5059      }
5060    }
5061    if (p != null) {
5062      trackUsage(p, hostContext, element);
5063
5064      if (!elementValidated) {
5065        if (ei.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || ei.getElement().getSpecial() == SpecialElement.BUNDLE_OUTCOME || ei.getElement().getSpecial() == SpecialElement.PARAMETER)
5066          validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, ei.getElement(), ei.getElement(), type, localStack.resetIds(), thisIsCodeableConcept, checkDisplay, thisExtension);
5067        else
5068          validateElement(hostContext, errors, p, getElementByTail(p, tail), profile, checkDefn, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
5069      }
5070      int index = profile.getSnapshot().getElement().indexOf(checkDefn);
5071      if (index < profile.getSnapshot().getElement().size() - 1) {
5072        String nextPath = profile.getSnapshot().getElement().get(index + 1).getPath();
5073        if (!nextPath.equals(checkDefn.getPath()) && nextPath.startsWith(checkDefn.getPath()))
5074          validateElement(hostContext, errors, profile, checkDefn, null, null, resource, ei.getElement(), type, localStack, thisIsCodeableConcept, checkDisplay, thisExtension);
5075      }
5076    }
5077  }
5078
5079  private boolean isResourceAndTypes(ElementDefinition ed) {
5080    if (!Utilities.existsInList(ed.getBase().getPath(), "Bundle.entry.resource", "Bundle.entry.response.outcome", "DomainResource.contained", "Parameters.parameter.resource", "Parameters.parameter.part.resource")) {
5081      return false;
5082    }
5083    for (TypeRefComponent tr : ed.getType()) {
5084      if (!isResource(tr.getCode())) {
5085        return false;
5086      }
5087    }
5088    return true;
5089  }
5090
5091  private boolean isResource(String type) {
5092    StructureDefinition sd = context.fetchTypeDefinition(type);
5093    return sd != null && sd.getKind().equals(StructureDefinitionKind.RESOURCE);
5094  }
5095
5096  private void trackUsage(StructureDefinition profile, ValidatorHostContext hostContext, Element element) {
5097    if (tracker != null) {
5098      tracker.recordProfileUsage(profile, hostContext.getAppContext(), element);
5099    }
5100  }
5101
5102  private boolean hasMapping(String url, StructureDefinition defn, ElementDefinition elem) {
5103    String id = null;
5104    for (StructureDefinitionMappingComponent m : defn.getMapping()) {
5105      if (url.equals(m.getUri())) {
5106        id = m.getIdentity();
5107        break;
5108      }
5109    }
5110    if (id != null) {
5111      for (ElementDefinitionMappingComponent m : elem.getMapping()) {
5112        if (id.equals(m.getIdentity())) {
5113          return true;
5114        }
5115      }
5116
5117    }
5118    return false;
5119  }
5120
5121  private List<String> getMapping(String url, StructureDefinition defn, ElementDefinition elem) {
5122    List<String> res = new ArrayList<>();
5123    String id = null;
5124    for (StructureDefinitionMappingComponent m : defn.getMapping()) {
5125      if (url.equals(m.getUri())) {
5126        id = m.getIdentity();
5127        break;
5128      }
5129    }
5130    if (id != null) {
5131      for (ElementDefinitionMappingComponent m : elem.getMapping()) {
5132        if (id.equals(m.getIdentity())) {
5133          res.add(m.getMap());
5134        }
5135      }
5136    }
5137    return res;
5138  }
5139
5140  public void checkMustSupport(StructureDefinition profile, ElementInfo ei) {
5141    String usesMustSupport = profile.getUserString("usesMustSupport");
5142    if (usesMustSupport == null) {
5143      usesMustSupport = "N";
5144      for (ElementDefinition pe : profile.getSnapshot().getElement()) {
5145        if (pe.getMustSupport()) {
5146          usesMustSupport = "Y";
5147          break;
5148        }
5149      }
5150      profile.setUserData("usesMustSupport", usesMustSupport);
5151    }
5152    if (usesMustSupport.equals("Y")) {
5153      String elementSupported = ei.getElement().getUserString("elementSupported");
5154      if (elementSupported == null || ei.definition.getMustSupport())
5155        if (ei.definition.getMustSupport()) {
5156          ei.getElement().setUserData("elementSupported", "Y");
5157        }
5158    }
5159  }
5160
5161  public void checkCardinalities(List<ValidationMessage> errors, StructureDefinition profile, Element element, NodeStack stack,
5162    List<ElementDefinition> childDefinitions, List<ElementInfo> children, List<String> problematicPaths) throws DefinitionException {
5163    // 3. report any definitions that have a cardinality problem
5164    for (ElementDefinition ed : childDefinitions) {
5165      if (ed.getRepresentation().isEmpty()) { // ignore xml attributes
5166        int count = 0;
5167        List<ElementDefinition> slices = null;
5168        if (ed.hasSlicing())
5169          slices = profileUtilities.getSliceList(profile, ed);
5170        for (ElementInfo ei : children)
5171          if (ei.definition == ed)
5172            count++;
5173          else if (slices != null) {
5174            for (ElementDefinition sed : slices) {
5175              if (ei.definition == sed) {
5176                count++;
5177                break;
5178              }
5179            }
5180          }
5181        if (ed.getMin() > 0) {
5182          if (problematicPaths.contains(ed.getPath()))
5183            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()));
5184          else {
5185            if (count < ed.getMin()) {
5186              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));
5187            }
5188          }
5189        }
5190        if (ed.hasMax() && !ed.getMax().equals("*")) {
5191          if (problematicPaths.contains(ed.getPath()))
5192            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());
5193          else if (count > Integer.parseInt(ed.getMax())) {
5194            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));
5195          }
5196        }
5197      }
5198    }
5199  }
5200
5201  public List<String> assignChildren(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, Element resource,
5202    NodeStack stack, List<ElementDefinition> childDefinitions, List<ElementInfo> children) throws DefinitionException {
5203    // 2. assign children to a definition
5204    // for each definition, for each child, check whether it belongs in the slice
5205    ElementDefinition slicer = null;
5206    boolean unsupportedSlicing = false;
5207    List<String> problematicPaths = new ArrayList<String>();
5208    String slicingPath = null;
5209    int sliceOffset = 0;
5210    for (int i = 0; i < childDefinitions.size(); i++) {
5211      ElementDefinition ed = childDefinitions.get(i);
5212      boolean childUnsupportedSlicing = false;
5213      boolean process = true;
5214      if (ed.hasSlicing() && !ed.getSlicing().getOrdered()) {
5215        slicingPath = ed.getPath();
5216      } else if (slicingPath != null && ed.getPath().equals(slicingPath)) {
5217        ; // nothing
5218      } else if (slicingPath != null && !ed.getPath().startsWith(slicingPath)) {
5219        slicingPath = null;
5220      }
5221      // where are we with slicing
5222      if (ed.hasSlicing()) {
5223        if (slicer != null && slicer.getPath().equals(ed.getPath())) {
5224          String errorContext = "profile " + profile.getUrl();
5225          if (!resource.getChildValue(ID).isEmpty()) {
5226            errorContext += "; instance " + resource.getChildValue("id");
5227          }
5228          throw new DefinitionException(context.formatMessage(I18nConstants.SLICE_ENCOUNTERED_MIDWAY_THROUGH_SET_PATH___ID___, slicer.getPath(), slicer.getId(), errorContext));
5229        }
5230        slicer = ed;
5231        process = false;
5232        sliceOffset = i;
5233      } else if (slicer != null && !slicer.getPath().equals(ed.getPath()))
5234        slicer = null;
5235
5236      for (ElementInfo ei : children) {
5237        if (ei.sliceInfo == null) {
5238          ei.sliceInfo = new ArrayList<>();
5239        }
5240        unsupportedSlicing = matchSlice(hostContext, errors, ei.sliceInfo, profile, stack, slicer, unsupportedSlicing, problematicPaths, sliceOffset, i, ed, childUnsupportedSlicing, ei);
5241      }
5242    }
5243    int last = -1;
5244    ElementInfo lastei = null;
5245    int lastSlice = -1;
5246    for (ElementInfo ei : children) {
5247      String sliceInfo = "";
5248      if (slicer != null) {
5249        sliceInfo = " (slice: " + slicer.getPath() + ")";
5250      }
5251      if (!unsupportedSlicing) {
5252        if (ei.additionalSlice && ei.definition != null) {
5253          if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) ||
5254            ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) {
5255            slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo), 
5256              context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_,
5257                profile == null ? "" : " defined in the profile " + profile.getUrl()),
5258              context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo),
5259              errorSummaryForSlicingAsText(ei.sliceInfo));
5260          } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) {
5261            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));
5262          }
5263        } else {
5264          // Don't raise this if we're in an abstract profile, like Resource
5265          if (!profile.getAbstract()) {
5266            rule(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.getPath(), (ei.definition != null), I18nConstants.VALIDATION_VAL_PROFILE_NOTALLOWED, profile.getUrl());
5267          }
5268        }
5269      }
5270      // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements
5271      boolean isXmlAttr = false;
5272      if (ei.definition != null) {
5273        for (Enumeration<PropertyRepresentation> r : ei.definition.getRepresentation()) {
5274          if (r.getValue() == PropertyRepresentation.XMLATTR) {
5275            isXmlAttr = true;
5276            break;
5277          }
5278        }
5279      }
5280
5281      if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) {
5282        boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr;
5283        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());
5284      }
5285      if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) {
5286        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());
5287      }
5288      if (ei.definition == null || !isXmlAttr) {
5289        last = ei.index;
5290        lastei = ei;
5291      }
5292      if (ei.slice != null) {
5293        lastSlice = ei.sliceindex;
5294      } else {
5295        lastSlice = -1;
5296      }
5297    }
5298    return problematicPaths;
5299  }
5300
5301
5302  public List<ElementInfo> listChildren(Element element, NodeStack stack) {
5303    // 1. List the children, and remember their exact path (convenience)
5304    List<ElementInfo> children = new ArrayList<ElementInfo>();
5305    ChildIterator iter = new ChildIterator(this, stack.getLiteralPath(), element);
5306    while (iter.next())
5307      children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count()));
5308    return children;
5309  }
5310
5311  public void checkInvariants(ValidatorHostContext hostContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, Element resource, Element element, NodeStack stack, boolean onlyNonInherited) throws FHIRException {
5312    checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited);
5313  }
5314
5315  public boolean matchSlice(ValidatorHostContext hostContext, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, StructureDefinition profile, NodeStack stack,
5316    ElementDefinition slicer, boolean unsupportedSlicing, List<String> problematicPaths, int sliceOffset, int i, ElementDefinition ed,
5317    boolean childUnsupportedSlicing, ElementInfo ei) {
5318    boolean match = false;
5319    if (slicer == null || slicer == ed) {
5320      match = nameMatches(ei.getName(), tail(ed.getPath()));
5321    } else {
5322      if (nameMatches(ei.getName(), tail(ed.getPath())))
5323        try {
5324//          System.out.println("match slices for "+stack.getLiteralPath()+": "+slicer.getId()+" = "+slicingSummary(slicer.getSlicing()));
5325          match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile);
5326          if (match) {
5327            ei.slice = slicer;
5328
5329            // Since a defined slice was found, this is not an additional (undefined) slice.
5330            ei.additionalSlice = false;
5331          } else if (ei.slice == null) {
5332            // 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
5333            ei.additionalSlice = true;
5334          }
5335        } catch (FHIRException e) {
5336          rule(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.getPath(), false,  I18nConstants.SLICING_CANNOT_BE_EVALUATED, e.getMessage());
5337          unsupportedSlicing = true;
5338          childUnsupportedSlicing = true;
5339        }
5340    }
5341    if (match) {
5342      boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", "")));
5343      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() : ""))) {
5344        ei.definition = ed;
5345        if (ei.slice == null) {
5346          ei.index = i;
5347        } else {
5348          ei.index = sliceOffset;
5349          ei.sliceindex = i - (sliceOffset + 1);
5350        }
5351      }
5352    } else if (childUnsupportedSlicing) {
5353      problematicPaths.add(ed.getPath());
5354    }
5355    return unsupportedSlicing;
5356  }
5357
5358  private String slicingSummary(ElementDefinitionSlicingComponent slicing) {
5359    StringBuilder b = new StringBuilder();
5360    b.append('[');
5361    boolean first = true;
5362    for (ElementDefinitionSlicingDiscriminatorComponent t : slicing.getDiscriminator()) {
5363      if (first) first = false; else b.append(","); 
5364      b.append(t.getType().toCode());
5365      b.append(":");
5366      b.append(t.getPath());
5367    }
5368    b.append(']');
5369    b.append(slicing.getOrdered() ? ";ordered" : "");
5370    b.append(slicing.getRules().toString());
5371    return b.toString();
5372  }
5373
5374  private ElementDefinition getElementByTail(StructureDefinition p, String tail) throws DefinitionException {
5375    if (tail == null)
5376      return p.getSnapshot().getElement().get(0);
5377    for (ElementDefinition t : p.getSnapshot().getElement()) {
5378      if (tail.equals(t.getId()))
5379        return t;
5380    }
5381    throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_WITH_ID_, tail));
5382  }
5383
5384  private IdStatus idStatusForEntry(Element ep, ElementInfo ei) {
5385    if (isBundleEntry(ei.getPath())) {
5386      Element req = ep.getNamedChild("request");
5387      Element resp = ep.getNamedChild("response");
5388      Element fullUrl = ep.getNamedChild(FULL_URL);
5389      Element method = null;
5390      Element url = null;
5391      if (req != null) {
5392        method = req.getNamedChild("method");
5393        url = req.getNamedChild("url");
5394      }
5395      if (resp != null) {
5396        return IdStatus.OPTIONAL;
5397      }
5398      if (method == null) {
5399        if (fullUrl == null)
5400          return IdStatus.REQUIRED;
5401        else if (fullUrl.primitiveValue().startsWith("urn:uuid:") || fullUrl.primitiveValue().startsWith("urn:oid:"))
5402          return IdStatus.OPTIONAL;
5403        else
5404          return IdStatus.REQUIRED;
5405      } else {
5406        String s = method.primitiveValue();
5407        if (s.equals("PUT")) {
5408          if (url == null)
5409            return IdStatus.REQUIRED;
5410          else
5411            return IdStatus.OPTIONAL; // or maybe prohibited? not clear
5412        } else if (s.equals("POST"))
5413          return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102
5414        else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource
5415          return IdStatus.OPTIONAL;
5416      }
5417    } else if (isParametersEntry(ei.getPath()) || isBundleOutcome(ei.getPath()))
5418      return IdStatus.OPTIONAL;
5419    else
5420      return IdStatus.REQUIRED;
5421  }
5422
5423  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 {
5424    if (noInvariantChecks)
5425      return;
5426
5427    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
5428      if (inv.hasExpression() && (!onlyNonInherited || !inv.hasSource() || (!isInheritedProfile(profile, inv.getSource()) && !isInheritedProfile(ed.getType(), inv.getSource())) )) {
5429        @SuppressWarnings("unchecked")
5430        Map<String, List<ValidationMessage>> invMap = executionId.equals(element.getUserString(EXECUTION_ID)) ? (Map<String, List<ValidationMessage>>) element.getUserData(EXECUTED_CONSTRAINT_LIST) : null;
5431        if (invMap == null) {
5432          invMap = new HashMap<>();
5433          element.setUserData(EXECUTED_CONSTRAINT_LIST, invMap);
5434          element.setUserData(EXECUTION_ID, executionId);
5435        }
5436        List<ValidationMessage> invErrors = null;
5437        // 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.
5438        String key = fixExpr(inv.getExpression(), inv.getKey());
5439        if (!invMap.keySet().contains(key)) {
5440          invErrors = new ArrayList<ValidationMessage>();
5441          invMap.put(key, invErrors);
5442          checkInvariant(hostContext, invErrors, path, profile, resource, element, inv);
5443        } else {
5444          invErrors = (ArrayList<ValidationMessage>)invMap.get(key);
5445        }
5446        errors.addAll(invErrors);
5447      }
5448    }
5449  }
5450
5451  private boolean isInheritedProfile(List<TypeRefComponent> types, String source) {
5452    for (TypeRefComponent type : types) {
5453      for (CanonicalType c : type.getProfile()) {
5454        StructureDefinition sd = context.fetchResource(StructureDefinition.class, c.asStringValue());
5455        if (sd != null) {
5456          if (sd.getUrl().equals(source)) {
5457            return true;
5458          }
5459          if (isInheritedProfile(sd, source)) {
5460            return true;
5461          }
5462        }
5463      }
5464    }
5465    return false;
5466  }
5467
5468  private boolean isInheritedProfile(StructureDefinition profile, String source) {
5469    if (source.equals(profile.getUrl())) {
5470      return false;
5471    }
5472    while (profile != null) {
5473      profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());
5474      if (profile != null) {
5475        if (source.equals(profile.getUrl())) {
5476          return true;
5477        }
5478      }
5479    }
5480    return false;
5481  }
5482
5483  public void checkInvariant(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, StructureDefinition profile, Element resource, Element element, ElementDefinitionConstraintComponent inv) throws FHIRException {
5484//    if (debug) {
5485//      System.out.println("inv "+inv.getKey()+" on "+path+" in "+resource.fhirType()+" {{ "+inv.getExpression()+" }}");
5486//    }
5487    ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
5488    if (n == null) {
5489      long t = System.nanoTime();
5490      try {
5491        n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey()));
5492      } catch (FHIRLexerException e) {
5493        rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, false, I18nConstants.PROBLEM_PROCESSING_EXPRESSION__IN_PROFILE__PATH__, inv.getExpression(), profile.getUrl(), path, e.getMessage());
5494        return;
5495      }
5496      timeTracker.fpe(t);
5497      inv.setUserData("validator.expression.cache", n);
5498    }
5499
5500    String msg;
5501    boolean ok;
5502    try {
5503      long t = System.nanoTime();
5504      ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n);
5505      timeTracker.fpe(t);
5506      msg = fpe.forLog();
5507    } catch (Exception ex) {
5508      ok = false;
5509      msg = ex.getMessage();
5510    }
5511    if (!ok) {
5512      if (!Utilities.noString(msg)) {
5513        msg = "'" + inv.getHuman()+"' (" + msg + ")";
5514      } else if (wantInvariantInMessage) {
5515        msg = "'" + inv.getHuman()+"'  [" + n.toString() + "]";
5516      } else {
5517        msg = context.formatMessage(I18nConstants.INV_FAILED, "'" + inv.getHuman()+"'");        
5518      }
5519      if (inv.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice") &&
5520        ToolingExtensions.readBooleanExtension(inv, "http://hl7.org/fhir/StructureDefinition/elementdefinition-bestpractice")) {
5521        if (bpWarnings == BestPracticeWarningLevel.Hint)
5522          hint(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": " + msg);
5523        else if (bpWarnings == BestPracticeWarningLevel.Warning)
5524          warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
5525        else if (bpWarnings == BestPracticeWarningLevel.Error)
5526          rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
5527      } else if (inv.getSeverity() == ConstraintSeverity.ERROR) {
5528        rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
5529      } else if (inv.getSeverity() == ConstraintSeverity.WARNING) {
5530        warning(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getKey() + ": '" + inv.getHuman()+"' " + msg);
5531      }
5532    }
5533  }
5534  
5535  private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) {
5536    // all observations should have a subject, a performer, and a time
5537
5538    bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_SUBJECT);
5539    List<Element> performers = new ArrayList<>();
5540    element.getNamedChildren("performer", performers);
5541    bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), performers.size() > 0, I18nConstants.ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER);
5542    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);
5543  }
5544
5545  /*
5546   * The actual base entry point for internal use (re-entrant)
5547   */
5548  private void validateResource(ValidatorHostContext hostContext, List<ValidationMessage> errors, Element resource,
5549                                Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack) throws FHIRException {
5550
5551    // check here if we call validation policy here, and then change it to the new interface
5552    assert stack != null;
5553    assert resource != null;
5554    boolean ok = true;
5555    String resourceName = element.getType(); // todo: consider namespace...?
5556
5557    if (defn == null) {
5558      long t = System.nanoTime();
5559      defn = element.getProperty().getStructure();
5560      if (defn == null)
5561        defn = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
5562      timeTracker.sd(t);
5563      //check exists
5564      ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName),
5565        defn != null, I18nConstants.VALIDATION_VAL_PROFILE_NODEFINITION, resourceName);
5566    }
5567
5568    // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead
5569    if (!typeMatchesDefn(resourceName, defn) && resourceName.equals(BUNDLE)) {
5570      NodeStack first = getFirstEntry(stack);
5571      if (first != null && typeMatchesDefn(first.getElement().getType(), defn)) {
5572        element = first.getElement();
5573        stack = first;
5574        resourceName = element.getType(); // todo: consider namespace...?
5575        idstatus = IdStatus.OPTIONAL; // why?
5576      }
5577      // todo: validate everything in this bundle.
5578    }
5579    if (ok) {
5580      if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) {
5581        rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING);
5582      } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) {
5583        rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED);
5584      }
5585      if (element.getNamedChild(ID) != null) {
5586        Element eid = element.getNamedChild(ID);
5587        if (eid.getProperty() != null && eid.getProperty().getDefinition() != null && eid.getProperty().getDefinition().getBase().getPath().equals("Resource.id")) {
5588          NodeStack ns = stack.push(eid, -1, eid.getProperty().getDefinition(), null);
5589          rule(errors, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), FormatUtilities.isValidId(eid.primitiveValue()), I18nConstants.RESOURCE_RES_ID_MALFORMED);
5590        }
5591      }
5592      // validate
5593      if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), resourceName.equals(defn.getType()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE,
5594          defn.getType(), resourceName, defn.getUrl())) {
5595        start(hostContext, errors, element, element, defn, stack); // root is both definition and type
5596      }
5597    }
5598  }
5599
5600  private boolean typeMatchesDefn(String name, StructureDefinition defn) {
5601    if (defn.getKind() == StructureDefinitionKind.LOGICAL) {
5602      return name.equals(defn.getType()) || name.equals(defn.getName()) || name.equals(defn.getId());
5603    } else {
5604      return name.matches(defn.getType());
5605    }
5606  }
5607
5608  private NodeStack getFirstEntry(NodeStack bundle) {
5609    List<Element> list = new ArrayList<Element>();
5610    bundle.getElement().getNamedChildren(ENTRY, list);
5611    if (list.isEmpty())
5612      return null;
5613    Element resource = list.get(0).getNamedChild(RESOURCE);
5614    if (resource == null)
5615      return null;
5616    else {
5617      NodeStack entry = bundle.push(list.get(0), 0, list.get(0).getProperty().getDefinition(), list.get(0).getProperty().getDefinition());
5618      return entry.push(resource, -1, resource.getProperty().getDefinition(), context.fetchTypeDefinition(resource.fhirType()).getSnapshot().getElementFirstRep());
5619    }
5620  }
5621
5622  private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException {
5623    if (criteria.hasFixed()) {
5624      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
5625      checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getUrl(), "value", null, false);
5626      return msgs.size() == 0;
5627    } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) {
5628      throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE));
5629    } else {
5630      throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__NO_FIXED_VALUE_OR_REQUIRED_VALUE_SET));
5631    }
5632  }
5633
5634  private boolean yearIsValid(String v) {
5635    if (v == null) {
5636      return false;
5637    }
5638    try {
5639      int i = Integer.parseInt(v.substring(0, Math.min(4, v.length())));
5640      return i >= 1800 && i <= thisYear() + 80;
5641    } catch (NumberFormatException e) {
5642      return false;
5643    }
5644  }
5645
5646  private int thisYear() {
5647    return Calendar.getInstance().get(Calendar.YEAR);
5648  }
5649
5650
5651  public String reportTimes() {
5652    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);
5653    timeTracker.reset();
5654    return s;
5655  }
5656
5657  public boolean isNoBindingMsgSuppressed() {
5658    return noBindingMsgSuppressed;
5659  }
5660
5661  public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) {
5662    this.noBindingMsgSuppressed = noBindingMsgSuppressed;
5663    return this;
5664  }
5665
5666
5667  public boolean isNoTerminologyChecks() {
5668    return noTerminologyChecks;
5669  }
5670
5671  public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) {
5672    this.noTerminologyChecks = noTerminologyChecks;
5673    return this;
5674  }
5675
5676  public void checkAllInvariants() {
5677    for (StructureDefinition sd : context.allStructures()) {
5678      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
5679        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
5680          for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
5681            if (inv.hasExpression()) {
5682              try {
5683                ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
5684                if (n == null) {
5685                  n = fpe.parse(fixExpr(inv.getExpression(), inv.getKey()));
5686                  inv.setUserData("validator.expression.cache", n);
5687                }
5688                fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ? sd.getType() : "DomainResource", ed.getPath(), n);
5689              } catch (Exception e) {
5690                System.out.println("Error processing structure [" + sd.getId() + "] path " + ed.getPath() + ":" + inv.getKey() + " ('" + inv.getExpression() + "'): " + e.getMessage());
5691              }
5692            }
5693          }
5694        }
5695      }
5696    }
5697  }
5698
5699  private String fixExpr(String expr, String key) {
5700    // this is a hack work around for past publication of wrong FHIRPath expressions
5701    // R4
5702    // waiting for 4.0.2
5703    //TODO is this expression below correct? @grahamegrieve
5704    if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) {
5705      return "probability.empty() or ((probability is decimal) implies ((probability as decimal) <= 100))";
5706    }
5707    if ("enableWhen.count() > 2 implies enableBehavior.exists()".equals(expr)) {
5708      return "enableWhen.count() >= 2 implies enableBehavior.exists()";
5709    }
5710    if ("txt-2".equals(key)) {
5711      return "htmlChecks2()";
5712    }
5713
5714    // handled in 4.0.1
5715    if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) {
5716      return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())";
5717    }
5718    if ("isModifier implies isModifierReason.exists()".equals(expr)) {
5719      return "(isModifier.exists() and isModifier) implies isModifierReason.exists()";
5720    }
5721    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)) {
5722      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('\\\\..*','')&'.')))";
5723    }
5724    if ("differential.element.all(id) and differential.element.id.trace('ids').isDistinct()".equals(expr)) {
5725      return "differential.element.all(id.exists()) and differential.element.id.trace('ids').isDistinct()";
5726    }
5727    if ("snapshot.element.all(id) and snapshot.element.id.trace('ids').isDistinct()".equals(expr)) {
5728      return "snapshot.element.all(id.exists()) and snapshot.element.id.trace('ids').isDistinct()";
5729    }
5730
5731    // R3
5732    if ("(code or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')".equals(expr)) {
5733      return "(code.exists() or value.empty()) and (system.empty() or system = 'urn:iso:std:iso:4217')";
5734    }
5735    if ("value.empty() or code!=component.code".equals(expr)) {
5736      return "value.empty() or (code in component.code).not()";
5737    }
5738    if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
5739      return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
5740    }
5741    if ("element.all(definition and min and max)".equals(expr)) {
5742      return "element.all(definition.exists() and min.exists() and max.exists())";
5743    }
5744    if ("telecom or endpoint".equals(expr)) {
5745      return "telecom.exists() or endpoint.exists()";
5746    }
5747    if ("(code or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)".equals(expr)) {
5748      return "(code.exists() or value.empty()) and (system.empty() or system = %ucum) and (value.empty() or value > 0)";
5749    }
5750    if ("searchType implies type = 'string'".equals(expr)) {
5751      return "searchType.exists() implies type = 'string'";
5752    }
5753    if ("abatement.empty() or (abatement as boolean).not()  or clinicalStatus='resolved' or clinicalStatus='remission' or clinicalStatus='inactive'".equals(expr)) {
5754      return "abatement.empty() or (abatement is boolean).not() or (abatement as boolean).not() or (clinicalStatus = 'resolved') or (clinicalStatus = 'remission') or (clinicalStatus = 'inactive')";
5755    }
5756    if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr)) {
5757      return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())";
5758    }
5759    if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) {
5760      return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
5761    }
5762    if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) {
5763      return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
5764    }
5765    if ("probability is decimal implies probability.as(decimal) <= 100".equals(expr)) {
5766      if (key.equals("ras-2")) {
5767        return "probability.empty() or (probability is decimal implies probability.as(decimal) <= 100)";
5768      }
5769    }
5770    if ("".equals(expr)) {
5771      return "";
5772    }
5773    return expr;
5774  }
5775
5776  public IEvaluationContext getExternalHostServices() {
5777    return externalHostServices;
5778  }
5779
5780  public String getValidationLanguage() {
5781    return validationLanguage;
5782  }
5783
5784  public void setValidationLanguage(String validationLanguage) {
5785    this.validationLanguage = validationLanguage;
5786  }
5787
5788  public boolean isDebug() {
5789    return debug;
5790  }
5791
5792  public void setDebug(boolean debug) {
5793    this.debug = debug;
5794  }
5795  private String tail(String path) {
5796    return path.substring(path.lastIndexOf(".") + 1);
5797  }
5798  private String tryParse(String ref) {
5799    String[] parts = ref.split("\\/");
5800    switch (parts.length) {
5801      case 1:
5802        return null;
5803      case 2:
5804        return checkResourceType(parts[0]);
5805      default:
5806        if (parts[parts.length - 2].equals("_history") && parts.length >= 4)
5807          return checkResourceType(parts[parts.length - 4]);
5808        else
5809          return checkResourceType(parts[parts.length - 2]);
5810    }
5811  }
5812
5813  private boolean typesAreAllReference(List<TypeRefComponent> theType) {
5814    for (TypeRefComponent typeRefComponent : theType) {
5815      if (typeRefComponent.getCode().equals("Reference") == false) {
5816        return false;
5817      }
5818    }
5819    return true;
5820  }
5821
5822
5823  public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet vs, String value, ValidationOptions options) {
5824    return context.validateCode(options, value, vs);
5825  }
5826
5827  // no delay on this one? 
5828  public ValidationResult checkCodeOnServer(NodeStack stack, String code, String system, String version, String display, boolean checkDisplay) {
5829    return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), system, version, code, checkDisplay ? display : null);
5830  }
5831
5832  public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) {
5833    if (checkMembership) {
5834      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), c, valueset);   
5835    } else {
5836      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset);
5837    }
5838  }
5839  
5840  public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, CodeableConcept cc, boolean vsOnly) {
5841    if (vsOnly) {
5842      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()).checkValueSetOnly(), cc, valueset);
5843    } else {
5844      return context.validateCode(baseOptions.setLanguage(stack.getWorkingLang()), cc, valueset);
5845    }
5846  }
5847
5848  public boolean isSecurityChecks() {
5849    return securityChecks;
5850  }
5851
5852  public void setSecurityChecks(boolean securityChecks) {
5853    this.securityChecks = securityChecks;
5854  }
5855
5856  @Override
5857  public List<BundleValidationRule> getBundleValidationRules() {
5858    return bundleValidationRules ;
5859  }
5860
5861  @Override
5862  public boolean isValidateValueSetCodesOnTxServer() {
5863    return validateValueSetCodesOnTxServer;
5864  }
5865
5866  @Override
5867  public void setValidateValueSetCodesOnTxServer(boolean value) {
5868    this.validateValueSetCodesOnTxServer = value;    
5869  }
5870
5871  public boolean isNoCheckAggregation() {
5872    return noCheckAggregation;
5873  }
5874
5875  public void setNoCheckAggregation(boolean noCheckAggregation) {
5876    this.noCheckAggregation = noCheckAggregation;
5877  }
5878
5879 
5880  public static void setParents(Element element) {
5881    if (element != null && !element.hasParentForValidator()) {
5882      element.setParentForValidator(null);
5883      setParentsInner(element);
5884    }
5885  }
5886  
5887  public static void setParentsInner(Element element) {
5888    for (Element child : element.getChildren()) {
5889      child.setParentForValidator(element);
5890      setParentsInner(child);
5891    }
5892    
5893  }
5894
5895  public void setQuestionnaireMode(QuestionnaireMode questionnaireMode) {
5896    this.questionnaireMode = questionnaireMode;
5897  }
5898
5899  public QuestionnaireMode getQuestionnaireMode() {
5900    return questionnaireMode;
5901  }
5902
5903  public boolean isWantCheckSnapshotUnchanged() {
5904    return wantCheckSnapshotUnchanged;
5905  }
5906
5907  public void setWantCheckSnapshotUnchanged(boolean wantCheckSnapshotUnchanged) {
5908    this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged;
5909  }
5910
5911  public ValidationOptions getBaseOptions() {
5912    return baseOptions;
5913  }
5914
5915  public void setBaseOptions(ValidationOptions baseOptions) {
5916    this.baseOptions = baseOptions;
5917  }
5918
5919  public boolean isNoUnicodeBiDiControlChars() {
5920    return noUnicodeBiDiControlChars;
5921  }
5922
5923  public void setNoUnicodeBiDiControlChars(boolean noUnicodeBiDiControlChars) {
5924    this.noUnicodeBiDiControlChars = noUnicodeBiDiControlChars;
5925  }
5926
5927}