001package org.hl7.fhir.dstu3.validation;
002
003import static org.apache.commons.lang3.StringUtils.isBlank;
004import static org.apache.commons.lang3.StringUtils.isNotBlank;
005
006import java.io.IOException;
007import java.io.InputStream;
008import java.util.ArrayList;
009import java.util.HashMap;
010import java.util.HashSet;
011import java.util.List;
012import java.util.Map;
013import java.util.Set;
014import java.util.regex.Matcher;
015import java.util.regex.Pattern;
016
017import org.apache.commons.lang3.StringUtils;
018import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
019import org.hl7.fhir.dstu3.context.IWorkerContext;
020import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
021import org.hl7.fhir.dstu3.elementmodel.Element;
022import org.hl7.fhir.dstu3.elementmodel.Element.SpecialElement;
023import org.hl7.fhir.dstu3.elementmodel.JsonParser;
024import org.hl7.fhir.dstu3.elementmodel.Manager;
025import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat;
026import org.hl7.fhir.dstu3.elementmodel.ObjectConverter;
027import org.hl7.fhir.dstu3.elementmodel.ParserBase;
028import org.hl7.fhir.dstu3.elementmodel.ParserBase.ValidationPolicy;
029import org.hl7.fhir.dstu3.elementmodel.XmlParser;
030import org.hl7.fhir.dstu3.formats.FormatUtilities;
031import org.hl7.fhir.dstu3.model.*;
032import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
033import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
034import org.hl7.fhir.dstu3.model.ElementDefinition.AggregationMode;
035import org.hl7.fhir.dstu3.model.ElementDefinition.ConstraintSeverity;
036import org.hl7.fhir.dstu3.model.ElementDefinition.DiscriminatorType;
037import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent;
038import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent;
039import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
040import org.hl7.fhir.dstu3.model.ElementDefinition.PropertyRepresentation;
041import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
042import org.hl7.fhir.dstu3.model.Enumerations.BindingStrength;
043import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent;
044import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemOptionComponent;
045import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
046import org.hl7.fhir.dstu3.model.StructureDefinition.ExtensionContext;
047import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
048import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionSnapshotComponent;
049import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
050import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
051import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException;
052import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
053import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext;
054import org.hl7.fhir.dstu3.utils.IResourceValidator;
055import org.hl7.fhir.dstu3.utils.ToolingExtensions;
056import org.hl7.fhir.dstu3.utils.ValidationProfileSet;
057import org.hl7.fhir.dstu3.utils.ValidationProfileSet.ProfileRegistration;
058import org.hl7.fhir.exceptions.DefinitionException;
059import org.hl7.fhir.exceptions.FHIRException;
060import org.hl7.fhir.exceptions.FHIRFormatError;
061import org.hl7.fhir.exceptions.PathEngineException;
062import org.hl7.fhir.exceptions.TerminologyServiceException;
063import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
064import org.hl7.fhir.utilities.Utilities;
065import org.hl7.fhir.utilities.validation.ValidationMessage;
066import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
067import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
068import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
069import org.hl7.fhir.utilities.xhtml.NodeType;
070import org.hl7.fhir.utilities.xhtml.XhtmlNode;
071import org.w3c.dom.Document;
072import org.w3c.dom.Node;
073
074import com.google.gson.Gson;
075import com.google.gson.JsonObject;
076import com.google.gson.stream.JsonWriter;
077
078import ca.uhn.fhir.util.ObjectUtil;
079
080
081/**
082 * Thinking of using this in a java program? Don't! 
083 * You should use on of the wrappers instead. Either in HAPI, or use ValidationEngine
084 * 
085 * @author Grahame Grieve
086 *
087 */
088/* 
089 * todo:
090 * check urn's don't start oid: or uuid: 
091 */
092public class InstanceValidator extends BaseValidator implements IResourceValidator {
093
094
095  private IWorkerContext context;
096  private FHIRPathEngine fpe; 
097
098  // configuration items
099  private CheckDisplayOption checkDisplay;
100  private boolean anyExtensionsAllowed;
101  private boolean errorForUnknownProfiles;
102  private boolean noInvariantChecks;
103  private boolean noTerminologyChecks;
104  private BestPracticeWarningLevel bpWarnings;
105
106  private List<String> extensionDomains = new ArrayList<String>();
107
108  private IdStatus resourceIdRule;
109  private boolean allowXsiLocation;
110
111  // used during the build process to keep the overall volume of messages down
112  private boolean suppressLoincSnomedMessages;
113
114  private Bundle logical;
115
116  // time tracking
117  private long overall = 0;
118  private long txTime = 0;
119  private long sdTime = 0;
120  private long loadTime = 0;
121  private long fpeTime = 0;
122
123  private boolean noBindingMsgSuppressed;
124  private HashMap<Element, ResourceProfiles> resourceProfilesMap;
125  private IValidatorResourceFetcher fetcher;
126  long time = 0;
127
128  /*
129   * Keeps track of whether a particular profile has been checked or not yet
130   */
131  private class ProfileUsage {
132    private StructureDefinition profile;
133    private boolean checked;
134
135    public ProfileUsage(StructureDefinition profile) {
136      this.profile = profile;
137      this.checked = false;
138    }
139
140    public boolean isChecked() {
141      return checked;
142    }
143
144    public void setChecked() {
145      this.checked = true;
146    }
147
148    public StructureDefinition getProfile() {
149      return profile;
150    }
151  }
152
153  /*
154   * Keeps track of all profiles associated with a resource element and whether the resource has been checked against those profiles yet
155   */
156  public class ResourceProfiles {
157    private Element resource;
158    private Element owner;
159    private NodeStack stack;
160    private HashMap<StructureDefinition, ProfileUsage> profiles;
161    private boolean processed;
162
163    public ResourceProfiles(Element resource, NodeStack stack) {
164      this.resource = resource;
165      if (this.resource.getName().equals("contained"))
166        this.owner = stack.parent.element;
167      else
168        this.owner = resource;
169      this.stack = stack;
170      this.profiles = new HashMap<StructureDefinition, ProfileUsage>();
171      this.processed = false;
172    }
173
174    public boolean isProcessed() {
175      return processed;
176    }
177
178    public void setProcessed() {
179      processed = true;
180    }
181
182    public NodeStack getStack() {
183      return stack;
184    }
185
186    public Element getOwner() {
187      return owner;
188    }
189
190    public boolean hasProfiles() {
191      return !profiles.isEmpty();
192    }
193
194    public void addProfiles(List<ValidationMessage> errors, ValidationProfileSet profiles, String path, Element element) throws FHIRException {
195      for (ProfileRegistration profile : profiles.getCanonical())
196        addProfile(errors, profile.getProfile(), profile.isError(), path, element);
197    }
198    
199    public boolean addProfile(List<ValidationMessage> errors, String profile, boolean error, String path, Element element) {
200      String effectiveProfile = profile;
201      String version = null;
202      if (profile.contains("|")) {
203        effectiveProfile = profile.substring(0, profile.indexOf('|'));
204        version = profile.substring(profile.indexOf('|')+1);
205      }
206      StructureDefinition sd = context.fetchResource(StructureDefinition.class, effectiveProfile);
207      if (warningOrError(error, errors, IssueType.INVALID, element.line(), element.col(), path, sd != null, "StructureDefinition reference \"{0}\" could not be resolved", profile)) {
208        if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, version==null || (sd.getVersion()!=null && sd.getVersion().equals(version)), 
209             "Referenced version " + version + " does not match found version " + sd.getVersion() + " for profile " + sd.getUrl(), profile)) {
210          if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd.hasSnapshot(),
211              "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
212            if (!profiles.containsKey(sd)) {
213              profiles.put(sd,  new ProfileUsage(sd));
214              addAncestorProfiles(sd);
215              return true;
216            }
217          }
218        }
219      }
220      return false;
221    }
222
223    public void addAncestorProfiles(StructureDefinition sd) {
224      if (sd.getDerivation().equals(StructureDefinition.TypeDerivationRule.CONSTRAINT)) {
225        StructureDefinition parentSd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
226        if (parentSd != null && !profiles.containsKey(parentSd)) {
227          ProfileUsage pu = new ProfileUsage(parentSd);
228          pu.setChecked(); // We're going to check the child, so no need to check the parent
229          profiles.put(parentSd, pu);
230        }
231      }
232    }
233
234    public List<ProfileUsage> uncheckedProfiles() {
235      List<ProfileUsage> uncheckedProfiles = new ArrayList<ProfileUsage>();
236      for (ProfileUsage profileUsage : profiles.values()) {
237        if (!profileUsage.isChecked())
238          uncheckedProfiles.add(profileUsage);
239      }
240      return uncheckedProfiles;
241    }
242
243    public boolean hasUncheckedProfiles() {
244      return !uncheckedProfiles().isEmpty();
245    }
246
247    public void checkProfile(StructureDefinition profile) {
248      ProfileUsage profileUsage = profiles.get(profile);
249      if (profileUsage==null)
250        throw new Error("Can't check profile that hasn't been added: " + profile.getUrl());
251      else
252        profileUsage.setChecked();
253    }
254  }
255
256  public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) {
257    super();
258    this.context = theContext;
259    fpe = new FHIRPathEngine(context);
260    fpe.setHostServices(hostServices);
261    source = Source.InstanceValidator;
262  }
263
264  public InstanceValidator(ValidationEngine engine) {
265    super();
266    this.context = engine.getContext();
267    fpe = engine.getFpe();
268    source = Source.InstanceValidator;
269  }
270
271  @Override
272  public boolean isNoInvariantChecks() {
273    return noInvariantChecks;
274  }
275
276  @Override
277  public IResourceValidator setNoInvariantChecks(boolean value) {
278    this.noInvariantChecks = value;
279    return this;
280  }
281
282  public IValidatorResourceFetcher getFetcher() {
283    return this.fetcher;
284  }
285
286  public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
287    this.fetcher = value;
288    return this;
289  }
290
291  private boolean allowUnknownExtension(String url) {
292    if (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"))
293      // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
294      return true;
295    for (String s : extensionDomains)
296      if (url.startsWith(s))
297        return true;
298    return anyExtensionsAllowed;
299  }
300
301  private boolean isKnownExtension(String url) {
302    // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
303    if (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"))
304      return true;
305    for (String s : extensionDomains)
306      if (url.startsWith(s))
307        return true;
308    return false;
309  }
310
311  private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
312    if (bpWarnings != null) {
313      switch (bpWarnings) {
314      case Error:
315        rule(errors, invalid, line, col, literalPath, test, message);
316      case Warning:
317        warning(errors, invalid, line, col, literalPath, test, message);
318      case Hint:
319        hint(errors, invalid, line, col, literalPath, test, message);
320      default: // do nothing
321      }
322    }
323  }
324
325  @Override
326  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws FHIRException, IOException {
327    return validate(appContext, errors, stream, format, new ValidationProfileSet());
328  }
329
330  @Override
331  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws FHIRException, IOException {
332    return validate(appContext, errors, stream, format,  new ValidationProfileSet(profile, true));
333  }
334
335  @Override
336  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, StructureDefinition profile) throws FHIRException, IOException {
337    return validate(appContext, errors, stream, format, new ValidationProfileSet(profile));
338  }
339
340  @Override
341  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, InputStream stream, FhirFormat format, ValidationProfileSet profiles) throws FHIRException, IOException {
342    ParserBase parser = Manager.makeParser(context, format);
343    if (parser instanceof XmlParser) 
344      ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
345    parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
346    long t = System.nanoTime();
347    Element e = parser.parse(stream);
348    loadTime = System.nanoTime() - t;
349    if (e != null)
350      validate(appContext, errors, e, profiles);
351    return e;
352  }
353
354  @Override
355  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource) throws FHIRException, IOException {
356    return validate(appContext, errors, resource, new ValidationProfileSet());
357  }
358
359  @Override
360  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, String profile) throws FHIRException, IOException {
361    return validate(appContext, errors, resource, new ValidationProfileSet(profile, true));
362  }
363
364  @Override
365  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, StructureDefinition profile) throws FHIRException, IOException {
366    return validate(appContext, errors, resource, new ValidationProfileSet(profile));
367  }
368
369  @Override
370  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Resource resource, ValidationProfileSet profiles) throws FHIRException, IOException {
371    long t = System.nanoTime();
372    Element e = new ObjectConverter(context).convert(resource);
373    loadTime = System.nanoTime() - t;
374    validate(appContext, errors, e, profiles);
375    return e;
376  }
377
378  @Override
379  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element) throws FHIRException, IOException {
380    return validate(appContext, errors, element, new ValidationProfileSet());
381  }
382
383  @Override
384  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, String profile) throws FHIRException, IOException {
385    return validate(appContext, errors, element, new ValidationProfileSet(profile, true));
386  }
387
388  @Override
389  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, StructureDefinition profile) throws FHIRException, IOException {
390    return validate(appContext, errors, element, new ValidationProfileSet(profile));
391  }
392
393  @Override
394  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, org.w3c.dom.Element element, ValidationProfileSet profiles) throws FHIRException, IOException {
395    XmlParser parser = new XmlParser(context);
396    parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
397    long t = System.nanoTime();
398    Element e = parser.parse(element);
399    loadTime = System.nanoTime() - t;
400    if (e != null) 
401      validate(appContext, errors, e, profiles);
402    return e;
403  }
404
405  @Override
406  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document) throws FHIRException, IOException {
407    return validate(appContext, errors, document, new ValidationProfileSet());
408  }
409
410  @Override
411  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, String profile) throws FHIRException, IOException {
412    return validate(appContext, errors, document, new ValidationProfileSet(profile, true));
413  }
414
415  @Override
416  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, StructureDefinition profile) throws FHIRException, IOException {
417    return validate(appContext, errors, document, new ValidationProfileSet(profile));
418  }
419
420  @Override
421  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, Document document, ValidationProfileSet profiles) throws FHIRException, IOException {
422    XmlParser parser = new XmlParser(context);
423    parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
424    long t = System.nanoTime();
425    Element e = parser.parse(document);
426    loadTime = System.nanoTime() - t;
427    if (e != null)
428      validate(appContext, errors, e, profiles);
429    return e;
430  }
431
432  @Override
433  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object) throws FHIRException, IOException {
434    return validate(appContext, errors, object, new ValidationProfileSet());
435  }
436
437  @Override
438  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, String profile) throws FHIRException, IOException {
439    return validate(appContext, errors, object, new ValidationProfileSet(profile, true));
440  }
441
442  @Override
443  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, StructureDefinition profile) throws FHIRException, IOException {
444    return validate(appContext, errors, object, new ValidationProfileSet(profile));
445  }
446
447  @Override
448  public org.hl7.fhir.dstu3.elementmodel.Element validate(Object appContext, List<ValidationMessage> errors, JsonObject object, ValidationProfileSet profiles) throws FHIRException, IOException {
449    JsonParser parser = new JsonParser(context);
450    parser.setupValidation(ValidationPolicy.EVERYTHING, errors); 
451    long t = System.nanoTime();
452    Element e = parser.parse(object);
453    loadTime = System.nanoTime() - t;
454    if (e != null)
455      validate(appContext, errors, e, profiles);
456    return e;
457  }
458
459  @Override
460  public void validate(Object appContext, List<ValidationMessage> errors, Element element) throws FHIRException, IOException {
461    ValidationProfileSet profileSet = new ValidationProfileSet();
462    validate(appContext, errors, element, profileSet);
463  }
464
465  private void validateRemainder(Object appContext, List<ValidationMessage> errors) throws IOException, FHIRException {
466    boolean processedResource;
467    do {
468      processedResource = false;
469      Set<Element> keys = new HashSet<Element>();
470      keys.addAll(resourceProfilesMap.keySet());
471      for (Element resource : keys) {
472        ResourceProfiles rp = resourceProfilesMap.get(resource);
473        if (rp.hasUncheckedProfiles()) {
474          processedResource = true;
475          start(appContext, errors, rp.getOwner(), resource, null, rp.getStack());
476        }
477      }
478    } while (processedResource);
479  }
480  
481  @Override
482  public void validate(Object appContext, List<ValidationMessage> errors, Element element, String profile) throws FHIRException, IOException {
483    validate(appContext, errors, element, new ValidationProfileSet(profile, true));
484  }
485
486  @Override
487  public void validate(Object appContext, List<ValidationMessage> errors, Element element, StructureDefinition profile) throws FHIRException, IOException {
488    validate(appContext, errors, element, new ValidationProfileSet(profile));
489  }
490
491  @Override
492  public void validate(Object appContext, List<ValidationMessage> errors, Element element, ValidationProfileSet profiles) throws FHIRException, IOException {
493    // this is the main entry point; all the other entry points end up here coming here...
494    long t = System.nanoTime();
495    boolean isRoot = false;
496    if (resourceProfilesMap == null) {
497      resourceProfilesMap = new HashMap<Element, ResourceProfiles>();
498      isRoot = true;
499    }
500    validateResource(appContext, errors, element, element, null, profiles, resourceIdRule, new NodeStack(element));
501    if (isRoot) {
502      validateRemainder(appContext, errors);
503      resourceProfilesMap = null;
504    }
505    overall = System.nanoTime() - t;
506  }
507
508
509  private boolean check(String v1, String v2) {
510    return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
511  }
512
513  private void checkAddress(List<ValidationMessage> errors, String path, Element focus, Address fixed) {
514    checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus);
515    checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus);
516    checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), "city", focus);
517    checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), "state", focus);
518    checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), "country", focus);
519    checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), "postalCode", focus);
520
521    List<Element> lines = new ArrayList<Element>();
522    focus.getNamedChildren("line", lines);
523    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lines.size() == fixed.getLine().size(),
524        "Expected " + Integer.toString(fixed.getLine().size()) + " but found " + Integer.toString(lines.size()) + " line elements")) {
525      for (int i = 0; i < lines.size(); i++)
526        checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), "coding", focus);
527    }
528  }
529
530  private void checkAttachment(List<ValidationMessage> errors, String path, Element focus, Attachment fixed) {
531    checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), "contentType", focus);
532    checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), "language", focus);
533    checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus);
534    checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), "url", focus);
535    checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), "size", focus);
536    checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), "hash", focus);
537    checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), "title", focus);
538  }
539
540  // public API
541
542  private boolean checkCode(List<ValidationMessage> errors, Element element, String path, String code, String system, String display) throws TerminologyServiceException {
543    long t = System.nanoTime();
544    boolean ss = context.supportsSystem(system);
545    txTime = txTime + (System.nanoTime() - t);
546    if (ss) {
547      t = System.nanoTime();
548      ValidationResult s = context.validateCode(system, code, display);
549      txTime = txTime + (System.nanoTime() - t);
550      if (s == null || s.isOk())
551        return true;
552      if (s.getSeverity() == IssueSeverity.INFORMATION)
553        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
554      else if (s.getSeverity() == IssueSeverity.WARNING)
555        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
556      else
557        return rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
558      return true;
559    } else if (system.startsWith("http://hl7.org/fhir")) {
560      if (system.equals("http://hl7.org/fhir/sid/icd-10"))
561        return true; // else don't check ICD-10 (for now)
562      else {
563        CodeSystem cs = getCodeSystem(system);
564        if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, "Unknown Code System " + system)) {
565          ConceptDefinitionComponent def = getCodeDefinition(cs, code);
566          if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, "Unknown Code (" + system + "#" + code + ")"))
567            return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), "Display should be '" + def.getDisplay() + "'");
568        }
569        return false;
570      }
571    } else if (system.startsWith("http://loinc.org")) {
572      return true;
573    } else if (system.startsWith("http://unitsofmeasure.org")) {
574      return true;
575    } else
576      return true;
577  }
578
579  private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element focus, CodeableConcept fixed) {
580    checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus);
581    List<Element> codings = new ArrayList<Element>();
582    focus.getNamedChildren("coding", codings);
583    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(),
584        "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size()) + " coding elements")) {
585      for (int i = 0; i < codings.size(); i++)
586        checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), "coding", focus);
587    }
588  }
589
590  private void checkCodeableConcept(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext)  {
591    if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
592      ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
593      if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
594        if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
595          ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
596          if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
597            try {
598              CodeableConcept cc = readAsCodeableConcept(element);
599              if (!cc.hasCoding()) {
600                if (binding.getStrength() == BindingStrength.REQUIRED)
601                  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());
602                else if (binding.getStrength() == BindingStrength.EXTENSIBLE) 
603                  warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code should be provided from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl());
604              } else {
605                long t = System.nanoTime();
606
607                // Check whether the codes are appropriate for the type of binding we have
608                boolean bindingsOk = true;
609                if (binding.getStrength() != BindingStrength.EXAMPLE) {
610                  boolean atLeastOneSystemIsSupported = false;
611                  for (Coding nextCoding : cc.getCoding()) {
612                    String nextSystem = nextCoding.getSystem();
613                    if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
614                      atLeastOneSystemIsSupported = true;
615                      break;
616                    }
617                  }
618
619                  if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
620                    // ignore this since we can't validate but it doesn't matter..
621                  } else {
622                    ValidationResult vr = context.validateCode(cc, valueset);
623                    if (!vr.isOk()) {
624                      bindingsOk = false;
625                      if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
626                        if (binding.getStrength() == BindingStrength.REQUIRED)
627                          warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code from this value set is required (class = "+vr.getErrorClass().toString()+")");
628                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
629                          if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 
630                            checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), cc);
631                          else
632                            warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code should come from this value set unless it has no suitable code (class = "+vr.getErrorClass().toString()+")");
633                        } else if (binding.getStrength() == BindingStrength.PREFERRED)
634                          hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code is recommended to come from this value set (class = "+vr.getErrorClass().toString()+")");
635                      } else {
636                        if (binding.getStrength() == BindingStrength.REQUIRED)
637                          rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()+", and a code from this value set is required) (codes = "+ccSummary(cc)+")");
638                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
639                          if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 
640                            checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), cc);
641                          else
642                            warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code) (codes = "+ccSummary(cc)+")");
643                        } else if (binding.getStrength() == BindingStrength.PREFERRED)
644                          hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set) (codes = "+ccSummary(cc)+")");
645                      }
646                    } else if (vr.getMessage()!=null)
647                      warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
648                  }
649                  // Then, for any codes that are in code systems we are able
650                  // to validate, we'll validate that the codes actually exist
651                  if (bindingsOk) {
652                    for (Coding nextCoding : cc.getCoding()) {
653                      String nextCode = nextCoding.getCode();
654                      String nextSystem = nextCoding.getSystem();
655                      if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
656                        ValidationResult vr = context.validateCode(nextSystem, nextCode, null);
657                        if (!vr.isOk()) {
658                          warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Code {0} is not a valid code in code system {1}", nextCode, nextSystem);
659                        }
660                      }
661                    }
662                  }
663                  txTime = txTime + (System.nanoTime() - t);
664                }
665              }
666            } catch (Exception e) {
667              warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept");
668            }
669          }
670        } else if (binding.hasValueSet()) {
671          hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
672        } else if (!noBindingMsgSuppressed) {
673          hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
674        }
675      }
676    }
677  }
678
679  private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, Reference maxVSUrl, CodeableConcept cc) {
680    // TODO Auto-generated method stub
681    ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
682    if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found")) {
683      try {
684        long t = System.nanoTime();
685        ValidationResult vr = context.validateCode(cc, valueset);
686        txTime = txTime + (System.nanoTime() - t);
687        if (!vr.isOk()) {
688          rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+", and a code from this value set is required) (codes = "+ccSummary(cc)+")");
689        }
690      } catch (Exception e) {
691        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept using maxValueSet");
692      }
693    }
694  }
695
696  private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, Reference maxVSUrl, Coding c) {
697    // TODO Auto-generated method stub
698    ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
699    if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found")) {
700      try {
701        long t = System.nanoTime();
702        ValidationResult vr = context.validateCode(c, valueset);
703        txTime = txTime + (System.nanoTime() - t);
704        if (!vr.isOk()) {
705          rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+", and a code from this value set is required) (code = "+c.getSystem()+"#"+c.getCode()+")");
706        }
707      } catch (Exception e) {
708        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept using maxValueSet");
709      }
710    }
711  }
712
713  private void checkMaxValueSet(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, Reference maxVSUrl, String value) {
714    // TODO Auto-generated method stub
715    ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
716    if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found")) {
717      try {
718        long t = System.nanoTime();
719        ValidationResult vr = context.validateCode(null, value, null, valueset);
720        txTime = txTime + (System.nanoTime() - t);
721        if (!vr.isOk()) {
722          if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) 
723            warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided could not be validated against the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+"), (error = "+vr.getMessage()+")");
724          else
725            rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl()+"), and a code from this value set is required) (code = "+value+"), (error = "+vr.getMessage()+")");
726        }
727      } catch (Exception e) {
728        warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating CodeableConcept using maxValueSet");
729      }
730    }
731  }
732
733  private String ccSummary(CodeableConcept cc) {
734    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
735    for (Coding c : cc.getCoding()) 
736      b.append(c.getSystem()+"#"+c.getCode());
737    return b.toString();
738  }
739
740  private CodeableConcept readAsCodeableConcept(Element element) {
741    CodeableConcept cc = new CodeableConcept();
742    List<Element> list = new ArrayList<Element>();
743    element.getNamedChildren("coding", list);
744    for (Element item : list)
745      cc.addCoding(readAsCoding(item));
746    cc.setText(element.getNamedChildValue("text"));
747    return cc;
748  }
749
750  private Coding readAsCoding(Element item) {
751    Coding c = new Coding();
752    c.setSystem(item.getNamedChildValue("system"));
753    c.setVersion(item.getNamedChildValue("version"));
754    c.setCode(item.getNamedChildValue("code"));
755    c.setDisplay(item.getNamedChildValue("display"));
756    return c;
757  }
758
759  private Reference readAsReference(Element item) {
760    Reference r = new Reference();
761    r.setDisplay(item.getNamedChildValue("display"));
762    r.setReference(item.getNamedChildValue("reference"));
763    List<Element> identifier = item.getChildrenByName("identifier");
764    if (identifier.isEmpty() == false) {
765      r.setIdentifier(readAsIdentifier(identifier.get(0)));
766    }
767    return r;
768  }
769
770  private Identifier readAsIdentifier(Element item) {
771    Identifier r = new Identifier();
772    r.setSystem(item.getNamedChildValue("system"));
773    r.setValue(item.getNamedChildValue("value"));
774    return r;
775  }
776
777  private void checkCoding(List<ValidationMessage> errors, String path, Element focus, Coding fixed) {
778    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus);
779    checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus);
780    checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display", focus);
781    checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected", focus);
782  }
783
784  private void checkCoding(List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept)  {
785    String code = element.getNamedChildValue("code");
786    String system = element.getNamedChildValue("system");
787    String display = element.getNamedChildValue("display");
788    rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference");
789
790    if (system != null && code != null && !noTerminologyChecks) {
791      rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), "The Coding references a value set, not a code system (\""+system+"\")");
792      try {
793        if (checkCode(errors, element, path, code, system, display))
794          if (theElementCntext != null && theElementCntext.hasBinding()) {
795            ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
796            if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
797              if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
798                ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
799                if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found")) {
800                  try {
801                    Coding c = readAsCoding(element);
802                    long t = System.nanoTime();
803                    ValidationResult vr = context.validateCode(c, valueset);
804                    txTime = txTime + (System.nanoTime() - t);
805                    if (!vr.isOk()) {
806                      if (vr.IsNoService())
807                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "The value provided could not be validated in the absence of a terminology server");
808                      else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
809                        if (binding.getStrength() == BindingStrength.REQUIRED)
810                          warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl()+", and a code from this value set is required)"); 
811                        else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
812                          if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 
813                            checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), c);
814                          else
815                            warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code)");
816                        } else if (binding.getStrength() == BindingStrength.PREFERRED)
817                          hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set)");
818                      } else if (binding.getStrength() == BindingStrength.REQUIRED)
819                        rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is required from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : ""));
820                      else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
821                        if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 
822                          checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), c);
823                        else
824                          warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : ""));
825                      } else if (binding.getStrength() == BindingStrength.PREFERRED)
826                        hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "The Coding provided is not in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : ""));
827                    }
828                  } catch (Exception e) {
829                    warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating Coding");
830                  }
831                }
832              } else if (binding.hasValueSet()) {
833                hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
834              } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
835                hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
836              }
837            }
838          }
839      } catch (Exception e) {
840        rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error "+e.getMessage()+" validating Coding");
841      }
842    }
843  }
844
845  private boolean isValueSet(String url) {
846    try {
847      ValueSet vs = context.fetchResourceWithException(ValueSet.class, url);
848      return vs != null;
849    } catch (Exception e) {
850      return false;
851    }
852  }
853
854  private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed) {
855    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus);
856    checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus);
857    checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus);
858    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus);
859
860  }
861
862  protected void checkDeclaredProfiles(ResourceProfiles resourceProfiles, List<ValidationMessage> errors, Element resource, Element element, NodeStack stack) throws FHIRException {
863    Element meta = element.getNamedChild("meta");
864    if (meta != null) {
865      List<Element> profiles = new ArrayList<Element>();
866      meta.getNamedChildren("profile", profiles);
867      int i = 0;
868      for (Element profile : profiles) {
869        String ref = profile.primitiveValue();
870        String p = stack.addToLiteralPath("meta", "profile", ":" + Integer.toString(i));
871        if (rule(errors, IssueType.INVALID, element.line(), element.col(), p, !Utilities.noString(ref), "StructureDefinition reference invalid")) {
872          long t = System.nanoTime();
873          resourceProfiles.addProfile(errors, ref, errorForUnknownProfiles, p, element);
874          i++;
875        }
876      }
877    }
878  }
879
880  private StructureDefinition checkExtension(Object appContext, List<ValidationMessage> errors, String path, Element resource, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack) throws FHIRException, IOException {
881    String url = element.getNamedChildValue("url");
882    boolean isModifier = element.getName().equals("modifierExtension");
883
884    long t = System.nanoTime();
885    StructureDefinition ex = context.fetchResource(StructureDefinition.class, url);
886    sdTime = sdTime + (System.nanoTime() - t);
887    if (ex == null) {
888      if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here"))
889        hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), "Unknown extension " + url);
890    } else {
891      if (def.getIsModifier())
892        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
893            "Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not");
894      else
895        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
896            "Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is");
897
898      // two questions
899      // 1. can this extension be used here?
900      checkExtensionContext(errors, element, /* path+"[url='"+url+"']", */ ex, stack, ex.getUrl());
901
902      if (isModifier)
903        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
904            "The Extension '" + url + "' must be used as a modifierExtension");
905      else
906        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
907            "The Extension '" + url + "' must not be used as an extension (it's a modifierExtension)");
908
909      // check the type of the extension:
910      Set<String> allowedTypes = listExtensionTypes(ex);
911      String actualType = getExtensionType(element);
912      if (actualType == null) 
913        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.isEmpty(), "The Extension '" + url + "' definition is for a complex extension, so it cannot contain a value");
914      else 
915        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types "+allowedTypes.toString()+" but found type "+actualType);      
916      
917      // 3. is the content of the extension valid?
918      validateElement(appContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false);
919
920    }
921    return ex;
922  }
923
924  private String getExtensionType(Element element) {
925    for (Element e : element.getChildren()) {
926      if (e.getName().startsWith("value")) {
927        String tn = e.getName().substring(5);
928        String ltn = Utilities.uncapitalize(tn);
929        if (isPrimitiveType(ltn))
930          return ltn;
931        else
932          return tn;
933      }
934    }
935    return null;
936  }
937
938  private Set<String> listExtensionTypes(StructureDefinition ex) {
939    ElementDefinition vd = null;
940    for (ElementDefinition ed : ex.getSnapshot().getElement()) {
941      if (ed.getPath().startsWith("Extension.value")) {
942        vd = ed;
943        break;
944      }
945    }
946    Set<String> res = new HashSet<String>();
947    if (vd != null && !"0".equals(vd.getMax())) {
948      for (TypeRefComponent tr : vd.getType()) {
949        res.add(tr.getCode());
950      }
951    }
952    return res;
953  }
954
955  private boolean checkExtensionContext(List<ValidationMessage> errors, Element element, StructureDefinition definition, NodeStack stack, String extensionParent) {
956    String extUrl = definition.getUrl();
957    CommaSeparatedStringBuilder p = new CommaSeparatedStringBuilder();
958    for (String lp : stack.getLogicalPaths())
959      p.append(lp);
960    if (definition.getContextType() == ExtensionContext.DATATYPE) {
961      boolean ok = false;
962      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
963      for (StringType ct : definition.getContext()) {
964        b.append(ct.getValue());
965        if (ct.getValue().equals("*") || stack.getLogicalPaths().contains(ct.getValue() + ".extension"))
966          ok = true;
967      }
968      return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok,
969          "The extension " + extUrl + " is not allowed to be used on the logical path set [" + p.toString() + "] (allowed: datatype=" + b.toString() + ")");
970    } else if (definition.getContextType() == ExtensionContext.EXTENSION) {
971      boolean ok = false;
972      for (StringType ct : definition.getContext())
973        if (ct.getValue().equals("*") || ct.getValue().equals(extensionParent))
974          ok = true;
975      return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok,
976          "The extension " + extUrl + " is not allowed to be used with the extension '" + extensionParent + "'");
977    } else if (definition.getContextType() == ExtensionContext.RESOURCE) {
978      boolean ok = false;
979      // String simplePath = container.getPath();
980      // System.out.println(simplePath);
981      // if (effetive.endsWith(".extension") || simplePath.endsWith(".modifierExtension"))
982      // simplePath = simplePath.substring(0, simplePath.lastIndexOf('.'));
983      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
984      for (StringType ct : definition.getContext()) {
985        String c = ct.getValue();
986        b.append(c);
987        if (c.equals("*") || stack.getLogicalPaths().contains(c + ".extension") || (c.startsWith("@") && stack.getLogicalPaths().contains(c.substring(1) + ".extension")))
988          ;
989        ok = true;
990      }
991      return rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), ok,
992          "The extension " + extUrl + " is not allowed to be used on the logical path set " + p.toString() + " (allowed: resource=" + b.toString() + ")");
993    } else
994      throw new Error("Unknown context type");
995  }
996  //
997  // private String simplifyPath(String path) {
998  // String s = path.replace("/f:", ".");
999  // while (s.contains("["))
1000  // s = s.substring(0, s.indexOf("["))+s.substring(s.indexOf("]")+1);
1001  // String[] parts = s.split("\\.");
1002  // int i = 0;
1003  // while (i < parts.length && !context.getProfiles().containsKey(parts[i].toLowerCase()))
1004  // i++;
1005  // if (i >= parts.length)
1006  // throw new Error("Unable to process part "+path);
1007  // int j = parts.length - 1;
1008  // while (j > 0 && (parts[j].equals("extension") || parts[j].equals("modifierExtension")))
1009  // j--;
1010  // StringBuilder b = new StringBuilder();
1011  // boolean first = true;
1012  // for (int k = i; k <= j; k++) {
1013  // if (k == j || !parts[k].equals(parts[k+1])) {
1014  // if (first)
1015  // first = false;
1016  // else
1017  // b.append(".");
1018  // b.append(parts[k]);
1019  // }
1020  // }
1021  // return b.toString();
1022  // }
1023  //
1024
1025  private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.dstu3.model.Element fixed, String propName, Element parent) {
1026    if ((fixed == null || fixed.isEmpty()) && focus == null)
1027      ; // this is all good
1028    else if (fixed == null && focus != null)
1029      rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Unexpected element " + focus.getName());
1030    else if (fixed != null && !fixed.isEmpty() && focus == null)
1031      rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, "Missing element '" + propName+"'");
1032    else {
1033      String value = focus.primitiveValue();
1034      if (fixed instanceof org.hl7.fhir.dstu3.model.BooleanType)
1035        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.BooleanType) fixed).asStringValue(), value),
1036            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.BooleanType) fixed).asStringValue() + "'");
1037      else if (fixed instanceof org.hl7.fhir.dstu3.model.IntegerType)
1038        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.IntegerType) fixed).asStringValue(), value),
1039            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.IntegerType) fixed).asStringValue() + "'");
1040      else if (fixed instanceof org.hl7.fhir.dstu3.model.DecimalType)
1041        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.DecimalType) fixed).asStringValue(), value),
1042            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.DecimalType) fixed).asStringValue() + "'");
1043      else if (fixed instanceof org.hl7.fhir.dstu3.model.Base64BinaryType)
1044        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.Base64BinaryType) fixed).asStringValue(), value),
1045            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.Base64BinaryType) fixed).asStringValue() + "'");
1046      else if (fixed instanceof org.hl7.fhir.dstu3.model.InstantType)
1047        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.InstantType) fixed).getValue().toString(), value),
1048            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.InstantType) fixed).asStringValue() + "'");
1049      else if (fixed instanceof org.hl7.fhir.dstu3.model.StringType)
1050        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.StringType) fixed).getValue(), value),
1051            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.StringType) fixed).getValue() + "'");
1052      else if (fixed instanceof org.hl7.fhir.dstu3.model.UriType)
1053        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.UriType) fixed).getValue(), value),
1054            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.UriType) fixed).getValue() + "'");
1055      else if (fixed instanceof org.hl7.fhir.dstu3.model.DateType)
1056        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.DateType) fixed).getValue().toString(), value),
1057            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.DateType) fixed).getValue() + "'");
1058      else if (fixed instanceof org.hl7.fhir.dstu3.model.DateTimeType)
1059        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.DateTimeType) fixed).getValue().toString(), value),
1060            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.DateTimeType) fixed).getValue() + "'");
1061      else if (fixed instanceof org.hl7.fhir.dstu3.model.OidType)
1062        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.OidType) fixed).getValue(), value),
1063            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.OidType) fixed).getValue() + "'");
1064      else if (fixed instanceof org.hl7.fhir.dstu3.model.UuidType)
1065        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.UuidType) fixed).getValue(), value),
1066            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.UuidType) fixed).getValue() + "'");
1067      else if (fixed instanceof org.hl7.fhir.dstu3.model.CodeType)
1068        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.CodeType) fixed).getValue(), value),
1069            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.CodeType) fixed).getValue() + "'");
1070      else if (fixed instanceof org.hl7.fhir.dstu3.model.IdType)
1071        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.dstu3.model.IdType) fixed).getValue(), value),
1072            "Value is '" + value + "' but must be '" + ((org.hl7.fhir.dstu3.model.IdType) fixed).getValue() + "'");
1073      else if (fixed instanceof Quantity)
1074        checkQuantity(errors, path, focus, (Quantity) fixed);
1075      else if (fixed instanceof Address)
1076        checkAddress(errors, path, focus, (Address) fixed);
1077      else if (fixed instanceof ContactPoint)
1078        checkContactPoint(errors, path, focus, (ContactPoint) fixed);
1079      else if (fixed instanceof Attachment)
1080        checkAttachment(errors, path, focus, (Attachment) fixed);
1081      else if (fixed instanceof Identifier)
1082        checkIdentifier(errors, path, focus, (Identifier) fixed);
1083      else if (fixed instanceof Coding)
1084        checkCoding(errors, path, focus, (Coding) fixed);
1085      else if (fixed instanceof HumanName)
1086        checkHumanName(errors, path, focus, (HumanName) fixed);
1087      else if (fixed instanceof CodeableConcept)
1088        checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed);
1089      else if (fixed instanceof Timing)
1090        checkTiming(errors, path, focus, (Timing) fixed);
1091      else if (fixed instanceof Period)
1092        checkPeriod(errors, path, focus, (Period) fixed);
1093      else if (fixed instanceof Range)
1094        checkRange(errors, path, focus, (Range) fixed);
1095      else if (fixed instanceof Ratio)
1096        checkRatio(errors, path, focus, (Ratio) fixed);
1097      else if (fixed instanceof SampledData)
1098        checkSampledData(errors, path, focus, (SampledData) fixed);
1099
1100      else
1101        rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, "Unhandled fixed value type " + fixed.getClass().getName());
1102      List<Element> extensions = new ArrayList<Element>();
1103      focus.getNamedChildren("extension", extensions);
1104      if (fixed.getExtension().size() == 0) {
1105        rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed");
1106      } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(),
1107          "Extensions count mismatch: expected " + Integer.toString(fixed.getExtension().size()) + " but found " + Integer.toString(extensions.size()))) {
1108        for (Extension e : fixed.getExtension()) {
1109          Element ex = getExtensionByUrl(extensions, e.getUrl());
1110          if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, "Extension count mismatch: unable to find extension: " + e.getUrl())) {
1111            checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), "extension.value", ex.getNamedChild("extension"));
1112          }
1113        }
1114      }
1115    }
1116  }
1117
1118  private void checkHumanName(List<ValidationMessage> errors, String path, Element focus, HumanName fixed) {
1119    checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus);
1120    checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus);
1121    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus);
1122
1123    List<Element> parts = new ArrayList<Element>();
1124    focus.getNamedChildren("family", parts);
1125    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(),
1126        "Expected " + (fixed.hasFamily() ? "1" : "0") + " but found " + Integer.toString(parts.size()) + " family elements")) {
1127      for (int i = 0; i < parts.size(); i++)
1128        checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), "family", focus);
1129    }
1130    focus.getNamedChildren("given", parts);
1131    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(),
1132        "Expected " + Integer.toString(fixed.getGiven().size()) + " but found " + Integer.toString(parts.size()) + " given elements")) {
1133      for (int i = 0; i < parts.size(); i++)
1134        checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), "given", focus);
1135    }
1136    focus.getNamedChildren("prefix", parts);
1137    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(),
1138        "Expected " + Integer.toString(fixed.getPrefix().size()) + " but found " + Integer.toString(parts.size()) + " prefix elements")) {
1139      for (int i = 0; i < parts.size(); i++)
1140        checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), "prefix", focus);
1141    }
1142    focus.getNamedChildren("suffix", parts);
1143    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(),
1144        "Expected " + Integer.toString(fixed.getSuffix().size()) + " but found " + Integer.toString(parts.size()) + " suffix elements")) {
1145      for (int i = 0; i < parts.size(); i++)
1146        checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), "suffix", focus);
1147    }
1148  }
1149
1150  private void checkIdentifier(List<ValidationMessage> errors, String path, Element element, ElementDefinition context) {
1151    String system = element.getNamedChildValue("system");
1152    rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Identifier.system must be an absolute reference, not a local reference");
1153  }
1154
1155  private void checkIdentifier(List<ValidationMessage> errors, String path, Element focus, Identifier fixed) {
1156    checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus);
1157    checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), "type", focus);
1158    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus);
1159    checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus);
1160    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus);
1161    checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), "assigner", focus);
1162  }
1163
1164  private void checkPeriod(List<ValidationMessage> errors, String path, Element focus, Period fixed) {
1165    checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), "start", focus);
1166    checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus);
1167  }
1168
1169  private void checkPrimitive(Object appContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) throws FHIRException, IOException {
1170    if (isBlank(e.primitiveValue())) {
1171      rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "primitive types must have a value or must have child extensions");
1172      return;
1173    }
1174    String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
1175    if (regex!=null)
1176        rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), "Element value '" + e.primitiveValue() + "' does not meet regex '" + regex + "'");
1177    
1178    if (type.equals("boolean")) { 
1179      rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'");
1180    }
1181    if (type.equals("uri")) {
1182      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("oid:"), "URI values cannot start with oid:");
1183      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:");
1184      rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace");
1185      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 ||  e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
1186      
1187      // now, do we check the URI target? 
1188      if (fetcher != null) {
1189        rule(errors, IssueType.INVALID, e.line(), e.col(), path, fetcher.resolveURL(appContext, path, e.primitiveValue()), "URL value '"+e.primitiveValue()+"' does not resolve");
1190      }
1191    }
1192    if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
1193      if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) {
1194        warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), "value should not start or finish with whitespace");
1195        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 ||  e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
1196      }
1197    }
1198    if (type.equals("dateTime")) {
1199      rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year");
1200      rule(errors, IssueType.INVALID, e.line(), e.col(), path,
1201          e.primitiveValue()
1202          .matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),
1203          "Not a valid date time");
1204      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), "if a date has a time, it must have a timezone");
1205      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 ||  e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
1206    }
1207    if (type.equals("date")) {
1208        rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year");
1209        rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("-?[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1]))?)?"),
1210            "Not a valid date");
1211        rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 ||  e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum value of " + context.getMaxLength());
1212    }
1213    if (type.equals("integer")) {
1214      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= new Integer(e.getValue()).intValue()), "value is greater than permitted maximum value of " + (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : ""));
1215      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= new Integer(e.getValue()).intValue()), "value is less than permitted minimum value of " + (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : ""));
1216    }
1217    if (type.equals("instant")) {
1218      rule(errors, IssueType.INVALID, e.line(), e.col(), path,
1219          e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[0-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"),
1220          "The instant '" + e.primitiveValue() + "' is not valid (by regex)");
1221      rule(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' does not have a valid year");
1222    }
1223
1224    if (type.equals("code") && e.primitiveValue() != null) {
1225      // 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
1226      // other than single spaces in the contents
1227      rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), "The code '" + e.primitiveValue() + "' is not valid (whitespace rules)");
1228      rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 ||  e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
1229    }
1230
1231    if (context.hasBinding() && e.primitiveValue() != null) {
1232      checkPrimitiveBinding(errors, path, type, context, e, profile);
1233    }
1234
1235    if (type.equals("xhtml")) {
1236      XhtmlNode xhtml = e.getXhtml();
1237      if (xhtml != null) { // if it is null, this is an error already noted in the parsers
1238        // check that the namespace is there and correct.
1239        String ns = xhtml.getNsDecl();
1240        rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')");
1241        // check that inner namespaces are all correct 
1242        checkInnerNS(errors, e, path, xhtml.getChildNodes());
1243        rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), "Wrong name on the XHTML ('"+ns+"') - must start with div");
1244        // check that no illegal elements and attributes have been used
1245        checkInnerNames(errors, e, path, xhtml.getChildNodes());
1246      } 
1247    }
1248
1249    if (context.hasFixed())
1250      checkFixedValue(errors,path,e, context.getFixed(), context.getSliceName(), null);
1251
1252    // for nothing to check
1253  }
1254
1255  private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
1256    for (XhtmlNode node : list) {
1257      if (node.getNodeType() == NodeType.Element) {
1258        rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), 
1259            "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
1260            "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
1261            "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
1262            "code", "samp", "img", "map", "area"
1263
1264            ), "Illegal element name in the XHTML ('"+node.getName()+"')");
1265        for (String an : node.getAttributes().keySet()) {
1266          boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an, 
1267              "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex",
1268              // tables
1269              "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
1270
1271              Utilities.existsInList(node.getName()+"."+an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
1272                  "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
1273                  "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
1274                  "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
1275                  "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space"
1276                  );
1277          if (!ok)
1278            rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Illegal attribute name in the XHTML ('"+an+"' on '"+node.getName()+"')");
1279        }
1280        checkInnerNames(errors, e, path, node.getChildNodes());
1281      }
1282    }   
1283  }
1284
1285  private void checkInnerNS(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
1286    for (XhtmlNode node : list) {
1287      if (node.getNodeType() == NodeType.Element) {
1288        String ns = node.getNsDecl();
1289        rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('"+ns+"')");
1290        checkInnerNS(errors, e, path, node.getChildNodes());
1291      }
1292    }   
1293  }
1294
1295  private void checkPrimitiveBinding(List<ValidationMessage> errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile) {
1296    // We ignore bindings that aren't on string, uri or code
1297    if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type))) {
1298      return;
1299    }
1300    if (noTerminologyChecks)
1301      return;
1302
1303    String value = element.primitiveValue();
1304    // System.out.println("check "+value+" in "+path);
1305
1306    // firstly, resolve the value set
1307    ElementDefinitionBindingComponent binding = elementContext.getBinding();
1308    if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
1309      ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
1310      if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) {
1311        long t = System.nanoTime();
1312        ValidationResult vr = context.validateCode(null, value, null, vs);
1313        txTime = txTime + (System.nanoTime() - t);
1314        if (vr != null && !vr.isOk()) {
1315          if (vr.IsNoService())
1316            hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "The value provided ('"+value+"') could not be validated in the absence of a terminology server");
1317          else if (binding.getStrength() == BindingStrength.REQUIRED)
1318            rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is required from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : ""));
1319          else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
1320            if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) 
1321              checkMaxValueSet(errors, path, element, profile, (Reference) binding.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet").get(0).getValue(), value);
1322            else
1323              warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code should come from this value set unless it has no suitable code)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : ""));
1324          } else if (binding.getStrength() == BindingStrength.PREFERRED)
1325            hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,  "The value provided ('"+value+"') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is recommended to come from this value set)"+(vr.getMessage() != null ? " (error message = "+vr.getMessage()+")" : ""));
1326        }
1327      }
1328    } else if (!noBindingMsgSuppressed)
1329      hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), "Binding has no source, so can't be checked");
1330  }
1331
1332  private void checkQuantity(List<ValidationMessage> errors, String path, Element focus, Quantity fixed) {
1333    checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus);
1334    checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), "comparator", focus);
1335    checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), "units", focus);
1336    checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus);
1337    checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus);
1338  }
1339
1340  // implementation
1341
1342  private void checkRange(List<ValidationMessage> errors, String path, Element focus, Range fixed) {
1343    checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), "low", focus);
1344    checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), "high", focus);
1345
1346  }
1347
1348  private void checkRatio(List<ValidationMessage> errors, String path, Element focus, Ratio fixed) {
1349    checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), "numerator", focus);
1350    checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), "denominator", focus);
1351  }
1352
1353  private void checkReference(Object appContext, List<ValidationMessage> errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) throws FHIRException, IOException {
1354    Reference reference = readAsReference(element);
1355    
1356    String ref = reference.getReference();
1357    if (Utilities.noString(ref)) {
1358      if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) {
1359        warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference or identifier should have a display");
1360      }
1361      return;
1362    }
1363
1364    Element we = localResolve(ref, stack, errors, path);
1365    String refType;
1366    if (ref.startsWith("#")) {
1367      refType = "contained";
1368    } else {
1369      if (we == null) {
1370        refType = "remote";
1371      } else {
1372        refType = "bundle";
1373      }
1374    }
1375    String ft;
1376    if (we != null)
1377      ft = we.getType();
1378    else
1379      ft = tryParse(ref);
1380    ReferenceValidationPolicy pol = refType.equals("contained") ? ReferenceValidationPolicy.CHECK_VALID : fetcher == null ? ReferenceValidationPolicy.IGNORE : fetcher.validationPolicy(appContext, path, ref);
1381
1382    if (pol.checkExists()) {
1383      if (we == null) {
1384        if (fetcher == null)
1385          throw new FHIRException("Resource resolution services not provided");
1386        we = fetcher.fetch(appContext, ref);
1387      }
1388      rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, we != null, "Unable to resolve resource '"+ref+"'");
1389    }
1390
1391    if (we != null && pol.checkType()) {
1392      if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft!=null, "Unable to determine type of target resource")) {
1393        boolean ok = false;
1394        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1395        for (TypeRefComponent type : container.getType()) {
1396          if (!ok && type.getCode().equals("Reference")) {
1397            // we validate as much as we can. First, can we infer a type from the profile? 
1398            if (!type.hasTargetProfile() || type.getTargetProfile().equals("http://hl7.org/fhir/StructureDefinition/Resource"))
1399              ok = true;
1400            else {
1401              String pr = type.getTargetProfile();
1402
1403              String bt = getBaseType(profile, pr);
1404              StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + bt);
1405              if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) {
1406                b.append(bt);
1407                ok = bt.equals(ft);
1408                if (ok && we!=null && pol.checkValid()) {
1409                  doResourceProfile(appContext, we, pr, errors, stack.push(we, -1, null, null), path, element);
1410                }
1411              } else
1412                ok = true; // suppress following check
1413              if (ok && type.hasAggregation()) {
1414                boolean modeOk;
1415                for (Enumeration<AggregationMode> mode : type.getAggregation()) {
1416                  if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
1417                    ok = true;
1418                  else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
1419                    ok = true;
1420                  else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled")||refType.equals("remote")))
1421                    ok = true;
1422                }
1423                rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference");
1424              }
1425            }
1426          }
1427          if (!ok && type.getCode().equals("*")) {
1428            ok = true; // can refer to anything
1429          }
1430        }
1431        rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + b.toString() + ")");
1432      }
1433    }
1434    if (pol == ReferenceValidationPolicy.CHECK_VALID) {
1435      // todo....
1436    }
1437  }
1438  
1439  private void doResourceProfile(Object appContext, Element resource, String profile, List<ValidationMessage> errors, NodeStack stack, String path, Element element) throws FHIRException, IOException {
1440    ResourceProfiles resourceProfiles = addResourceProfile(errors, resource, profile, path, element, stack);
1441    if (resourceProfiles.isProcessed()) {
1442      start(appContext, errors, resource, resource, null, stack);  
1443    }
1444  }
1445
1446  private ResourceProfiles getResourceProfiles(Element resource, NodeStack stack) {
1447    ResourceProfiles resourceProfiles = resourceProfilesMap.get(resource);
1448    if (resourceProfiles==null) {
1449      resourceProfiles = new ResourceProfiles(resource, stack);
1450      resourceProfilesMap.put(resource, resourceProfiles);
1451    }
1452    return resourceProfiles;
1453  }
1454
1455  private ResourceProfiles addResourceProfile(List<ValidationMessage> errors, Element resource, String profile, String path, Element element, NodeStack stack) {
1456    ResourceProfiles resourceProfiles = getResourceProfiles(resource, stack);
1457    resourceProfiles.addProfile(errors, profile, errorForUnknownProfiles, path, element);
1458    return resourceProfiles;
1459  }
1460
1461  private String checkResourceType(String type)  {
1462    long t = System.nanoTime();
1463    try {
1464      if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
1465        return type;
1466      else
1467        return null;
1468    } finally {
1469      sdTime = sdTime + (System.nanoTime() - t);
1470    }
1471  }
1472
1473  private void checkSampledData(List<ValidationMessage> errors, String path, Element focus, SampledData fixed) {
1474    checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), "origin", focus);
1475    checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), "period", focus);
1476    checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), "factor", focus);
1477    checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), "lowerLimit", focus);
1478    checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), "upperLimit", focus);
1479    checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), "dimensions", focus);
1480    checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus);
1481  }
1482
1483  private void checkTiming(List<ValidationMessage> errors, String path, Element focus, Timing fixed) {
1484    checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), "value", focus);
1485
1486    List<Element> events = new ArrayList<Element>();
1487    focus.getNamedChildren("event", events);
1488    if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(),
1489        "Expected " + Integer.toString(fixed.getEvent().size()) + " but found " + Integer.toString(events.size()) + " event elements")) {
1490      for (int i = 0; i < events.size(); i++)
1491        checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), "event", focus);
1492    }
1493  }
1494
1495  private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
1496    for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
1497      if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
1498        return true;
1499      if (codeinExpansion(c, system, code))
1500        return true;
1501    }    
1502    return false;
1503  }
1504
1505  private boolean codeInExpansion(ValueSet vs, String system, String code) {
1506    for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
1507      if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
1508        return true;
1509      if (codeinExpansion(c, system, code))
1510        return true;
1511    }
1512    return false;
1513  }
1514
1515  private String describeReference(Type reference) {
1516    if (reference == null)
1517      return "null";
1518    if (reference instanceof UriType)
1519      return ((UriType) reference).getValue();
1520    if (reference instanceof Reference)
1521      return ((Reference) reference).getReference();
1522    return "??";
1523  }
1524
1525  private String describeTypes(List<TypeRefComponent> types) {
1526    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1527    for (TypeRefComponent t : types) {
1528      b.append(t.getCode());
1529    }
1530    return b.toString();
1531  }
1532
1533  protected ElementDefinition findElement(StructureDefinition profile, String name) {
1534    for (ElementDefinition c : profile.getSnapshot().getElement()) {
1535      if (c.getPath().equals(name)) {
1536        return c;
1537      }
1538    }
1539    return null;
1540  }
1541
1542  public BestPracticeWarningLevel getBasePracticeWarningLevel() {
1543    return bpWarnings;
1544  }  
1545
1546  private String getBaseType(StructureDefinition profile, String pr)  {
1547    StructureDefinition p = resolveProfile(profile, pr);
1548    if (p == null)
1549      return null;
1550    else 
1551      return p.getType();
1552  }
1553
1554  @Override
1555  public CheckDisplayOption getCheckDisplay() {
1556    return checkDisplay;
1557  }
1558
1559  //    private String findProfileTag(Element element) {
1560  //    String uri = null;
1561  //      List<Element> list = new ArrayList<Element>();
1562  //      element.getNamedChildren("category", list);
1563  //      for (Element c : list) {
1564  //            if ("http://hl7.org/fhir/tag/profile".equals(c.getAttribute("scheme"))) {
1565  //                    uri = c.getAttribute("term");
1566  //            }
1567  //      }
1568  //      return uri;
1569  //  }
1570
1571  private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
1572    if (code.equals(c.getCode()))
1573      return c;
1574    for (ConceptDefinitionComponent g : c.getConcept()) {
1575      ConceptDefinitionComponent r = getCodeDefinition(g, code);
1576      if (r != null)
1577        return r;
1578    }
1579    return null;
1580  }
1581
1582  private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
1583    for (ConceptDefinitionComponent c : cs.getConcept()) {
1584      ConceptDefinitionComponent r = getCodeDefinition(c, code);
1585      if (r != null)
1586        return r;
1587    }
1588    return null;
1589  }
1590
1591  private Element getContainedById(Element container, String id) {
1592    List<Element> contained = new ArrayList<Element>();
1593    container.getNamedChildren("contained", contained);
1594    for (Element we : contained) {
1595      if (id.equals(we.getNamedChildValue("id")))
1596        return we;
1597    }   
1598    return null;
1599  }
1600
1601  public IWorkerContext getContext() {
1602    return context;
1603  }
1604
1605  private ElementDefinition getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile) throws DefinitionException {
1606    StructureDefinition sd = profile;
1607    ElementDefinition ed = element;
1608    
1609    if ("value".equals(discriminator) && element.hasFixed())
1610      return element;
1611    
1612    List<String> nodes = new ArrayList<String>();
1613    Matcher matcher = Pattern.compile("\\.([a-zA-Z0-0]+(\\([^\\(^\\)]*\\))?)").matcher("."+discriminator);
1614    while (matcher.find()) {
1615      if (!matcher.group(1).startsWith("@"))
1616        nodes.add(matcher.group(1));
1617    }
1618    
1619    for (String fullnode : nodes) {
1620      String node = fullnode.contains("(") ? fullnode.substring(0, fullnode.indexOf('(')) : fullnode;
1621      String qualifier = fullnode.contains("(") ? fullnode.substring(fullnode.indexOf('(') + 2, fullnode.length()-2) : null;
1622      if (qualifier!=null && !node.equals("extension"))
1623        throw new DefinitionException("Function specified other than 'extension()': " + discriminator);
1624      // get the children of element
1625      List<ElementDefinition> childDefinitions;
1626      childDefinitions = ProfileUtilities.getChildMap(sd, ed);
1627      // if that's empty, get the children of the type
1628      if (childDefinitions.isEmpty()) {
1629        if (ed.getType().size() == 0)
1630          throw new DefinitionException("Error in profile for " + path + " no children, no type");
1631        if (ed.getType().size() > 1)
1632          throw new DefinitionException("Error in profile for " + path + " multiple types defined in slice discriminator");
1633        if (ed.hasSlicing()) {
1634          List<ElementDefinition> slices = ProfileUtilities.getSliceList(profile, ed);
1635          boolean found = false;
1636          for (ElementDefinition sed: slices) {
1637            childDefinitions = ProfileUtilities.getChildMap(sd, sed);
1638            for (ElementDefinition t : childDefinitions) {
1639              if (tailMatches(t, node)) {
1640                found = true;
1641                if (qualifier!=null) {
1642                  List<ElementDefinition> childNodes = ProfileUtilities.getChildList(profile, t);
1643                  if (childNodes.isEmpty()) {
1644                    if (t.getType().size()==1 && t.getType().get(0).hasProfile() && t.getType().get(0).getProfile().equals(qualifier)) {
1645                      found = true;
1646                      break;
1647                    }
1648                      
1649                  } else {
1650                    for (ElementDefinition c : childNodes) {
1651                      if (c.getPath().endsWith(".url") && c.hasFixed() && c.getFixed() instanceof UriType && ((UriType)c.getFixed()).equals(qualifier)) {
1652                        found = true;
1653                        break;
1654                      }
1655                    }
1656                  }
1657                }
1658                if (found) {
1659                  ed = t;
1660                  break;
1661                }
1662              }
1663            }
1664          }
1665          if (found)
1666            continue;
1667        }
1668        if (ed.getType().get(0).hasProfile() && ed.getType().get(0).getCode().equals("Reference") && node.equals("reference")) {
1669          long t1 = System.nanoTime();
1670          sd = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getTargetProfile());
1671          sdTime = sdTime + (System.nanoTime() - t1);
1672          // we've skipped the reference.
1673          ed = sd.getSnapshot().getElementFirstRep();
1674          continue;
1675        } else if (ed.getType().get(0).hasProfile()) {
1676          long t2 = System.nanoTime();
1677          sd = context.fetchResource(StructureDefinition.class, ed.getType().get(0).getProfile());
1678          sdTime = sdTime + (System.nanoTime() - t2);
1679        } else {
1680          long t2 = System.nanoTime();
1681          sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + ed.getType().get(0).getCode());
1682          sdTime = sdTime + (System.nanoTime() - t2);
1683        }
1684        if (sd == null)
1685          throw new DefinitionException("Error in profile for " + path + ": unable to find type for "+ed.getId());
1686        childDefinitions = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep());
1687      }
1688      // find a single match. (no slicing!)
1689      boolean found = false;
1690      for (ElementDefinition t : childDefinitions) {
1691        if (tailMatches(t, node)) {
1692          found = true;
1693          if (qualifier!=null) {
1694            found = false;
1695            List<ElementDefinition> childNodes = ProfileUtilities.getChildList(profile, t);
1696            if (childNodes.isEmpty()) {
1697              if (t.getType().size()==1 && t.getType().get(0).hasProfile() && t.getType().get(0).getProfile().equals(qualifier)) {
1698                found = true;
1699                ed = t;
1700                break;
1701              }
1702                
1703            } else {
1704              for (ElementDefinition c : childNodes) {
1705                if (c.getPath().endsWith(".url") && c.hasFixed() && c.getFixed() instanceof UriType && ((UriType)c.getFixed()).equals(qualifier)) {
1706                  found = true;
1707                  ed = t;
1708                  break;
1709                }
1710              }
1711            }
1712          }
1713          if (found) {
1714            ed = t;
1715            break;
1716          }
1717        }
1718      }
1719      if (!found)
1720        throw new DefinitionException("Unable to find definition for discriminator "+discriminator+" from "+path+" in "+profile.getUrl());
1721    }
1722    return ed;
1723    //    List<ElementDefinition> snapshot = null;
1724    //    int index;
1725    //    if (childDefinitions.isEmpty()) {
1726    //      // going to look at the type
1727    //      if (ed.getType().size() == 0)
1728    //        throw new DefinitionException("Error in profile for " + path + " no children, no type");
1729    //      snapshot = type.getSnapshot().getElement();
1730    //      ed = snapshot.get(0);
1731    //      index = 0;
1732    //    } else {
1733    //      snapshot = childDefinitions;
1734    //      index = -1;
1735    //    }
1736    //    String originalPath = ed.getPath();
1737    //    String goal = originalPath + "." + discriminator;
1738    //
1739    //    index++;
1740    //    while (index < snapshot.size() && !snapshot.get(index).getPath().equals(originalPath)) {
1741    //      if (snapshot.get(index).getPath().equals(goal))
1742    //        return snapshot.get(index);
1743    //      index++;
1744    //    }
1745    //    throw new Error("Unable to find discriminator definition for " + goal + " in " + discriminator + " at " + path+" in "+profile.getUrl());
1746  }
1747
1748  private boolean tailMatches(ElementDefinition t, String d) {
1749    String tail = tail(t.getPath());
1750    if (d.contains("["))
1751      return tail.startsWith(d.substring(0, d.indexOf('[')));
1752    else
1753      return tail.equals(d);
1754  }
1755
1756  private Element getExtensionByUrl(List<Element> extensions, String urlSimple) {
1757    for (Element e : extensions) {
1758      if (urlSimple.equals(e.getNamedChildValue("url")))
1759        return e;
1760    }
1761    return null;
1762  }
1763
1764  public List<String> getExtensionDomains() {
1765    return extensionDomains;
1766  }
1767
1768  private Element getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path) {
1769    String targetUrl = null;
1770    String version = "";
1771    if (ref.startsWith("http") || ref.startsWith("urn")) {
1772      // We've got an absolute reference, no need to calculate
1773      if (ref.contains("/_history/")) {
1774        targetUrl = ref.substring(0, ref.indexOf("/_history/") - 1);
1775        version = ref.substring(ref.indexOf("/_history/") + 10);
1776      }  else
1777        targetUrl = ref;
1778
1779    } else if (fullUrl == null) {
1780      //This isn't a problem for signatures - if it's a signature, we won't have a resolution for a relative reference.  For anything else, this is an error
1781      rule(errors, IssueType.REQUIRED, -1, -1, path, path.startsWith("Bundle.signature"), "Relative Reference appears inside Bundle whose entry is missing a fullUrl");
1782      return null;
1783
1784    } else if (ref.split("/").length!=2) {
1785      rule(errors, IssueType.INVALID, -1, -1, path, false, "Relative URLs must be of the format [ResourceName]/[id].  Encountered " + ref);
1786      return null;
1787
1788    } else {
1789      String base = "";
1790      if (fullUrl.startsWith("urn")) {
1791        String[] parts = fullUrl.split("\\:");
1792        for (int i=0; i < parts.length-1; i++) {
1793          base = base + parts[i] + ":";
1794        }
1795      } else {
1796        String[] parts;
1797        parts = fullUrl.split("/");
1798        for (int i=0; i < parts.length-2; i++) {
1799          base = base + parts[i] + "/";
1800        }
1801      }
1802
1803      String id = null;
1804      if (ref.contains("/_history/")) {
1805        version = ref.substring(ref.indexOf("/_history/") + 10);
1806        id = ref.substring(0, ref.indexOf("/history/")-1);
1807      } else if (base.startsWith("urn"))
1808        id = ref.split("/")[1];
1809      else
1810        id = ref;
1811
1812      targetUrl = base + id;
1813    }
1814
1815    List<Element> entries = new ArrayList<Element>();
1816    bundle.getNamedChildren("entry", entries);
1817    Element match = null;
1818    for (Element we : entries) {
1819      if (we.getChildValue("fullUrl").equals(targetUrl)) {
1820        Element r = we.getNamedChild("resource");
1821        if (version.isEmpty()) {
1822          rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref);
1823          match = r;
1824        } else {
1825          try {
1826            if (r.getChildren("meta").get(0).getChildValue("versionId").equals(version)) {
1827              rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref);
1828              match = r;
1829            }
1830          } catch (Exception e) {
1831            warning(errors, IssueType.REQUIRED, -1, -1, path, r.getChildren("meta").size()==1 && r.getChildren("meta").get(0).getChildValue("versionId")!=null, "Entries matching fullURL " + targetUrl + " should declare meta/versionId because there are version-specific references");
1832            // If one of these things is null
1833          }
1834        }
1835      }
1836    }
1837
1838    warning(errors, IssueType.REQUIRED, -1, -1, path, match!=null || !targetUrl.startsWith("urn"), "URN reference is not locally contained within the bundle " + ref);
1839    return match;
1840  }  
1841
1842  private StructureDefinition getProfileForType(String type) {
1843    if (logical != null)
1844      for (BundleEntryComponent be : logical.getEntry()) {
1845        if (be.hasResource() && be.getResource() instanceof StructureDefinition) {
1846          StructureDefinition sd = (StructureDefinition) be.getResource();
1847          if (sd.getId().equals(type))
1848            return sd;
1849        }
1850      }
1851
1852    long t = System.nanoTime();
1853    try {
1854      return context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type);
1855    } finally {
1856      sdTime = sdTime + (System.nanoTime() - t);
1857    }
1858  }
1859
1860  private Element getValueForDiscriminator(Object appContext, List<ValidationMessage> errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException  {
1861    String p = stack.getLiteralPath()+"."+element.getName();
1862    Element focus = element;
1863    String[] dlist = discriminator.split("\\.");
1864    for (String d : dlist) {
1865      if (focus.fhirType().equals("Reference") && d.equals("reference")) {
1866        String url = focus.getChildValue("reference");
1867        if (Utilities.noString(url))
1868          throw new FHIRException("No reference resolving discriminator "+discriminator+" from "+element.getProperty().getName());
1869        // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough? 
1870        Element target = resolve(appContext, url, stack, errors, p);
1871        if (target == null)
1872          throw new FHIRException("Unable to find resource "+url+" at "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName());
1873        focus = target;
1874      } else if (d.equals("value") && focus.isPrimitive()) {
1875        return focus;
1876      } else {
1877        List<Element> children = focus.getChildren(d);
1878        if (children.isEmpty())
1879          throw new FHIRException("Unable to find "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName());
1880        if (children.size() > 1)
1881          throw new FHIRException("Found "+Integer.toString(children.size())+" items for "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName());
1882        focus = children.get(0);
1883        p = p + "."+d;
1884      }
1885    }
1886    return focus;
1887  }
1888
1889  private CodeSystem getCodeSystem(String system) {
1890    long t = System.nanoTime();
1891    try {
1892      return context.fetchCodeSystem(system);
1893    } finally {
1894      txTime = txTime + (System.nanoTime() - t);
1895    }
1896  }
1897
1898  private boolean hasTime(String fmt) {
1899    return fmt.contains("T");
1900  }
1901
1902  private boolean hasTimeZone(String fmt) {
1903    return fmt.length() > 10 && (fmt.substring(10).contains("-") || fmt.substring(10).contains("+") || fmt.substring(10).contains("Z"));
1904  }
1905
1906  private boolean isAbsolute(String uri) {
1907    return Utilities.noString(uri) || uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("urn:uuid:") || uri.startsWith("urn:oid:") || uri.startsWith("urn:ietf:")
1908        || uri.startsWith("urn:iso:") || uri.startsWith("urn:iso-astm:") || isValidFHIRUrn(uri);
1909  }
1910
1911  private boolean isValidFHIRUrn(String uri) {
1912    return (uri.equals("urn:x-fhir:uk:id:nhs-number")) || uri.startsWith("urn:"); // Anyone can invent a URN, so why should we complain?
1913  }
1914
1915  public boolean isAnyExtensionsAllowed() {
1916    return anyExtensionsAllowed;
1917  }
1918
1919  public boolean isErrorForUnknownProfiles() {
1920    return errorForUnknownProfiles;
1921  }
1922
1923  public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
1924    this.errorForUnknownProfiles = errorForUnknownProfiles;
1925  }
1926
1927  private boolean isParametersEntry(String path) {
1928    String[] parts = path.split("\\.");
1929    return parts.length > 2 && parts[parts.length - 1].equals("resource") && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part"));
1930  }
1931
1932  private boolean isBundleEntry(String path) {
1933    String[] parts = path.split("\\.");
1934    return parts.length > 2 && parts[parts.length - 1].equals("resource") && pathEntryHasName(parts[parts.length - 2], "entry");
1935  }
1936
1937  private boolean isBundleOutcome(String path) {
1938    String[] parts = path.split("\\.");
1939    return parts.length > 2 && parts[parts.length - 1].equals("outcome") && pathEntryHasName(parts[parts.length - 2], "response");
1940  }
1941
1942
1943  private static boolean pathEntryHasName(String thePathEntry, String theName) {
1944    if (thePathEntry.equals(theName)) {
1945      return true;
1946    }
1947    if (thePathEntry.length() >= theName.length() + 3) {
1948      if (thePathEntry.startsWith(theName)) {
1949        if (thePathEntry.charAt(theName.length()) == '[') {
1950          return true;
1951        }
1952      }
1953    }
1954    return false;
1955  }
1956
1957  private boolean isPrimitiveType(String code) {
1958    StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+code);
1959    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
1960  }
1961
1962
1963
1964  public boolean isSuppressLoincSnomedMessages() {
1965    return suppressLoincSnomedMessages;
1966  }
1967
1968  private boolean nameMatches(String name, String tail) {
1969    if (tail.endsWith("[x]"))
1970      return name.startsWith(tail.substring(0, tail.length() - 3));
1971    else
1972      return (name.equals(tail));
1973  }
1974
1975  // private String mergePath(String path1, String path2) {
1976  // // path1 is xpath path
1977  // // path2 is dotted path
1978  // String[] parts = path2.split("\\.");
1979  // StringBuilder b = new StringBuilder(path1);
1980  // for (int i = 1; i < parts.length -1; i++)
1981  // b.append("/f:"+parts[i]);
1982  // return b.toString();
1983  // }
1984
1985  private boolean passesCodeWhitespaceRules(String v) {
1986    if (!v.trim().equals(v))
1987      return false;
1988    boolean lastWasSpace = true;
1989    for (char c : v.toCharArray()) {
1990      if (c == ' ') {
1991        if (lastWasSpace)
1992          return false;
1993        else
1994          lastWasSpace = true;
1995      } else if (Character.isWhitespace(c))
1996        return false;
1997      else
1998        lastWasSpace = false;
1999    }
2000    return true;
2001  }
2002
2003  private Element localResolve(String ref, NodeStack stack, List<ValidationMessage> errors, String path) {
2004    if (ref.startsWith("#")) {
2005      // work back through the contained list.
2006      // really, there should only be one level for this (contained resources cannot contain
2007      // contained resources), but we'll leave that to some other code to worry about
2008      while (stack != null && stack.getElement() != null) {
2009        if (stack.getElement().getProperty().isResource()) {
2010          // ok, we'll try to find the contained reference
2011          Element res = getContainedById(stack.getElement(), ref.substring(1));
2012          if (res != null)
2013            return res;
2014        }
2015        if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) {
2016          return null; // we don't try to resolve contained references across this boundary
2017        }
2018        stack = stack.parent;
2019      }
2020      return null;
2021    } else {
2022      // work back through the contained list - if any of them are bundles, try to resolve
2023      // the resource in the bundle
2024      String fullUrl = null; // we're going to try to work this out as we go up
2025      while (stack != null && stack.getElement() != null) {
2026        if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl==null && stack.parent.getElement().getName().equals("entry")) {
2027          fullUrl = stack.parent.getElement().getChildValue("fullUrl"); // we don't try to resolve contained references across this boundary
2028          if (fullUrl==null)
2029            rule(errors, IssueType.REQUIRED, stack.parent.getElement().line(), stack.parent.getElement().col(), stack.parent.getLiteralPath(), fullUrl!=null, "Bundle entry missing fullUrl");
2030        }
2031        if ("Bundle".equals(stack.getElement().getType())) {
2032          Element res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path);
2033          return res;
2034        }
2035        stack = stack.parent;
2036      }
2037    }
2038    return null;
2039  }
2040
2041  private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException {
2042    Element local = localResolve(ref, stack, errors, path);
2043    if (local!=null)
2044      return local;
2045    if (fetcher == null)
2046      return null;
2047    return fetcher.fetch(appContext, ref);
2048  }
2049
2050  private ValueSet resolveBindingReference(DomainResource ctxt, Type reference, String uri) {
2051    if (reference instanceof UriType) {
2052      long t = System.nanoTime();
2053      ValueSet fr = context.fetchResource(ValueSet.class, ((UriType) reference).getValue().toString());
2054      txTime = txTime + (System.nanoTime() - t);
2055      return fr;
2056    }
2057    else if (reference instanceof Reference) {
2058      String s = ((Reference) reference).getReference();
2059      if (s.startsWith("#")) {
2060        for (Resource c : ctxt.getContained()) {
2061          if (c.getId().equals(s.substring(1)) && (c instanceof ValueSet))
2062            return (ValueSet) c;
2063        }
2064        return null;
2065      } else {
2066        long t = System.nanoTime();
2067        String ref = ((Reference) reference).getReference();
2068        if (!Utilities.isAbsoluteUrl(ref))
2069          ref = resolve(uri, ref);
2070        ValueSet fr = context.fetchResource(ValueSet.class, ref);
2071        txTime = txTime + (System.nanoTime() - t);
2072        return fr;
2073      }
2074    }
2075    else
2076      return null;
2077  }
2078
2079  private String resolve(String uri, String ref) {
2080    String[] up = uri.split("\\/");
2081    String[] rp = ref.split("\\/");
2082    if (context.getResourceNames().contains(up[up.length-2]) && context.getResourceNames().contains(rp[0])) {
2083      StringBuilder b = new StringBuilder();
2084      for (int i = 0; i < up.length-2; i++) {
2085        b.append(up[i]);
2086        b.append("/");
2087      }
2088      b.append(ref);
2089      return b.toString();
2090    } else
2091      return ref;
2092  }
2093
2094  private Element resolveInBundle(List<Element> entries, String ref, String fullUrl, String type, String id) {
2095    if (Utilities.isAbsoluteUrl(ref)) {
2096      // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. 
2097      for (Element entry : entries) {
2098        String fu = entry.getNamedChildValue("fullUrl");
2099        if (ref.equals(fu))
2100          return entry;
2101      }
2102      return null;
2103    } else {
2104      // split into base, type, and id
2105      String u = null;
2106      if (fullUrl != null && fullUrl.endsWith(type+"/"+id))
2107        // fullUrl = complex
2108        u = fullUrl.substring((type+"/"+id).length())+ref;
2109      String[] parts = ref.split("\\/");
2110      if (parts.length >= 2) {
2111        String t = parts[0];
2112        String i = parts[1];
2113        for (Element entry : entries) {
2114          String fu = entry.getNamedChildValue("fullUrl");
2115          if (u != null && fullUrl.equals(u))
2116            return entry;
2117          if (u == null) {
2118            Element resource = entry.getNamedChild("resource");
2119            String et = resource.getType();
2120            String eid = resource.getNamedChildValue("id");
2121            if (t.equals(et) && i.equals(eid))
2122              return entry;
2123          }
2124        }
2125      }
2126      return null;
2127    }
2128  }
2129
2130  private ElementDefinition resolveNameReference(StructureDefinitionSnapshotComponent snapshot, String contentReference) {
2131    for (ElementDefinition ed : snapshot.getElement())
2132      if (contentReference.equals("#"+ed.getId()))
2133        return ed;
2134    return null;
2135  }
2136
2137  private StructureDefinition resolveProfile(StructureDefinition profile, String pr)  {
2138    if (pr.startsWith("#")) {
2139      for (Resource r : profile.getContained()) {
2140        if (r.getId().equals(pr.substring(1)) && r instanceof StructureDefinition)
2141          return (StructureDefinition) r;
2142      }
2143      return null;
2144    } else {
2145      long t = System.nanoTime();
2146      StructureDefinition fr = context.fetchResource(StructureDefinition.class, pr);
2147      sdTime = sdTime + (System.nanoTime() - t);
2148      return fr;
2149    }
2150  }
2151
2152  private ElementDefinition resolveType(String type)  {
2153    if (logical != null)
2154      for (BundleEntryComponent be : logical.getEntry()) {
2155        if (be.hasResource() && be.getResource() instanceof StructureDefinition) {
2156          StructureDefinition sd = (StructureDefinition) be.getResource();
2157          if (sd.getId().equals(type))
2158            return sd.getSnapshot().getElement().get(0);
2159        }
2160      }
2161    String url = "http://hl7.org/fhir/StructureDefinition/" + type;
2162    long t = System.nanoTime();
2163    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
2164    sdTime = sdTime + (System.nanoTime() - t);
2165    if (sd == null || !sd.hasSnapshot())
2166      return null;
2167    else
2168      return sd.getSnapshot().getElement().get(0);
2169  }
2170
2171  public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) {
2172    this.anyExtensionsAllowed = anyExtensionsAllowed;
2173  }
2174
2175  public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
2176    bpWarnings = value;
2177    return this;
2178  }
2179
2180  @Override
2181  public void setCheckDisplay(CheckDisplayOption checkDisplay) {
2182    this.checkDisplay = checkDisplay;
2183  }
2184
2185  public void setSuppressLoincSnomedMessages(boolean suppressLoincSnomedMessages) {
2186    this.suppressLoincSnomedMessages = suppressLoincSnomedMessages;
2187  }
2188
2189  public IdStatus getResourceIdRule() {
2190    return resourceIdRule;
2191  }
2192
2193  public void setResourceIdRule(IdStatus resourceIdRule) {
2194    this.resourceIdRule = resourceIdRule;
2195  }
2196
2197
2198  public boolean isAllowXsiLocation() {
2199    return allowXsiLocation;
2200  }
2201
2202  public void setAllowXsiLocation(boolean allowXsiLocation) {
2203    this.allowXsiLocation = allowXsiLocation;
2204  }
2205
2206  /**
2207   * 
2208   * @param element
2209   *          - the candidate that might be in the slice
2210   * @param path
2211   *          - for reporting any errors. the XPath for the element
2212   * @param slice
2213   *          - the definition of how slicing is determined
2214   * @param ed
2215   *          - the slice for which to test membership
2216   * @param errors 
2217   * @param stack 
2218   * @return
2219   * @throws DefinitionException 
2220   * @throws DefinitionException 
2221   * @throws IOException 
2222   * @throws FHIRException
2223   */
2224  private boolean sliceMatches(Object appContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, NodeStack stack) throws DefinitionException, FHIRException, IOException {
2225    if (!slicer.getSlicing().hasDiscriminator())
2226      return false; // cannot validate in this case
2227
2228    ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache");
2229    if (n == null) {
2230      long t = System.nanoTime();
2231      // GG: this approach is flawed because it treats discriminators individually rather than collectively 
2232      String expression = "true";
2233      for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) {
2234        String discriminator = s.getPath();
2235        if (s.getType() == DiscriminatorType.PROFILE)
2236          throw new FHIRException("Validating against slices with discriminators based on profiles is not yet supported by the FHIRPath engine: " + discriminator);
2237        // Todo: Fix this once FHIRPath (and this engine) supports a boolean function that test profile conformance
2238        
2239        ElementDefinition criteriaElement = getCriteriaForDiscriminator(path, ed, discriminator, profile);
2240        if (s.getType() == DiscriminatorType.TYPE) {
2241          String type = null;
2242          if (!criteriaElement.getPath().contains("[") && discriminator.contains("[")) {
2243            discriminator = discriminator.substring(0, discriminator.indexOf('['));
2244            String lastNode = tail(discriminator);
2245            type = tail(criteriaElement.getPath()).substring(lastNode.length());
2246            type = type.substring(0,1).toLowerCase() + type.substring(1);
2247          } else if (!criteriaElement.hasType() || criteriaElement.getType().size()==1) {
2248            if (discriminator.contains("["))
2249              discriminator = discriminator.substring(0, discriminator.indexOf('['));
2250            type = criteriaElement.getType().get(0).getCode();
2251          }
2252          if (type==null)
2253            throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + ed.getId() + " does not declare a type");
2254          if (discriminator.isEmpty())
2255            expression = expression + " and this is " + type;
2256          else
2257            expression = expression + " and " + discriminator + " is " + type;
2258        } else if (criteriaElement.hasFixed()) {
2259          expression = expression + " and " + discriminator + " = ";
2260          Type fixed = criteriaElement.getFixed();
2261          if (fixed instanceof StringType) {
2262            Gson gson = new Gson();
2263            String json = gson.toJson((StringType)fixed);
2264            String escapedString = json.substring(json.indexOf(":")+2);
2265            escapedString = escapedString.substring(0, escapedString.indexOf(",\"myStringValue")-1);
2266            expression = expression + "'" + escapedString + "'";
2267          } else if (fixed instanceof UriType) {
2268              expression = expression + "'" + ((UriType)fixed).asStringValue() + "'";
2269          } else if (fixed instanceof IntegerType) {
2270            expression = expression + ((IntegerType)fixed).asStringValue();
2271          } else if (fixed instanceof DecimalType) {
2272            expression = expression + ((IntegerType)fixed).asStringValue();
2273          } else if (fixed instanceof BooleanType) {
2274            expression = expression + ((BooleanType)fixed).asStringValue();
2275          } else
2276            throw new DefinitionException("Unsupported fixed value type for discriminator(" + discriminator + ") for slice " + ed.getId() + ": " + fixed.getClass().getName());
2277        } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().getValueSetReference()!=null) {
2278          expression = expression + " and " + discriminator + " in '" + criteriaElement.getBinding().getValueSetReference().getReference() + "'";
2279        } else if (criteriaElement.hasMin() && criteriaElement.getMin()>0) {
2280          expression = expression + " and " + discriminator + ".exists()";
2281        } else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) {
2282          expression = expression + " and " + discriminator + ".exists().not()";
2283        } else {
2284          throw new DefinitionException("Could not match discriminator (" + discriminator + ") for slice " + ed.getId() + " - does not have fixed value, binding or existence assertions");
2285        }
2286      }
2287
2288      try {
2289        n = fpe.parse(expression);
2290      } catch (FHIRLexerException e) {
2291        throw new FHIRException("Problem processing expression "+expression +" in profile " + profile.getUrl() + " path " + path + ": " + e.getMessage());
2292      }
2293      fpeTime = fpeTime + (System.nanoTime() - t);
2294      ed.setUserData("slice.expression.cache", n);
2295    }
2296
2297    String msg;
2298    boolean ok;
2299    try {
2300      long t = System.nanoTime();
2301      ok = fpe.evaluateToBoolean(null, element, n);
2302      fpeTime = fpeTime + (System.nanoTime() - t);
2303      msg = fpe.forLog();
2304    } catch (Exception ex) {
2305      throw new FHIRException("Problem evaluating slicing expression for element in profile " + profile.getUrl() + " path " + path + ": " + ex.getMessage());
2306    }
2307    return ok;
2308  }
2309
2310  // we assume that the following things are true:
2311  // the instance at root is valid against the schema and schematron
2312  // the instance validator had no issues against the base resource profile
2313  private void start(Object appContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, NodeStack stack) throws FHIRException, FHIRException, IOException {
2314    // profile is valid, and matches the resource name
2315    ResourceProfiles resourceProfiles = getResourceProfiles(element, stack);
2316    if (!resourceProfiles.isProcessed())
2317      checkDeclaredProfiles(resourceProfiles, errors, resource, element, stack);
2318    
2319    if (!resourceProfiles.isProcessed()) {
2320      resourceProfiles.setProcessed();
2321      if (!resourceProfiles.hasProfiles() &&
2322          (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), defn.hasSnapshot(),
2323              "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided"))) {
2324        // Don't need to validate against the resource if there's a profile because the profile snapshot will include the relevant parts of the resources
2325        validateElement(appContext, errors, defn, defn.getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false);
2326      }
2327
2328      // specific known special validations
2329      if (element.getType().equals("Bundle"))
2330        validateBundle(errors, element, stack);
2331      if (element.getType().equals("Observation"))
2332        validateObservation(errors, element, stack);
2333      if (element.getType().equals("QuestionnaireResponse"))
2334        validateQuestionannaireResponse(errors, element, stack);
2335    }
2336    for (ProfileUsage profileUsage : resourceProfiles.uncheckedProfiles()) {
2337      profileUsage.setChecked();
2338      validateElement(appContext, errors, profileUsage.getProfile(), profileUsage.getProfile().getSnapshot().getElement().get(0), null, null, resource, element, element.getName(), stack, false);
2339    }
2340  }
2341
2342  private void validateQuestionannaireResponse(List<ValidationMessage> errors, Element element, NodeStack stack) {
2343    Element q = element.getNamedChild("questionnaire");
2344    if (hint(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), q != null && isNotBlank(q.getNamedChildValue("reference")), "No questionnaire is identified, so no validation can be performed against the base questionnaire")) {
2345      long t = System.nanoTime();
2346      Questionnaire qsrc = context.fetchResource(Questionnaire.class, q.getNamedChildValue("reference"));
2347      sdTime = sdTime + (System.nanoTime() - t);
2348      if (warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, "The questionnaire could not be resolved, so no validation can be performed against the base questionnaire")) {
2349        boolean inProgress = "in-progress".equals(element.getNamedChildValue("status"));
2350        validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack, inProgress);        
2351      }
2352    }
2353  }
2354
2355  private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) {
2356    String text = element.getNamedChildValue("text");
2357    rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), "If text exists, it must match the questionnaire definition for linkId "+qItem.getLinkId());
2358
2359    List<Element> answers = new ArrayList<Element>();
2360    element.getNamedChildren("answer", answers);
2361    if (inProgress)
2362      warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
2363    else
2364      rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
2365    if (answers.size() > 1)
2366      rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response answer item with this linkId allowed");
2367
2368    for (Element answer : answers) {
2369      NodeStack ns = stack.push(answer, -1, null, null);
2370      switch (qItem.getType()) {
2371      case GROUP: 
2372        rule(errors, IssueType.STRUCTURE, answer.line(), answer.col(), stack.getLiteralPath(), false, "Items of type group should not have answers"); 
2373        break;
2374      case DISPLAY:  // nothing
2375        break;
2376      case BOOLEAN:       
2377        validateQuestionnaireResponseItemType(errors, answer, ns, "boolean");
2378        break;
2379      case DECIMAL:       
2380        validateQuestionnaireResponseItemType(errors, answer, ns, "decimal");
2381        break;
2382      case INTEGER:       
2383        validateQuestionnaireResponseItemType(errors, answer, ns, "integer");
2384        break;
2385      case DATE:          
2386        validateQuestionnaireResponseItemType(errors, answer, ns, "date");
2387        break;
2388      case DATETIME:      
2389        validateQuestionnaireResponseItemType(errors, answer, ns, "dateTime");
2390        break;
2391      case TIME:          
2392        validateQuestionnaireResponseItemType(errors, answer, ns, "time");
2393        break;
2394      case STRING:        
2395        validateQuestionnaireResponseItemType(errors, answer, ns, "string");
2396        break;
2397      case TEXT:          
2398        validateQuestionnaireResponseItemType(errors, answer, ns, "text");
2399        break;
2400      case URL:           
2401        validateQuestionnaireResponseItemType(errors, answer, ns, "uri");
2402        break;
2403      case ATTACHMENT:    
2404        validateQuestionnaireResponseItemType(errors, answer, ns, "Attachment");
2405        break;
2406      case REFERENCE:     
2407        validateQuestionnaireResponseItemType(errors, answer, ns, "Reference");
2408        break;
2409      case QUANTITY:   
2410        if (validateQuestionnaireResponseItemType(errors, answer, ns, "Quantity").equals("Quantity"))
2411          if (qItem.hasExtension("???"))
2412            validateQuestionnaireResponseItemQuantity(errors, answer, ns);
2413        break;
2414      case CHOICE:     
2415        String itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string");
2416        if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, false);
2417        else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date");
2418        else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time");
2419        else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer");
2420        else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string");
2421        break;
2422      case OPENCHOICE: 
2423        itemType=validateQuestionnaireResponseItemType(errors, answer, ns, "Coding", "date", "time", "integer", "string");
2424        if (itemType.equals("Coding")) validateAnswerCode(errors, answer, ns, qsrc, qItem, true);
2425        else if (itemType.equals("date")) checkOption(errors, answer, ns, qsrc, qItem, "date");
2426        else if (itemType.equals("time")) checkOption(errors, answer, ns, qsrc, qItem, "time");
2427        else if (itemType.equals("integer")) checkOption(errors, answer, ns, qsrc, qItem, "integer");
2428        else if (itemType.equals("string")) checkOption(errors, answer, ns, qsrc, qItem, "string", true);
2429        break;
2430      }
2431      validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack, inProgress);
2432    }
2433    if (qItem.getType() == null) {
2434      fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, "Definition for item "+qItem.getLinkId() + " does not contain a type");
2435    } else if (qItem.getType() == QuestionnaireItemType.GROUP) {
2436      validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack, inProgress);
2437    } else {
2438      List<Element> items = new ArrayList<Element>();
2439      element.getNamedChildren("item", items);
2440      for (Element item : items) {
2441        NodeStack ns = stack.push(item, -1, null, null);
2442        rule(errors, IssueType.STRUCTURE, answers.get(0).line(), answers.get(0).col(), stack.getLiteralPath(), false, "Items not of type group should not have items - Item with linkId {0} of type {1} has {2} item(s)", qItem.getLinkId(), qItem.getType(), items.size());
2443      }
2444    }
2445  }
2446
2447  private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List<ValidationMessage> errors, List<Element> elements, NodeStack stack, boolean inProgress) {
2448    if (elements.size() > 1)
2449      rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response item with this linkId allowed");
2450    for (Element element : elements) {
2451      NodeStack ns = stack.push(element, -1, null, null);
2452      validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress);
2453    }
2454  }
2455
2456  private int getLinkIdIndex(List<QuestionnaireItemComponent> qItems, String linkId) {
2457    for (int i = 0; i < qItems.size(); i++) {
2458      if (linkId.equals(qItems.get(i).getLinkId()))
2459        return i;
2460    }
2461    return -1;
2462  }
2463
2464  private void validateQuestionannaireResponseItems(Questionnaire qsrc, List<QuestionnaireItemComponent> qItems, List<ValidationMessage> errors, Element element, NodeStack stack, boolean inProgress) {
2465    List<Element> items = new ArrayList<Element>();
2466    element.getNamedChildren("item", items);
2467    // now, sort into stacks
2468    Map<String, List<Element>> map = new HashMap<String, List<Element>>();
2469    int lastIndex = -1;
2470    for (Element item : items) {
2471      String linkId = item.getNamedChildValue("linkId");
2472      if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), "No LinkId, so can't be validated")) {
2473        int index = getLinkIdIndex(qItems, linkId);
2474        if (index == -1) {
2475          QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId);
2476          if (qItem != null) {
2477            rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, "Structural Error: item is in the wrong place");
2478            NodeStack ns = stack.push(item, -1, null, null);
2479            validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress);
2480          }
2481          else
2482            rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, "LinkId \""+linkId+"\" not found in questionnaire");
2483        }
2484        else
2485        {
2486          rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, "Structural Error: items are out of order");
2487          lastIndex = index;
2488          List<Element> mapItem = map.get(linkId);
2489          if (mapItem == null) {
2490            mapItem = new ArrayList<Element>();
2491            map.put(linkId, mapItem);
2492          }
2493          mapItem.add(item);
2494        }
2495      }
2496    }
2497
2498    // ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order
2499    for (QuestionnaireItemComponent qItem : qItems) {
2500      List<Element> mapItem = map.get(qItem.getLinkId());
2501      if (mapItem != null)
2502        validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress);
2503      else
2504        rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
2505    }
2506  }
2507
2508  private void validateQuestionnaireResponseItemQuantity( List<ValidationMessage> errors, Element answer, NodeStack stack)      {
2509
2510  }
2511
2512  private String validateQuestionnaireResponseItemType(List<ValidationMessage> errors, Element element, NodeStack stack, String... types) {
2513    List<Element> values = new ArrayList<Element>();
2514    element.getNamedChildrenWithWildcard("value[x]", values);
2515    if (values.size() > 0) {
2516      NodeStack ns = stack.push(values.get(0), -1, null, null);
2517      CommaSeparatedStringBuilder l = new CommaSeparatedStringBuilder();
2518      for (String s : types)  {
2519        l.append(s);
2520        if (values.get(0).getName().equals("value"+Utilities.capitalize(s))) 
2521          return(s);
2522      }
2523      if (types.length == 1)
2524        rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be of type "+types[0]);
2525      else
2526        rule(errors, IssueType.STRUCTURE, values.get(0).line(), values.get(0).col(), ns.getLiteralPath(), false, "Answer value must be one of the types "+l.toString());
2527    }
2528    return null;
2529  }
2530
2531  private QuestionnaireItemComponent findQuestionnaireItem(Questionnaire qSrc, String linkId) {
2532    return findItem(qSrc.getItem(), linkId);
2533  }
2534
2535  private QuestionnaireItemComponent findItem(List<QuestionnaireItemComponent> list, String linkId) {
2536    for (QuestionnaireItemComponent item : list) {
2537      if (linkId.equals(item.getLinkId()))
2538        return item;
2539      QuestionnaireItemComponent result = findItem(item.getItem(), linkId);
2540      if (result != null)
2541        return result;
2542    }
2543    return null;
2544  }
2545
2546  /*    private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, List<Coding> optionList) {
2547          String system = value.getNamedChildValue("system");
2548          String code = value.getNamedChildValue("code");
2549          boolean found = false;
2550          for (Coding c : optionList) {
2551      if (ObjectUtil.equals(c.getSystem(), system) && ObjectUtil.equals(c.getCode(), code)) {
2552              found = true;
2553              break;
2554            }
2555          }
2556          rule(errors, IssueType.STRUCTURE, value.line(), value.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option");
2557        }*/
2558
2559  private void validateAnswerCode(List<ValidationMessage> errors, Element value, NodeStack stack, Questionnaire qSrc, Reference ref, boolean theOpenChoice) {
2560    ValueSet vs = resolveBindingReference(qSrc, ref, qSrc.getUrl());
2561    if (warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), vs != null, "ValueSet " + describeReference(ref) + " not found"))  {
2562      try {
2563        Coding c = readAsCoding(value);
2564        if (isBlank(c.getCode()) && isBlank(c.getSystem()) && isNotBlank(c.getDisplay())) {
2565          if (theOpenChoice) {
2566            return;
2567          }
2568        }
2569
2570        long t = System.nanoTime();
2571        ValidationResult res = context.validateCode(c, vs);
2572        txTime = txTime + (System.nanoTime() - t);
2573        if (!res.isOk())
2574          rule(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "The value provided ("+c.getSystem()+"::"+c.getCode()+") is not in the options value set in the questionnaire");
2575      } catch (Exception e) {
2576        warning(errors, IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, "Error " + e.getMessage() + " validating Coding against Questionnaire Options");
2577      }
2578    }
2579  }
2580
2581  private void validateAnswerCode( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean theOpenChoice) {
2582    Element v = answer.getNamedChild("valueCoding");
2583    NodeStack ns = stack.push(v, -1, null, null);
2584    if (qItem.getOption().size() > 0)
2585      checkCodingOption(errors, answer, stack, qSrc, qItem, theOpenChoice);
2586    //      validateAnswerCode(errors, v, stack, qItem.getOption());
2587    else if (qItem.hasOptions())
2588      validateAnswerCode(errors, v, stack, qSrc, qItem.getOptions(), theOpenChoice);
2589    else
2590      hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate options because no option or options are provided");
2591  }
2592
2593  private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type) {
2594    checkOption(errors, answer, stack, qSrc,  qItem, type, false);
2595  }
2596
2597  private void checkOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, String type, boolean openChoice) {
2598    if (type.equals("integer"))     checkIntegerOption(errors, answer, stack, qSrc, qItem, openChoice);
2599    else if (type.equals("date"))   checkDateOption(errors, answer, stack, qSrc, qItem, openChoice);
2600    else if (type.equals("time"))   checkTimeOption(errors, answer, stack, qSrc, qItem, openChoice);
2601    else if (type.equals("string")) checkStringOption(errors, answer, stack, qSrc, qItem, openChoice);
2602    else if (type.equals("Coding")) checkCodingOption(errors, answer, stack, qSrc, qItem, openChoice);
2603  }
2604
2605  private void checkIntegerOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
2606    Element v = answer.getNamedChild("valueInteger");
2607    NodeStack ns = stack.push(v, -1, null, null);
2608    if (qItem.getOption().size() > 0) {
2609      List<IntegerType> list = new ArrayList<IntegerType>();
2610      for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
2611        try {
2612          list.add(components.getValueIntegerType());
2613        } catch (FHIRException e) {
2614          // If it's the wrong type, just keep going
2615        }
2616      }
2617      if (list.isEmpty() && !openChoice) {
2618        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type integer");
2619      } else {
2620        boolean found = false;
2621        for (IntegerType item : list) {
2622          if (item.getValue() == Integer.parseInt(v.primitiveValue())) {
2623            found = true;
2624            break;
2625          }
2626        }
2627        if (!found) {
2628          rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The integer "+v.primitiveValue()+" is not a valid option");
2629        }
2630      }
2631    } else
2632      hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate integer answer option because no option list is provided");
2633  }
2634
2635  private void checkDateOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
2636    Element v = answer.getNamedChild("valueDate");
2637    NodeStack ns = stack.push(v, -1, null, null);
2638    if (qItem.getOption().size() > 0) {
2639      List<DateType> list = new ArrayList<DateType>();
2640      for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
2641        try {
2642          list.add(components.getValueDateType());
2643        } catch (FHIRException e) {
2644          // If it's the wrong type, just keep going
2645        }
2646      }
2647      if (list.isEmpty() && !openChoice) {
2648        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type date");
2649      } else {
2650        boolean found = false;
2651        for (DateType item : list) {
2652          if (item.getValue().equals(v.primitiveValue())) {
2653            found = true;
2654            break;
2655          }
2656        }
2657        if (!found) {
2658          rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The date "+v.primitiveValue()+" is not a valid option");
2659        }
2660      }
2661    } else
2662      hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate date answer option because no option list is provided");
2663  }
2664
2665  private void checkTimeOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
2666    Element v = answer.getNamedChild("valueTime");
2667    NodeStack ns = stack.push(v, -1, null, null);
2668    if (qItem.getOption().size() > 0) {
2669      List<TimeType> list = new ArrayList<TimeType>();
2670      for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
2671        try {
2672          list.add(components.getValueTimeType());
2673        } catch (FHIRException e) {
2674          // If it's the wrong type, just keep going
2675        }
2676      }
2677      if (list.isEmpty() && !openChoice) {
2678        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type time");
2679      } else {
2680        boolean found = false;
2681        for (TimeType item : list) {
2682          if (item.getValue().equals(v.primitiveValue())) {
2683            found = true;
2684            break;
2685          }
2686        }
2687        if (!found) {
2688          rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The time "+v.primitiveValue()+" is not a valid option");
2689        }
2690      }
2691    } else
2692      hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate time answer option because no option list is provided");
2693  }
2694
2695  private void checkStringOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
2696    Element v = answer.getNamedChild("valueString");
2697    NodeStack ns = stack.push(v, -1, null, null);
2698    if (qItem.getOption().size() > 0) {
2699      List<StringType> list = new ArrayList<StringType>();
2700      for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
2701        try {
2702          if (components.getValue() != null) {
2703            list.add(components.getValueStringType());
2704          }
2705        } catch (FHIRException e) {
2706          // If it's the wrong type, just keep going
2707        }
2708      }
2709      if (list.isEmpty() && !openChoice) {
2710        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type string");
2711      } else {
2712        boolean found = false;
2713        for (StringType item : list) {
2714          if (item.getValue().equals((v.primitiveValue()))) {
2715            found = true;
2716            break;
2717          }
2718        }
2719        if (!found) {
2720          rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The string "+v.primitiveValue()+" is not a valid option");
2721        }
2722      }
2723    } else {
2724      hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate string answer option because no option list is provided");
2725    }
2726  }
2727
2728  private void checkCodingOption( List<ValidationMessage> errors, Element answer, NodeStack stack, Questionnaire qSrc, QuestionnaireItemComponent qItem, boolean openChoice) {
2729    Element v = answer.getNamedChild("valueCoding");
2730    String system = v.getNamedChildValue("system");
2731    String code = v.getNamedChildValue("code");
2732    NodeStack ns = stack.push(v, -1, null, null);
2733    if (qItem.getOption().size() > 0) {
2734      List<Coding> list = new ArrayList<Coding>();
2735      for (QuestionnaireItemOptionComponent components : qItem.getOption())  {
2736        try {
2737          if (components.getValue() != null) {
2738            list.add(components.getValueCoding());
2739          }
2740        } catch (FHIRException e) {
2741          // If it's the wrong type, just keep going
2742        }
2743      }
2744      if (list.isEmpty() && !openChoice) {
2745        rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Option list has no option values of type coding");
2746      } else {
2747        boolean found = false;
2748        for (Coding item : list) {
2749          if (ObjectUtil.equals(item.getSystem(), system) && ObjectUtil.equals(item.getCode(), code)) {
2750            found = true;
2751            break;
2752          }
2753        }
2754        if (!found) {
2755          rule(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), found, "The code "+system+"::"+code+" is not a valid option");
2756        }
2757      }
2758    } else
2759      hint(errors, IssueType.STRUCTURE, v.line(), v.col(), stack.getLiteralPath(), false, "Cannot validate Coding option because no option list is provided");
2760  }
2761
2762  private String tail(String path) {
2763    return path.substring(path.lastIndexOf(".") + 1);
2764  }
2765
2766  private String tryParse(String ref)  {
2767    String[] parts = ref.split("\\/");
2768    switch (parts.length) {
2769    case 1:
2770      return null;
2771    case 2:
2772      return checkResourceType(parts[0]);
2773    default:
2774      if (parts[parts.length - 2].equals("_history"))
2775        return checkResourceType(parts[parts.length - 4]);
2776      else
2777        return checkResourceType(parts[parts.length - 2]);
2778    }
2779  }
2780
2781  private boolean typesAreAllReference(List<TypeRefComponent> theType) {
2782    for (TypeRefComponent typeRefComponent : theType) {
2783      if (typeRefComponent.getCode().equals("Reference") == false) {
2784        return false;
2785      }
2786    }
2787    return true;
2788  }
2789
2790  private void validateBundle(List<ValidationMessage> errors, Element bundle, NodeStack stack) {
2791    List<Element> entries = new ArrayList<Element>();
2792    bundle.getNamedChildren("entry", entries);
2793    String type = bundle.getNamedChildValue("type");
2794    if (entries.size() == 0) {
2795      rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals("document") || type.equals("message")), "Documents or Messages must contain at least one entry");
2796    } else {
2797      Element firstEntry = entries.get(0);
2798      NodeStack firstStack = stack.push(firstEntry, 0, null, null);
2799      String fullUrl = firstEntry.getNamedChildValue("fullUrl");
2800
2801      if (type.equals("document")) {
2802        Element resource = firstEntry.getNamedChild("resource");
2803        NodeStack localStack = firstStack.push(resource, -1, null, null);
2804        String id = resource.getNamedChildValue("id");
2805        if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) {
2806          validateDocument(errors, entries, resource, localStack.push(resource, -1, null, null), fullUrl, id);
2807        }
2808        checkAllInterlinked(errors, entries, stack, bundle);
2809      }
2810      if (type.equals("message")) {
2811        Element resource = firstEntry.getNamedChild("resource");
2812        NodeStack localStack = firstStack.push(resource, -1, null, null);
2813        String id = resource.getNamedChildValue("id");
2814        if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) {
2815          validateMessage(errors, entries, resource, localStack.push(resource, -1, null, null), fullUrl, id);
2816        }
2817        checkAllInterlinked(errors, entries, stack, bundle);
2818      }
2819    }
2820  }
2821
2822  private void checkAllInterlinked(List<ValidationMessage> errors, List<Element> entries, NodeStack stack, Element bundle) {
2823    List<Element> visitedResources = new ArrayList<Element>();
2824    HashMap<Element,Element> candidateEntries = new HashMap<Element,Element>();
2825    List<Element> candidateResources = new ArrayList<Element>();
2826    for (Element entry: entries) {
2827      candidateEntries.put(entry.getNamedChild("resource"), entry);
2828      candidateResources.add(entry.getNamedChild("resource"));
2829    }
2830    followResourceLinks(entries.get(0), visitedResources, candidateEntries, candidateResources, true, errors, stack);
2831    List<Element> unusedResources = new ArrayList<Element>();
2832    unusedResources.addAll(candidateResources);
2833    unusedResources.removeAll(visitedResources);
2834    int i = 0;
2835    for (Element entry : entries) {
2836      warning(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry", Integer.toString(i)), !unusedResources.contains(entry.getNamedChild("resource")), "Entry isn't reachable by traversing from first Bundle entry");
2837      i++;
2838    }
2839    // Todo - check if the remaining resources point *to* the elements in the referenced set.  Any that are still left over are errors
2840  }
2841
2842  private void followResourceLinks(Element entry, List<Element> visitedResources, HashMap<Element, Element> candidateEntries, List<Element> candidateResources, boolean referenced, List<ValidationMessage> errors, NodeStack stack) {
2843    Element resource = entry.getNamedChild("resource");
2844    if (visitedResources.contains(resource))
2845      return;
2846
2847    if (referenced)
2848      visitedResources.add(resource);
2849
2850    List<String> references = findReferences(resource);
2851    for (String reference: references) {
2852      Element r = getFromBundle(stack.getElement(), reference, entry.getChildValue("fullUrl"), errors, stack.addToLiteralPath("entry:" + candidateResources.indexOf(resource)));
2853      if (r!=null && !visitedResources.contains(r)) {
2854        followResourceLinks(candidateEntries.get(r), visitedResources, candidateEntries, candidateResources, referenced, errors, stack);
2855      }
2856    }
2857  }
2858
2859  private List<String> findReferences(Element start) {
2860    List<String> references = new ArrayList<String>();
2861    findReferences(start, references);
2862    return references;
2863  }
2864
2865  private void findReferences(Element start, List<String> references) {
2866    for (Element child : start.getChildren()) {
2867      if (child.getType().equals("Reference")) {
2868        String ref = child.getChildValue("reference");
2869        if (ref!=null && !ref.startsWith("#"))
2870          references.add(ref);
2871      }
2872      findReferences(child, references);
2873    }
2874  }
2875
2876  private void validateBundleReference(List<ValidationMessage> errors, List<Element> entries, Element ref, String name, NodeStack stack, String fullUrl, String type, String id) {
2877    String reference = null;
2878    try {
2879      reference = ref.getNamedChildValue("reference");
2880    } catch (Error e) {
2881      
2882    }
2883        
2884    if (ref != null && !Utilities.noString(reference)) {
2885      Element target = resolveInBundle(entries, reference, fullUrl, type, id);
2886      rule(errors, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target != null, "Unable to resolve the target of the reference in the bundle (" + name + ")");
2887    }
2888  }
2889
2890  private void validateContains(Object appContext, List<ValidationMessage> errors, String path, ElementDefinition child, ElementDefinition context, Element resource, Element element, NodeStack stack, IdStatus idstatus) throws FHIRException, FHIRException, IOException {
2891    String resourceName = element.getType();
2892    long t = System.nanoTime();
2893    StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
2894    sdTime = sdTime + (System.nanoTime() - t);
2895    // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
2896    if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER ) 
2897      resource = element;
2898    if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, "No profile found for contained resource of type '" + resourceName + "'"))
2899      validateResource(appContext, errors, resource, element, profile, null, idstatus, stack);
2900  }
2901
2902  private void validateDocument(List<ValidationMessage> errors, List<Element> entries, Element composition, NodeStack stack, String fullUrl, String id) {
2903    // first entry must be a composition
2904    if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), composition.getType().equals("Composition"),
2905        "The first entry in a document must be a composition")) {
2906      // the composition subject and section references must resolve in the bundle
2907      Element elem = composition.getNamedChild("subject");
2908      if (rule(errors, IssueType.INVALID, composition.line(), composition.col(), stack.getLiteralPath(), elem != null, "A document composition must have a subject"))
2909        validateBundleReference(errors, entries, elem, "Composition Subject", stack.push(elem, -1, null, null), fullUrl, "Composition", id);
2910      validateSections(errors, entries, composition, stack, fullUrl, id);
2911    }
2912  }
2913  // rule(errors, IssueType.INVALID, bundle.line(), bundle.col(), "Bundle", !"urn:guid:".equals(base), "The base 'urn:guid:' is not valid (use urn:uuid:)");
2914  // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !"urn:guid:".equals(ebase), "The base 'urn:guid:' is not valid");
2915  // rule(errors, IssueType.INVALID, entry.line(), entry.col(), localStack.getLiteralPath(), !Utilities.noString(base) || !Utilities.noString(ebase), "entry
2916  // does not have a base");
2917  // String firstBase = null;
2918  // firstBase = ebase == null ? base : ebase;
2919
2920  private void validateElement(Object appContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
2921      Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept) throws FHIRException, FHIRException, IOException {
2922    element.markValidation(profile, definition);
2923
2924    //          System.out.println("  "+stack.getLiteralPath()+" "+Long.toString((System.nanoTime() - time) / 1000000));
2925    //          time = System.nanoTime();
2926    if (resource.getName().equals("contained")) {
2927      NodeStack ancestor = stack;
2928      while (!ancestor.element.isResource() || ancestor.element.getName().equals("contained"))
2929        ancestor = ancestor.parent;
2930      checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, ancestor.element, element);
2931    } else
2932      checkInvariants(errors, stack.getLiteralPath(), profile, definition, null, null, resource, element);
2933    if (definition.getFixed()!=null)
2934      checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), definition.getSliceName(), null);
2935
2936
2937    // get the list of direct defined children, including slices
2938    List<ElementDefinition> childDefinitions = ProfileUtilities.getChildMap(profile, definition);
2939    if (childDefinitions.isEmpty()) {
2940      if (actualType == null)
2941        return; // there'll be an error elsewhere in this case, and we're going to stop.
2942
2943      StructureDefinition dt = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + actualType);
2944      if (dt == null)
2945        throw new DefinitionException("Unable to resolve actual type " + actualType);
2946
2947      childDefinitions = ProfileUtilities.getChildMap(dt, dt.getSnapshot().getElement().get(0));
2948    }
2949
2950    // 1. List the children, and remember their exact path (convenience)
2951    List<ElementInfo> children = new ArrayList<InstanceValidator.ElementInfo>();
2952    ChildIterator iter = new ChildIterator(stack.getLiteralPath(), element);
2953    while (iter.next())
2954      children.add(new ElementInfo(iter.name(), iter.element(), iter.path(), iter.count()));
2955
2956    // 2. assign children to a definition
2957    // for each definition, for each child, check whether it belongs in the slice
2958    ElementDefinition slicer = null;
2959    boolean unsupportedSlicing = false;
2960    List<String> problematicPaths = new ArrayList<String>();
2961    String slicingPath = null;
2962    int sliceOffset = 0;
2963    for (int i = 0; i < childDefinitions.size(); i++) {
2964      ElementDefinition ed = childDefinitions.get(i);
2965      boolean childUnsupportedSlicing = false;
2966      boolean process = true;
2967      if (ed.hasSlicing() && !ed.getSlicing().getOrdered())
2968        slicingPath = ed.getPath();
2969      else if (slicingPath!=null && ed.getPath().equals(slicingPath))
2970        ; // nothing
2971      else if (slicingPath != null && !ed.getPath().startsWith(slicingPath))
2972        slicingPath = null;
2973      // where are we with slicing
2974      if (ed.hasSlicing()) {
2975        if (slicer != null && slicer.getPath().equals(ed.getPath()))
2976          throw new DefinitionException("Slice encountered midway through path on " + slicer.getPath());
2977        slicer = ed;
2978        process = false;
2979        sliceOffset = i;
2980      } else if (slicer != null && !slicer.getPath().equals(ed.getPath()))
2981        slicer = null;
2982
2983//      if (process) {
2984        for (ElementInfo ei : children) {
2985          boolean match = false;
2986          if (slicer == null || slicer == ed) {
2987            match = nameMatches(ei.name, tail(ed.getPath()));
2988          } else {
2989//            ei.slice = slice;
2990            if (nameMatches(ei.name, tail(ed.getPath())))
2991              try {
2992                match = sliceMatches(appContext, ei.element, ei.path, slicer, ed, profile, errors, stack);
2993                if (match)
2994                  ei.slice = slicer;
2995              } catch (FHIRException e) {
2996                warning(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.path, false, e.getMessage());
2997                unsupportedSlicing = true;
2998                childUnsupportedSlicing = true;
2999              }
3000          }
3001          if (match) {
3002            if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null || ei.definition == slicer, "Profile " + profile.getUrl() + ", Element matches more than one slice")) {
3003              ei.definition = ed;
3004              if (ei.slice == null) {
3005                ei.index = i;
3006              } else {
3007                ei.index = sliceOffset;
3008                ei.sliceindex = i - (sliceOffset + 1);
3009              }
3010            }
3011          } else if (childUnsupportedSlicing) {
3012            problematicPaths.add(ed.getPath());
3013          }
3014        }
3015//      }
3016    }
3017    int last = -1;
3018    int lastSlice = -1;
3019    for (ElementInfo ei : children) {
3020      String sliceInfo = "";
3021      if (slicer != null)
3022        sliceInfo = " (slice: " + slicer.getPath()+")";
3023      if (ei.path.endsWith(".extension"))
3024        rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition != null, "Element is unknown or does not match any slice (url=\"" + ei.element.getNamedChildValue("url") + "\")" + (profile==null ? "" : " for profile " + profile.getUrl()));
3025      else if (!unsupportedSlicing)
3026        if (ei.slice!=null && (ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN)))
3027          hint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.path, (ei.definition != null),
3028              "Element " + ei.element.getName() + " is unknown or does not match any slice " + sliceInfo + (profile==null ? "" : " for profile " + profile.getUrl()));
3029        else
3030          if (ei.slice!=null && (ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || ei.slice.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN)))
3031            rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition != null),
3032                "Element " + ei.element.getName() + " is unknown or does not match any slice " + sliceInfo + (profile==null ? "" : " for profile " + profile.getUrl()));
3033          else
3034            hint(errors, IssueType.NOTSUPPORTED, ei.line(), ei.col(), ei.path, (ei.definition != null),
3035                "Could not verify slice for profile " + profile.getUrl());
3036      // TODO: Should get the order of elements correct when parsing elements that are XML attributes vs. elements
3037      boolean isXmlAttr = false;
3038      if (ei.definition!=null)
3039        for (Enumeration<PropertyRepresentation> r : ei.definition.getRepresentation()) {
3040          if (r.getValue() == PropertyRepresentation.XMLATTR) {
3041            isXmlAttr = true;
3042            break;
3043          }
3044        }
3045
3046      rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition == null) || (ei.index >= last) || isXmlAttr, "As specified by profile " + profile.getUrl() + ", Element '"+ei.name+"' is out of order");
3047      if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered())
3048        rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, "As specified by profile " + profile.getUrl() + ", Element  '"+ei.name+"' is out of order in ordered slice");
3049      if (ei.definition == null || !isXmlAttr)
3050        last = ei.index;
3051      if (ei.slice != null)
3052        lastSlice = ei.sliceindex;
3053      else
3054        lastSlice = -1;
3055    }
3056
3057    // 3. report any definitions that have a cardinality problem
3058    for (ElementDefinition ed : childDefinitions) {
3059      if (ed.getRepresentation().isEmpty()) { // ignore xml attributes
3060        int count = 0;
3061        List<ElementDefinition> slices = null;
3062        if (ed.hasSlicing())
3063          slices = ProfileUtilities.getSliceList(profile, ed);
3064        for (ElementInfo ei : children)
3065          if (ei.definition == ed)
3066            count++;
3067          else if (slices!=null) {
3068            for (ElementDefinition sed : slices) {
3069              if (ei.definition == sed) {
3070                count++;
3071                break;
3072              }
3073            }
3074          }
3075        String location = "Profile " + profile.getUrl() + ", Element '" + stack.getLiteralPath() + "." + tail(ed.getPath()) + (ed.hasSliceName()? "[" + ed.getSliceName() + "]": "");
3076        if (ed.getMin() > 0) {
3077          if (problematicPaths.contains(ed.getPath()))
3078            hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(),
3079            location + "': Unable to check minimum required (" + Integer.toString(ed.getMin()) + ") due to lack of slicing validation");
3080          else
3081            rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(),
3082            location + "': minimum required = " + Integer.toString(ed.getMin()) + ", but only found " + Integer.toString(count));
3083        }
3084        if (ed.hasMax() && !ed.getMax().equals("*")) {
3085          if (problematicPaths.contains(ed.getPath()))
3086            hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()),
3087            location + ": Unable to check max allowed (" + ed.getMax() + ") due to lack of slicing validation");
3088          else
3089            rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()),
3090            location + ": max allowed = " + ed.getMax() + ", but found " + Integer.toString(count));
3091        }
3092      }
3093    }
3094    // 4. check order if any slices are ordered. (todo)
3095
3096    // 5. inspect each child for validity
3097    for (ElementInfo ei : children) {
3098      List<String> profiles = new ArrayList<String>();
3099      if (ei.definition != null) {
3100        String type = null;
3101        ElementDefinition typeDefn = null;
3102        if (ei.definition.getType().size() == 1 && !ei.definition.getType().get(0).getCode().equals("*") && !ei.definition.getType().get(0).getCode().equals("Element")
3103            && !ei.definition.getType().get(0).getCode().equals("BackboneElement")) {
3104          type = ei.definition.getType().get(0).getCode();
3105          // Excluding reference is a kludge to get around versioning issues
3106          if (ei.definition.getType().get(0).hasProfile() && !type.equals("Reference"))
3107            profiles.add(ei.definition.getType().get(0).getProfile());
3108          
3109        } else if (ei.definition.getType().size() == 1 && ei.definition.getType().get(0).getCode().equals("*")) {
3110          String prefix = tail(ei.definition.getPath());
3111          assert prefix.endsWith("[x]");
3112          type = ei.name.substring(prefix.length() - 3);
3113          if (isPrimitiveType(type))
3114            type = Utilities.uncapitalize(type);
3115          // Excluding reference is a kludge to get around versioning issues
3116          if (ei.definition.getType().get(0).hasProfile() && !type.equals("Reference"))
3117            profiles.add(ei.definition.getType().get(0).getProfile());
3118        } else if (ei.definition.getType().size() > 1) {
3119
3120          String prefix = tail(ei.definition.getPath());
3121          assert typesAreAllReference(ei.definition.getType()) || prefix.endsWith("[x]") : prefix;
3122
3123          prefix = prefix.substring(0, prefix.length() - 3);
3124          for (TypeRefComponent t : ei.definition.getType())
3125            if ((prefix + Utilities.capitalize(t.getCode())).equals(ei.name)) {
3126              type = t.getCode();
3127              // Excluding reference is a kludge to get around versioning issues
3128              if (t.hasProfile() && !type.equals("Reference"))
3129                profiles.add(t.getProfile());
3130            }
3131          if (type == null) {
3132            TypeRefComponent trc = ei.definition.getType().get(0);
3133            if (trc.getCode().equals("Reference"))
3134              type = "Reference";
3135            else 
3136              rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), false,
3137                  "The element " + ei.name + " is illegal. Valid types at this point are " + describeTypes(ei.definition.getType()));
3138          }
3139        } else if (ei.definition.getContentReference() != null) {
3140          typeDefn = resolveNameReference(profile.getSnapshot(), ei.definition.getContentReference());
3141        }
3142
3143        if (type != null) {
3144          if (type.startsWith("@")) {
3145            ei.definition = findElement(profile, type.substring(1));
3146            type = null;
3147          }
3148        }
3149        NodeStack localStack = stack.push(ei.element, ei.count, ei.definition, type == null ? typeDefn : resolveType(type));
3150        String localStackLiterapPath = localStack.getLiteralPath();
3151        String eiPath = ei.path;
3152        assert(eiPath.equals(localStackLiterapPath)) : "ei.path: " + ei.path + "  -  localStack.getLiteralPath: " + localStackLiterapPath;
3153        boolean thisIsCodeableConcept = false;
3154
3155        if (type != null) {
3156          if (isPrimitiveType(type)) {
3157            checkPrimitive(appContext, errors, ei.path, type, ei.definition, ei.element, profile);
3158          } else {
3159            if (type.equals("Identifier"))
3160              checkIdentifier(errors, ei.path, ei.element, ei.definition);
3161            else if (type.equals("Coding"))
3162              checkCoding(errors, ei.path, ei.element, profile, ei.definition, inCodeableConcept);
3163            else if (type.equals("CodeableConcept")) {
3164              checkCodeableConcept(errors, ei.path, ei.element, profile, ei.definition);
3165              thisIsCodeableConcept = true;
3166            } else if (type.equals("Reference"))
3167              checkReference(appContext, errors, ei.path, ei.element, profile, ei.definition, actualType, localStack);
3168
3169            // 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
3170            if (type.equals("Extension") && ei.element.getChildValue("url").contains("/"))
3171              checkExtension(appContext, errors, ei.path, resource, ei.element, ei.definition, profile, localStack);
3172            else if (type.equals("Resource"))
3173              validateContains(appContext, errors, ei.path, ei.definition, definition, resource, ei.element, localStack, idStatusForEntry(element, ei)); // if
3174            // (str.matches(".*([.,/])work\\1$"))
3175            else {
3176              StructureDefinition p = null;
3177              boolean elementValidated = false;
3178              if (profiles.isEmpty()) {
3179                p = getProfileForType(type);
3180                rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown type " + type);
3181              } else if (profiles.size()==1) {
3182                p = this.context.fetchResource(StructureDefinition.class, profiles.get(0));
3183                rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + profiles.get(0));
3184              } else {
3185                elementValidated = true;
3186                HashMap<String, List<ValidationMessage>> goodProfiles = new HashMap<String, List<ValidationMessage>>();
3187                List<List<ValidationMessage>> badProfiles = new ArrayList<List<ValidationMessage>>();
3188                for (String typeProfile : profiles) {
3189                  p = this.context.fetchResource(StructureDefinition.class, typeProfile);
3190                  if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, p != null, "Unknown profile " + typeProfile)) {
3191                    List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
3192                    validateElement(appContext, profileErrors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept);
3193                    boolean hasError = false;
3194                    for (ValidationMessage msg : profileErrors) {
3195                      if (msg.getLevel()==ValidationMessage.IssueSeverity.ERROR || msg.getLevel()==ValidationMessage.IssueSeverity.FATAL) {
3196                        hasError = true;
3197                        break;
3198                      }
3199                    }
3200                    if (hasError)
3201                      badProfiles.add(profileErrors);
3202                    else
3203                      goodProfiles.put(typeProfile, profileErrors);
3204                  }
3205                  if (goodProfiles.size()==1) {
3206                    errors.addAll(goodProfiles.get(0));
3207                  } else if (goodProfiles.size()==0) {
3208                    rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, false, "Unable to find matching profile among choices: " + StringUtils.join("; ", profiles));
3209                    for (List<ValidationMessage> messages : badProfiles) {
3210                      errors.addAll(messages);
3211                    }
3212                  } else {
3213                    warning(errors, IssueType.STRUCTURE, ei.line(), ei.col(), ei.path, false, "Found multiple matching profiles among choices: " + StringUtils.join("; ", goodProfiles.keySet()));
3214                    for (List<ValidationMessage> messages : goodProfiles.values()) {
3215                      errors.addAll(messages);
3216                    }                    
3217                  }
3218                }
3219              }
3220              if (p!=null) {
3221                if (!elementValidated)
3222                  validateElement(appContext, errors, p, p.getSnapshot().getElement().get(0), profile, ei.definition, resource, ei.element, type, localStack, thisIsCodeableConcept);
3223                int index = profile.getSnapshot().getElement().indexOf(ei.definition);
3224                if (index < profile.getSnapshot().getElement().size() - 1) {
3225                  String nextPath = profile.getSnapshot().getElement().get(index+1).getPath();
3226                  if (!nextPath.equals(ei.definition.getPath()) && nextPath.startsWith(ei.definition.getPath()))
3227                    validateElement(appContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, thisIsCodeableConcept);
3228                }
3229              }
3230            }
3231          }
3232        } else {
3233          if (rule(errors, IssueType.STRUCTURE, ei.line(), ei.col(), stack.getLiteralPath(), ei.definition != null, "Unrecognised Content " + ei.name))
3234            validateElement(appContext, errors, profile, ei.definition, null, null, resource, ei.element, type, localStack, false);
3235        }
3236      }
3237    }
3238  }
3239
3240  private IdStatus idStatusForEntry(Element ep, ElementInfo ei) {
3241    if (isBundleEntry(ei.path)) {
3242      Element req = ep.getNamedChild("request");
3243      Element resp = ep.getNamedChild("response");
3244      Element fullUrl = ep.getNamedChild("fullUrl");
3245      Element method = null;
3246      Element url = null;
3247      if (req != null) {
3248        method = req.getNamedChild("method");
3249        url = req.getNamedChild("url");
3250      }
3251      if (resp != null) {
3252        return IdStatus.OPTIONAL;
3253      } if (method == null) {
3254        if (fullUrl == null)
3255          return IdStatus.REQUIRED;
3256        else if (fullUrl.primitiveValue().startsWith("urn:uuid:") || fullUrl.primitiveValue().startsWith("urn:oid:"))
3257          return IdStatus.OPTIONAL;
3258        else
3259          return IdStatus.REQUIRED;
3260      } else {
3261        String s = method.primitiveValue();
3262        if (s.equals("PUT")) {
3263          if (url == null)
3264            return IdStatus.REQUIRED;
3265          else
3266            return IdStatus.OPTIONAL; // or maybe prohibited? not clear
3267        } else if (s.equals("POST"))
3268          return IdStatus.OPTIONAL; // this should be prohibited, but see task 9102
3269        else // actually, we should never get to here; a bundle entry with method get/delete should not have a resource
3270          return IdStatus.OPTIONAL;                                     
3271      }
3272    } else if (isParametersEntry(ei.path) || isBundleOutcome(ei.path))
3273      return IdStatus.OPTIONAL; 
3274    else
3275      return IdStatus.REQUIRED; 
3276  }
3277
3278  private void checkInvariants(List<ValidationMessage> errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element) throws FHIRException, FHIRException {
3279    if (noInvariantChecks)
3280      return;
3281
3282    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3283      if (inv.hasExpression()) {
3284        ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
3285        if (n == null) {
3286          long t = System.nanoTime();
3287          try {
3288            n = fpe.parse(inv.getExpression());
3289          } catch (FHIRLexerException e) {
3290            throw new FHIRException("Problem processing expression "+inv.getExpression() +" in profile " + profile.getUrl() + " path " + path + ": " + e.getMessage());
3291          }
3292          fpeTime = fpeTime + (System.nanoTime() - t);
3293          inv.setUserData("validator.expression.cache", n);
3294        }
3295
3296        String msg;
3297        boolean ok;
3298        try {
3299          long t = System.nanoTime();
3300          ok = fpe.evaluateToBoolean(resource, element, n);
3301          fpeTime = fpeTime + (System.nanoTime() - t);
3302          msg = fpe.forLog();
3303        } catch (Exception ex) {
3304          ok = false;
3305          msg = ex.getMessage(); 
3306        }
3307        if (!ok) {
3308          try {
3309            ok = fpe.evaluateToBoolean(resource, element, n);
3310          } catch (PathEngineException e) {
3311            throw new FHIRException("Problem processing expression "+inv.getExpression() +" in profile " + profile.getUrl() + " path " + path + ": " + e.getMessage());
3312          }
3313          if (!Utilities.noString(msg))
3314            msg = " ("+msg+")";
3315          if (inv.getSeverity() == ConstraintSeverity.ERROR)
3316            rule(errors, IssueType.INVARIANT, element.line(), element.col(), path, ok, inv.getHuman()+msg+" ["+inv.getExpression()+"]");
3317          else if (inv.getSeverity() == ConstraintSeverity.WARNING)
3318            warning(errors, IssueType.INVARIANT, element.line(), element.line(), path, ok, inv.getHuman()+msg+" ["+inv.getExpression()+"]");
3319        }
3320      }
3321    }
3322  }
3323
3324  private void validateMessage(List<ValidationMessage> errors, List<Element> entries, Element messageHeader, NodeStack stack, String fullUrl, String id) {
3325    // first entry must be a messageheader
3326    if (rule(errors, IssueType.INVALID, messageHeader.line(), messageHeader.col(), stack.getLiteralPath(), messageHeader.getType().equals("MessageHeader"),
3327        "The first entry in a message must be a MessageHeader")) {
3328      // the composition subject and section references must resolve in the bundle
3329      List<Element> elements = messageHeader.getChildren("data");
3330      for (Element elem: elements)
3331        validateBundleReference(errors, entries, elem, "MessageHeader Data", stack.push(elem, -1, null, null), fullUrl, "MessageHeader", id);
3332    }
3333  }
3334
3335  private void validateObservation(List<ValidationMessage> errors, Element element, NodeStack stack) {
3336    // all observations should have a subject, a performer, and a time
3337
3338    bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("subject") != null, "All observations should have a subject");
3339    bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("performer") != null, "All observations should have a performer");
3340    bpCheck(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), element.getNamedChild("effectiveDateTime") != null || element.getNamedChild("effectivePeriod") != null,
3341        "All observations should have an effectiveDateTime or an effectivePeriod");
3342  }
3343
3344  /*
3345   * The actual base entry point
3346   */
3347  /*  private void validateResource(List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, ValidationProfileSet profiles, IdStatus idstatus, NodeStack stack) throws FHIRException, FHIRException {
3348    List<StructureDefinition> declProfiles = new ArrayList<StructureDefinition>();
3349    List<Element> meta = element.getChildrenByName("meta");
3350    if (!meta.isEmpty()) {
3351      for (Element profileName : meta.get(0).getChildrenByName("profile")) {
3352        StructureDefinition sd = context.fetchResource(StructureDefinition.class, profileName.getValue());
3353        if (sd != null)
3354          declProfiles.add(sd);
3355      }
3356    }
3357
3358    if (!declProfiles.isEmpty()) {
3359      // Validate against profiles rather than the resource itself as they'll be more constrained and will cover the resource elements anyhow
3360      for (StructureDefinition sd : declProfiles)
3361        validateResource2(errors, resource, element, sd, profiles, idstatus, stack);
3362    } else
3363      validateResource2(errors, resource, element, defn, profiles, idstatus, stack);
3364  }*/
3365
3366  private void validateResource(Object appContext, List<ValidationMessage> errors, Element resource, Element element, StructureDefinition defn, ValidationProfileSet profiles, IdStatus idstatus, NodeStack stack) throws FHIRException, FHIRException, IOException {
3367    assert stack != null;
3368    assert resource != null;
3369
3370    boolean ok = true;
3371
3372    String resourceName = element.getType(); // todo: consider namespace...?
3373    if (defn == null) {
3374      long t = System.nanoTime();
3375      defn = element.getProperty().getStructure();
3376      if (defn == null)
3377        defn = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
3378      if (profiles!=null)
3379        getResourceProfiles(resource, stack).addProfiles(errors, profiles, stack.getLiteralPath(), element);
3380      sdTime = sdTime + (System.nanoTime() - t);
3381      ok = rule(errors, IssueType.INVALID, element.line(), element.col(), stack.addToLiteralPath(resourceName), defn != null, "No definition found for resource type '" + resourceName + "'");
3382    }
3383
3384    String type = defn.getKind() == StructureDefinitionKind.LOGICAL ? defn.getId() : defn.getType();
3385    // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead 
3386    if (!type.equals(resourceName) && resourceName.equals("Bundle")) {
3387      Element first = getFirstEntry(element);
3388      if (first != null && first.getType().equals(type)) {
3389        element = first;
3390        resourceName = element.getType();
3391        idstatus = IdStatus.OPTIONAL; // why?
3392      }
3393    }
3394    ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), type.equals(resourceName), "Specified profile type was '" + type + "', but found type '" + resourceName + "'");
3395
3396    if (ok) {
3397      if (idstatus == IdStatus.REQUIRED && (element.getNamedChild("id") == null))
3398        rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource requires an id, but none is present");
3399      else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild("id") != null))
3400        rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, "Resource has an id, but none is allowed");
3401      start(appContext, errors, resource, element, defn, stack); // root is both definition and type
3402    }
3403  }
3404
3405  private void loadProfiles(ValidationProfileSet profiles) throws DefinitionException {
3406    if (profiles != null) { 
3407      for (String profile : profiles.getCanonicalUrls()) {
3408        StructureDefinition p = context.fetchResource(StructureDefinition.class, profile);
3409        if (p == null)
3410          throw new DefinitionException("StructureDefinition '" + profile + "' not found");
3411        profiles.getDefinitions().add(p);
3412      }
3413    }
3414  }
3415
3416  private Element getFirstEntry(Element bundle) {
3417    List<Element> list = new ArrayList<Element>();
3418    bundle.getNamedChildren("entry", list);
3419    if (list.isEmpty())
3420      return null;
3421    Element resource = list.get(0).getNamedChild("resource");
3422    if (resource == null)
3423      return null;
3424    else
3425      return resource;
3426  }
3427
3428  private void validateSections(List<ValidationMessage> errors, List<Element> entries, Element focus, NodeStack stack, String fullUrl, String id) {
3429    List<Element> sections = new ArrayList<Element>();
3430    focus.getNamedChildren("entry", sections);
3431    int i = 0;
3432    for (Element section : sections) {
3433      NodeStack localStack = stack.push(section, 1, null, null);
3434      validateBundleReference(errors, entries, section.getNamedChild("content"), "Section Content", localStack, fullUrl, "Composition", id);
3435      validateSections(errors, entries, section, localStack, fullUrl, id);
3436      i++;
3437    }
3438  }
3439
3440  private boolean valueMatchesCriteria(Element value, ElementDefinition criteria) throws FHIRException {
3441    if (criteria.hasFixed()) {
3442      List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
3443      checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), "value", null);
3444      return msgs.size() == 0;
3445    } else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) {
3446      throw new FHIRException("Unable to resolve slice matching - slice matching by value set not done");      
3447    } else {
3448      throw new FHIRException("Unable to resolve slice matching - no fixed value or required value set");
3449    }
3450  }
3451
3452  private boolean yearIsValid(String v) {
3453    if (v == null) {
3454      return false;
3455    }
3456    try {
3457      int i = Integer.parseInt(v.substring(0, Math.min(4, v.length())));
3458      return i >= 1800 && i <= 2100;
3459    } catch (NumberFormatException e) {
3460      return false;
3461    }
3462  }
3463
3464  public class ChildIterator {
3465    private String basePath;
3466    private Element parent;
3467    private int cursor;
3468    private int lastCount;
3469
3470    public ChildIterator(String path, Element element) {
3471      parent = element;
3472      basePath = path;
3473      cursor = -1;
3474    }
3475
3476    public int count() {
3477      String nb = cursor == 0 ? "--" : parent.getChildren().get(cursor-1).getName();
3478      String na = cursor >= parent.getChildren().size() - 1 ? "--" : parent.getChildren().get(cursor+1).getName();
3479      if (name().equals(nb) || name().equals(na) ) {
3480        return lastCount + 1;
3481      } else
3482        return -1;
3483    }
3484
3485    public Element element() {
3486      return parent.getChildren().get(cursor);
3487    }
3488
3489    public String name() {
3490      return element().getName();
3491    }
3492
3493    public boolean next() {
3494      if (cursor == -1) {
3495        cursor++;
3496        lastCount = 0;
3497      } else {
3498        String lastName = name();
3499        cursor++;
3500        if (cursor < parent.getChildren().size() && name().equals(lastName))
3501          lastCount++;
3502        else
3503          lastCount = 0;
3504      }
3505      return cursor < parent.getChildren().size();
3506    }
3507
3508    public String path() {
3509      int i = count();
3510      String sfx = "";
3511      if (i > -1) {
3512        sfx = "[" + Integer.toString(lastCount + 1) + "]";
3513      }
3514      return basePath + "." + name() + sfx;
3515    }
3516  }
3517
3518  public class NodeStack {
3519    private ElementDefinition definition;
3520    private Element element;
3521    private ElementDefinition extension;
3522    private String literalPath; // xpath format
3523    private List<String> logicalPaths; // dotted format, various entry points
3524    private NodeStack parent;
3525    private ElementDefinition type;
3526
3527    public NodeStack() {
3528    }     
3529
3530    public NodeStack(Element element) {
3531      this.element = element;
3532      literalPath = element.getName();
3533    }     
3534
3535    public String addToLiteralPath(String... path) {
3536      StringBuilder b = new StringBuilder();
3537      b.append(getLiteralPath());
3538      for (String p : path) {
3539        if (p.startsWith(":")) {
3540          b.append("[");
3541          b.append(p.substring(1));
3542          b.append("]");
3543        } else {
3544          b.append(".");
3545          b.append(p);
3546        }         
3547      }   
3548      return b.toString();
3549    }
3550
3551    private ElementDefinition getDefinition() {
3552      return definition;
3553    }
3554
3555    private Element getElement() {
3556      return element;
3557    }
3558
3559    protected String getLiteralPath() {
3560      return literalPath == null ? "" : literalPath;
3561    }
3562
3563    private List<String> getLogicalPaths() {
3564      return logicalPaths == null ? new ArrayList<String>() : logicalPaths;
3565    }
3566
3567    private ElementDefinition getType() {
3568      return type;
3569    }
3570
3571    private NodeStack push(Element element, int count, ElementDefinition definition, ElementDefinition type) {
3572      NodeStack res = new NodeStack();
3573      res.parent = this;
3574      res.element = element;
3575      res.definition = definition;
3576      res.literalPath = getLiteralPath() + "." + element.getName();
3577      if (count > -1)
3578        res.literalPath = res.literalPath + "[" + Integer.toString(count) + "]";
3579      res.logicalPaths = new ArrayList<String>();
3580      if (type != null) {
3581        // type will be bull if we on a stitching point of a contained resource, or if....
3582        res.type = type;
3583        String t = tail(definition.getPath());
3584        for (String lp : getLogicalPaths()) {
3585          res.logicalPaths.add(lp + "." + t);
3586          if (t.endsWith("[x]"))
3587            res.logicalPaths.add(lp + "." + t.substring(0, t.length() - 3) + type.getPath());
3588        }
3589        res.logicalPaths.add(type.getPath());
3590      } else if (definition != null) {
3591        for (String lp : getLogicalPaths())
3592          res.logicalPaths.add(lp + "." + element.getName());
3593      } else
3594        res.logicalPaths.addAll(getLogicalPaths());
3595      // CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3596      // for (String lp : res.logicalPaths)
3597      // b.append(lp);
3598      // System.out.println(res.literalPath+" : "+b.toString());
3599      return res;
3600    }
3601
3602    private void setType(ElementDefinition type) {
3603      this.type = type;
3604    }
3605  }
3606
3607  private void checkForProcessingInstruction(List<ValidationMessage> errors, Document document) {
3608    Node node = document.getFirstChild();
3609    while (node != null) {
3610      rule(errors, IssueType.INVALID, -1, -1, "(document)", node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE, "No processing instructions allowed in resources");
3611      node = node.getNextSibling();
3612    }
3613  }
3614
3615  public class ElementInfo {
3616
3617    public int index; // order of definition in overall order. all slices get the index of the slicing definition
3618    public int sliceindex; // order of the definition in the slices (if slice != null)
3619    public int count;
3620    public ElementDefinition definition;
3621    public ElementDefinition slice;
3622    private Element element;
3623    private String name;
3624    private String path;
3625
3626    public ElementInfo(String name, Element element, String path, int count) {
3627      this.name = name;
3628      this.element = element;
3629      this.path = path;
3630      this.count = count;
3631    }
3632
3633    public int col() {
3634      return element.col();
3635    }
3636
3637    public int line() {
3638      return element.line();
3639    }
3640
3641  }
3642
3643  public String reportTimes() {
3644    String s = String.format("Times: overall = %d, tx = %d, sd = %d, load = %d, fpe = %d", overall, txTime, sdTime, loadTime, fpeTime);
3645    overall = 0;
3646    txTime = 0;
3647    sdTime = 0;
3648    loadTime = 0;
3649    fpeTime = 0;
3650    return s;
3651  }
3652
3653  public boolean isNoBindingMsgSuppressed() {
3654    return noBindingMsgSuppressed;
3655  }
3656
3657  public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) {
3658    this.noBindingMsgSuppressed = noBindingMsgSuppressed;
3659    return this;
3660  }
3661
3662  
3663  public boolean isNoTerminologyChecks() {
3664    return noTerminologyChecks;
3665  }
3666
3667  public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) {
3668    this.noTerminologyChecks = noTerminologyChecks;
3669    return this;
3670  }
3671
3672  public void checkAllInvariants(){
3673    for (StructureDefinition sd : context.allStructures()) {
3674      if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
3675        for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3676          for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3677            if (inv.hasExpression()) {
3678              try {
3679                ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
3680                if (n == null) {
3681                  n = fpe.parse(inv.getExpression());
3682                  inv.setUserData("validator.expression.cache", n);
3683                }
3684                fpe.check(null, sd.getKind() == StructureDefinitionKind.RESOURCE ?  sd.getType() : "DomainResource", ed.getPath(), n);
3685              } catch (Exception e) {
3686                System.out.println("Error processing structure ["+sd.getId()+"] path "+ed.getPath()+":"+inv.getKey()+" (\""+inv.getExpression()+"\"): "+e.getMessage());
3687              }
3688            }
3689          }
3690        }
3691      }
3692    }
3693  }
3694
3695}