001package org.hl7.fhir.validation;
002
003
004import static org.apache.commons.lang3.StringUtils.isBlank;
005
006import java.io.ByteArrayOutputStream;
007import java.io.IOException;
008import java.text.ParseException;
009import java.text.SimpleDateFormat;
010import java.util.ArrayList;
011import java.util.Date;
012import java.util.HashMap;
013import java.util.List;
014import java.util.Map;
015
016/*
017  Copyright (c) 2011+, HL7, Inc.
018  All rights reserved.
019
020  Redistribution and use in source and binary forms, with or without modification,
021  are permitted provided that the following conditions are met:
022
023   * Redistributions of source code must retain the above copyright notice, this
024     list of conditions and the following disclaimer.
025   * Redistributions in binary form must reproduce the above copyright notice,
026     this list of conditions and the following disclaimer in the documentation
027     and/or other materials provided with the distribution.
028   * Neither the name of HL7 nor the names of its contributors may be used to
029     endorse or promote products derived from this software without specific
030     prior written permission.
031
032  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
033  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
034  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
035  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
036  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
037  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
038  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
039  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
040  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
041  POSSIBILITY OF SUCH DAMAGE.
042
043 */
044
045import org.apache.commons.lang3.StringUtils;
046import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50;
047import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50;
048import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50;
049import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50;
050import org.hl7.fhir.exceptions.FHIRException;
051import org.hl7.fhir.r5.context.ContextUtilities;
052import org.hl7.fhir.r5.context.IWorkerContext;
053import org.hl7.fhir.r5.elementmodel.Element;
054import org.hl7.fhir.r5.elementmodel.JsonParser;
055import org.hl7.fhir.r5.formats.IParser.OutputStyle;
056import org.hl7.fhir.r5.model.Base;
057import org.hl7.fhir.r5.model.Coding;
058import org.hl7.fhir.r5.model.DomainResource;
059import org.hl7.fhir.r5.model.Resource;
060import org.hl7.fhir.r5.model.StructureDefinition;
061import org.hl7.fhir.r5.model.ValueSet;
062import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
063import org.hl7.fhir.r5.utils.XVerExtensionManager;
064import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
065import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader;
066import org.hl7.fhir.utilities.FhirPublication;
067import org.hl7.fhir.utilities.Utilities;
068import org.hl7.fhir.utilities.i18n.I18nConstants;
069import org.hl7.fhir.utilities.validation.ValidationMessage;
070import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
071import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
072import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
073import org.hl7.fhir.validation.cli.utils.ValidationLevel;
074import org.hl7.fhir.validation.instance.utils.IndexedElement;
075
076public class BaseValidator implements IValidationContextResourceLoader {
077
078  public class BooleanValue {
079
080    private boolean value;
081
082    public BooleanValue(boolean value) {
083      super();
084      this.value = value;
085    }
086
087    public boolean isValue() {
088      return value;
089    }
090
091    public void setValue(boolean value) {
092      this.value = value;
093    }
094
095    public void see(boolean ok) {
096      value = value && ok;
097    }
098  }
099
100  public class TrackedLocationRelatedMessage {
101    private Object location;
102    private ValidationMessage vmsg;
103    public TrackedLocationRelatedMessage(Object location, ValidationMessage vmsg) {
104      super();
105      this.location = location;
106      this.vmsg = vmsg;
107    }
108    public Object getLocation() {
109      return location;
110    }
111    public ValidationMessage getVmsg() {
112      return vmsg;
113    }
114      }
115
116  public class ValidationControl {
117    private boolean allowed;
118    private IssueSeverity level;
119    
120    public ValidationControl(boolean allowed, IssueSeverity level) {
121      super();
122      this.allowed = allowed;
123      this.level = level;
124    }
125    public boolean isAllowed() {
126      return allowed;
127    }
128    public IssueSeverity getLevel() {
129      return level;
130    }
131  }
132
133  public static final String NO_RULE_DATE = ValidationMessage.NO_RULE_DATE;
134
135  protected final String META = "meta";
136  protected final String ENTRY = "entry";
137  protected final String LINK = "link";
138  protected final String DOCUMENT = "document";
139  protected final String RESOURCE = "resource";
140  protected final String MESSAGE = "message";
141  protected final String SEARCHSET = "searchset";
142  protected final String ID = "id";
143  protected final String FULL_URL = "fullUrl";
144  protected final String PATH_ARG = ":0";
145  protected final String TYPE = "type";
146  protected final String BUNDLE = "Bundle";
147  protected final String LAST_UPDATED = "lastUpdated";
148
149
150  protected Source source;
151  protected IWorkerContext context;
152  protected TimeTracker timeTracker = new TimeTracker();
153  protected XVerExtensionManager xverManager;
154  protected List<TrackedLocationRelatedMessage> trackedMessages = new ArrayList<>();
155  protected List<ValidationMessage> messagesToRemove = new ArrayList<>();
156  private ValidationLevel level = ValidationLevel.HINTS;
157  protected Coding jurisdiction;
158
159  public BaseValidator(IWorkerContext context, XVerExtensionManager xverManager) {
160    super();
161    this.context = context;
162    this.xverManager = xverManager;
163    if (this.xverManager == null) {
164      this.xverManager = new XVerExtensionManager(context);
165    }
166
167  }
168  
169  private boolean doingLevel(IssueSeverity error) {
170    switch (error) {
171    case ERROR:
172      return level == null || level == ValidationLevel.ERRORS || level == ValidationLevel.WARNINGS || level == ValidationLevel.HINTS;
173    case FATAL:
174      return level == null || level == ValidationLevel.ERRORS || level == ValidationLevel.WARNINGS || level == ValidationLevel.HINTS;
175    case WARNING:
176      return level == null || level == ValidationLevel.WARNINGS || level == ValidationLevel.HINTS;
177    case INFORMATION:
178      return level == null || level == ValidationLevel.HINTS;
179    case NULL:
180      return true;
181    default:
182      return true;    
183    }
184  }
185
186  private boolean doingErrors() {
187    return doingLevel(IssueSeverity.ERROR);
188  }
189  
190  private boolean doingWarnings() {
191    return doingLevel(IssueSeverity.WARNING);
192  }
193  
194  private boolean doingHints() {
195    return doingLevel(IssueSeverity.INFORMATION);
196  }
197  
198
199  /**
200   * Use to control what validation the validator performs. 
201   * Using this, you can turn particular kinds of validation on and off 
202   * In addition, you can override the error | warning | hint level and make it a different level
203   * 
204   * There is no way to do this using the command line validator; it's a service that is only 
205   * offered when the validator is hosted in some other process
206   */
207  private Map<String, ValidationControl> validationControl = new HashMap<>();
208
209    /**
210   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
211   * 
212   * @param thePass
213   *          Set this parameter to <code>false</code> if the validation does not pass
214   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
215   */
216  @Deprecated
217  protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) {
218    if (!thePass && doingErrors()) {
219      addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, null);
220    }
221    return thePass;
222  }
223
224  protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
225    if (!thePass && doingErrors()) {
226      String msg = context.formatMessage(theMessage, theMessageArguments);
227      addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.FATAL, theMessage);
228    }
229    return thePass;
230  }
231
232  /**
233   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
234   * 
235   * @param thePass
236   *          Set this parameter to <code>false</code> if the validation does not pass
237   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
238   */
239  @Deprecated
240  protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String msg) {
241    if (!thePass && doingErrors()) {
242      String path = toPath(pathParts);
243      addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null);
244    }
245    return thePass;
246  }
247
248  /**
249   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
250   * 
251   * @param thePass
252   *          Set this parameter to <code>false</code> if the validation does not pass
253   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
254   */
255  @Deprecated
256  protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
257    if (!thePass && doingErrors()) {
258      String path = toPath(pathParts);
259      addValidationMessage(errors, ruleDate, type, -1, -1, path, context.formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL, theMessage);
260    }
261    return thePass;
262  }
263
264  /**
265   * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
266   * 
267   * @param thePass
268   *          Set this parameter to <code>false</code> if the validation does not pass
269   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
270   */
271  @Deprecated
272  protected boolean fail(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg) {
273    if (!thePass && doingErrors()) {
274      addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.FATAL, null);
275    }
276    return thePass;
277  }
278  //TODO: i18n
279  protected boolean grammarWord(String w) {
280    return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of");
281  }
282
283  /**
284   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
285   * 
286   * @param thePass
287   *          Set this parameter to <code>false</code> if the validation does not pass
288   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
289   */
290  protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg) {
291    if (!thePass && doingHints()) {
292      String message = context.formatMessage(msg);
293      addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, msg);
294    }
295    return thePass;
296  }
297
298  /**
299   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails. And mark it as a slicing hint for later recovery if appropriate
300   * 
301   * @param thePass
302   *          Set this parameter to <code>false</code> if the validation does not pass
303   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
304   */
305  //FIXME: formatMessage should be done here
306  protected boolean slicingHint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, boolean isCritical, String msg, String html, String[] text) {
307    if (!thePass && doingHints()) {
308      addValidationMessage(errors, ruleDate, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text).setCriticalSignpost(isCritical);
309    }
310    return thePass;
311  }
312
313  /**
314   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
315   * 
316   * @param thePass
317   *          Set this parameter to <code>false</code> if the validation does not pass
318   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
319   */
320  protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
321    if (!thePass && doingHints()) {
322      String message = context.formatMessage(theMessage, theMessageArguments);
323      addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage);
324    }
325    return thePass;
326  }
327
328  protected boolean hintPlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) {
329    if (!thePass && doingHints()) {
330      String message = context.formatMessagePlural(num, theMessage, theMessageArguments);
331      addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage);
332    }
333    return thePass;
334  }
335
336  protected ValidationMessage signpost(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String theMessage, Object... theMessageArguments) {
337    String message = context.formatMessage(theMessage, theMessageArguments);
338    return addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, theMessage).setSignpost(true);
339  }
340
341  protected boolean txHint(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
342    if (!thePass && doingHints()) {
343      String message = context.formatMessage(theMessage, theMessageArguments);
344      addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.INFORMATION, Source.TerminologyEngine, theMessage).setTxLink(txLink);
345    }
346    return thePass;
347  }
348
349  /**
350   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
351   * 
352   * @param thePass
353   *          Set this parameter to <code>false</code> if the validation does not pass
354   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
355   */
356  protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
357    if (!thePass && doingHints()) {
358      String path = toPath(pathParts);
359      String message = context.formatMessage(theMessage, theMessageArguments);
360      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage);
361    }
362    return thePass;
363  }
364
365  /**
366   * Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
367   * 
368   * @param thePass
369   *          Set this parameter to <code>false</code> if the validation does not pass
370   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
371   */
372  protected boolean hint(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
373    if (!thePass && doingHints()) {
374      String message = context.formatMessage(theMessage, theMessageArguments);
375      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, null);
376    }
377    return thePass;
378  }
379
380  /**
381   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
382   * 
383   * @param thePass
384   *          Set this parameter to <code>false</code> if the validation does not pass
385   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
386   */
387  protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
388    if (!thePass && doingErrors()) {
389      String message = context.formatMessage(theMessage, theMessageArguments);
390      addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage);
391    }
392    return thePass;
393  }
394
395  protected boolean rulePlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) {
396    if (!thePass && doingErrors()) {
397      String message = context.formatMessagePlural(num, theMessage, theMessageArguments);
398      addValidationMessage(errors, ruleDate, type, line, col, path, message, IssueSeverity.ERROR, theMessage);
399    }
400    return thePass;
401  }
402
403  protected boolean txRule(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
404    if (!thePass && doingErrors()) {
405      String message = context.formatMessage(theMessage, theMessageArguments);
406      ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(theMessage);
407      vm.setRuleDate(ruleDate);
408      if (checkMsgId(theMessage, vm)) {
409        errors.add(vm.setTxLink(txLink));
410      }
411    }
412    return thePass;
413  }
414
415  /**
416   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
417   * 
418   * @param thePass
419   *          Set this parameter to <code>false</code> if the validation does not pass
420   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
421   */
422  protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String msg) {
423    if (!thePass && doingErrors()) {
424      String path = toPath(pathParts);
425      addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.ERROR, null);
426    }
427    return thePass;
428  }
429
430  /**
431   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
432   * 
433   * @param thePass
434   *          Set this parameter to <code>false</code> if the validation does not pass
435   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
436   */
437  protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
438    if (!thePass && doingErrors()) {
439      String path = toPath(pathParts);
440      String message = context.formatMessage(theMessage, theMessageArguments);
441      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage);
442    }
443    return thePass;
444  }
445
446  /**
447   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
448   * 
449   * @param thePass
450   *          Set this parameter to <code>false</code> if the validation does not pass
451   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
452   */
453
454
455  protected boolean rule(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
456    if (!thePass && doingErrors()) {
457      String message = context.formatMessage(theMessage, theMessageArguments);
458      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage);
459    }
460    return thePass;
461  }
462
463  protected boolean rulePlural(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, int num, String theMessage, Object... theMessageArguments) {
464    if (!thePass && doingErrors()) {
465      String message = context.formatMessagePlural(num, theMessage, theMessageArguments);
466      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.ERROR, theMessage);
467    }
468    return thePass;
469  }
470
471  public boolean rule(List<ValidationMessage> errors, String ruleDate, Source source, IssueType type, String path, boolean thePass, String msg) {
472    if (!thePass && doingErrors()) {
473      addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.ERROR, source, null);
474    }
475    return thePass;
476  }
477
478  /**
479   * Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
480   * 
481   * @param thePass
482   *          Set this parameter to <code>false</code> if the validation does not pass
483   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
484   */
485  protected boolean ruleHtml(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) {
486    if (!thePass && doingErrors()) {
487      msg = context.formatMessage(msg, null);
488      html = context.formatMessage(html, null);
489      addValidationMessage(errors, ruleDate, type, path, msg, html, IssueSeverity.ERROR, null);
490    }
491    return thePass;
492  }
493
494  protected String splitByCamelCase(String s) {
495    StringBuilder b = new StringBuilder();
496    for (int i = 0; i < s.length(); i++) {
497      char c = s.charAt(i);
498      if (Character.isUpperCase(c) && !(i == 0 || Character.isUpperCase(s.charAt(i-1))))
499        b.append(' ');
500      b.append(c);
501    }
502    return b.toString();
503  }
504
505  protected String stripPunctuation(String s, boolean numbers) {
506    StringBuilder b = new StringBuilder();
507    for (char c : s.toCharArray()) {
508      int t = Character.getType(c);
509      if (t == Character.UPPERCASE_LETTER || t == Character.LOWERCASE_LETTER || t == Character.TITLECASE_LETTER || t == Character.MODIFIER_LETTER || t == Character.OTHER_LETTER || (t == Character.DECIMAL_DIGIT_NUMBER && numbers) || (t == Character.LETTER_NUMBER && numbers) || c == ' ')
510        b.append(c);
511    }
512    return b.toString();
513  }
514
515  private String toPath(List<String> pathParts) {
516    if (pathParts == null || pathParts.isEmpty()) {
517      return "";
518    }
519    return "//" + StringUtils.join(pathParts, '/');
520  }
521
522  /**
523   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
524   * 
525   * @param thePass
526   *          Set this parameter to <code>false</code> if the validation does not pass
527   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
528   */
529  protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
530    if (!thePass && doingWarnings()) {
531      String nmsg = context.formatMessage(msg, theMessageArguments);
532      IssueSeverity severity = IssueSeverity.WARNING;
533      addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg);
534    }
535    return thePass;
536
537  }
538
539  protected boolean warningPlural(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, int num, String msg, Object... theMessageArguments) {
540    if (!thePass && doingWarnings()) {
541      String nmsg = context.formatMessagePlural(num, msg, theMessageArguments);
542      IssueSeverity severity = IssueSeverity.WARNING;
543      addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, severity, msg);
544    }
545    return thePass;
546
547  }
548
549  protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, String id) {
550    Source source = this.source;
551    return addValidationMessage(errors, ruleDate, type, line, col, path, msg, theSeverity, source, id);
552  }
553
554  protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource, String id) {
555    ValidationMessage validationMessage = new ValidationMessage(theSource, type, line, col, path, msg, theSeverity).setMessageId(id);
556    validationMessage.setRuleDate(ruleDate);
557    if (doingLevel(theSeverity) && checkMsgId(id, validationMessage)) {
558      errors.add(validationMessage);
559    }
560    return validationMessage;
561  }
562
563  public boolean checkMsgId(String id, ValidationMessage vm) { 
564    if (id != null && validationControl.containsKey(id)) {
565      ValidationControl control = validationControl.get(id);
566      if (control.level != null) {
567        vm.setLevel(control.level);
568      }
569      return control.isAllowed();
570    }
571    return true;
572  }
573
574  /**
575   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
576   * 
577   * @param thePass
578   *          Set this parameter to <code>false</code> if the validation does not pass
579   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
580   */
581  protected boolean txWarning(List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
582    if (!thePass && doingWarnings()) {
583      String nmsg = context.formatMessage(msg, theMessageArguments);
584      ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg);
585      vmsg.setRuleDate(ruleDate);
586      if (checkMsgId(msg, vmsg)) {
587        errors.add(vmsg);
588      }
589    }
590    return thePass;
591
592  }
593  
594  /**
595   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails. Also, keep track of it later in case we want to remove it if we find a required binding for this element later
596   * 
597   * @param thePass
598   *          Set this parameter to <code>false</code> if the validation does not pass
599   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
600   */
601  protected boolean txWarningForLaterRemoval(Object location, List<ValidationMessage> errors, String ruleDate, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
602    if (!thePass && doingWarnings()) {
603      String nmsg = context.formatMessage(msg, theMessageArguments);
604      ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg);
605      vmsg.setRuleDate(ruleDate);
606      if (checkMsgId(msg, vmsg)) {
607        errors.add(vmsg);
608      }
609      trackedMessages.add(new TrackedLocationRelatedMessage(location, vmsg));
610    }
611    return thePass;
612
613  }
614
615  protected void removeTrackedMessagesForLocation(List<ValidationMessage> errors, Object location, String path) {
616    List<TrackedLocationRelatedMessage> messages = new ArrayList<>();
617    for (TrackedLocationRelatedMessage m : trackedMessages) {
618      if (m.getLocation() == location) {
619        messages.add(m);
620        messagesToRemove.add(m.getVmsg());
621      }
622    }
623    trackedMessages.removeAll(messages);    
624  }
625  
626  protected boolean warningOrError(boolean isError, List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
627    if (!thePass) {
628      String nmsg = context.formatMessage(msg, theMessageArguments);
629      IssueSeverity lvl = isError ? IssueSeverity.ERROR : IssueSeverity.WARNING;
630      if (doingLevel(lvl)) {
631        addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, lvl, msg);
632      }
633    }
634    return thePass;
635
636  }
637
638  /**
639   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
640   * 
641   * @param thePass
642   *          Set this parameter to <code>false</code> if the validation does not pass
643   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
644   */
645  protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
646    if (!thePass && doingWarnings()) {
647      String path = toPath(pathParts);
648      String message = context.formatMessage(theMessage, theMessageArguments);
649      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, theMessage);
650    }
651    return thePass;
652  }
653
654  /**
655   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
656   * 
657   * @param thePass
658   *          Set this parameter to <code>false</code> if the validation does not pass
659   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
660   */
661  protected boolean warning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, Object... theMessageArguments) {
662    if (!thePass && doingWarnings()) {
663      String message = context.formatMessage(msg, theMessageArguments);
664      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.WARNING, null);
665    }
666    return thePass;
667  }
668
669  /**
670   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
671   * 
672   * @param thePass
673   *          Set this parameter to <code>false</code> if the validation does not pass
674   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
675   */
676  protected boolean warningOrHint(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, boolean warning, String msg, Object... theMessageArguments) {
677    if (!thePass) {
678      String message = context.formatMessage(msg, theMessageArguments);
679      IssueSeverity lvl = warning ? IssueSeverity.WARNING : IssueSeverity.INFORMATION;
680      if  (doingLevel(lvl)) {
681        addValidationMessage(errors, ruleDate, type, -1, -1, path, message, lvl, null);
682      }
683    }
684    return thePass;
685  }
686
687  /**
688   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
689   * 
690   * @param thePass
691   *          Set this parameter to <code>false</code> if the validation does not pass
692   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
693   */
694  protected boolean warningHtml(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) {
695    if (!thePass && doingWarnings()) {
696      addValidationMessage(errors, ruleDate, type, path, msg, html, IssueSeverity.WARNING, null);
697    }
698    return thePass;
699  }
700
701  /**
702   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
703   * 
704   * @param thePass
705   *          Set this parameter to <code>false</code> if the validation does not pass
706   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
707   */
708  protected boolean warningHtml(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) {
709    if (!thePass && doingWarnings()) {
710      String nmsg = context.formatMessage(msg, theMessageArguments);
711      addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.WARNING, msg);
712    }
713    return thePass;
714  }
715
716  //---------
717  /**
718   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
719   * 
720   * @param thePass
721   *          Set this parameter to <code>false</code> if the validation does not pass
722   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
723   */
724  protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
725    if (!thePass && doingWarnings()) { 
726      String nmsg = context.formatMessage(msg, theMessageArguments);
727      addValidationMessage(errors, ruleDate, type, line, col, path, nmsg, IssueSeverity.INFORMATION, msg);
728    }
729    return thePass;
730
731  }
732
733  /**
734   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
735   * 
736   * @param thePass
737   *          Set this parameter to <code>false</code> if the validation does not pass
738   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
739   */
740  protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
741    if (!thePass && doingWarnings()) {
742      String path = toPath(pathParts);
743      String message = context.formatMessage(theMessage, theMessageArguments);
744      addValidationMessage(errors, ruleDate, type, -1, -1, path, message, IssueSeverity.INFORMATION, theMessage);
745    }
746    return thePass;
747  }
748
749  /**
750   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
751   * 
752   * @param thePass
753   *          Set this parameter to <code>false</code> if the validation does not pass
754   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
755   */
756  protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg) {
757    if (!thePass && doingWarnings()) {
758      addValidationMessage(errors, ruleDate, type, -1, -1, path, msg, IssueSeverity.INFORMATION, null);
759    }
760    return thePass;
761  }
762
763  /**
764   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
765   * 
766   * @param thePass
767   *          Set this parameter to <code>false</code> if the validation does not pass
768   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
769   */
770  protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html) {
771    if (!thePass && doingWarnings()) {
772      IssueSeverity severity = IssueSeverity.INFORMATION;
773      addValidationMessage(errors, ruleDate, type, path, msg, html, severity, null);
774    }
775    return thePass;
776  }
777
778  protected void addValidationMessage(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, String msg, String html, IssueSeverity theSeverity, String id) {
779    ValidationMessage vm = new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity);
780    vm.setRuleDate(ruleDate);
781    if (checkMsgId(id, vm)) {
782      if (doingLevel(theSeverity)) {
783        errors.add(vm.setMessageId(id));
784      }
785    }
786  }
787
788  /**
789   * Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
790   * 
791   * @param thePass
792   *          Set this parameter to <code>false</code> if the validation does not pass
793   * @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
794   */
795  protected boolean suppressedwarning(List<ValidationMessage> errors, String ruleDate, IssueType type, String path, boolean thePass, String msg, String html, Object... theMessageArguments) {
796    if (!thePass && doingWarnings()) {
797      String nmsg = context.formatMessage(msg, theMessageArguments);
798      addValidationMessage(errors, ruleDate, type, path, nmsg, html, IssueSeverity.INFORMATION, msg);
799    }
800    return thePass;
801  }
802
803
804  protected ValueSet resolveBindingReference(DomainResource ctxt, String reference, String uri, Resource src) {
805    if (reference != null) {
806      if (reference.equals("http://www.rfc-editor.org/bcp/bcp13.txt")) {
807        reference = "http://hl7.org/fhir/ValueSet/mimetypes";
808      }
809      if (reference.startsWith("#")) {
810        for (Resource c : ctxt.getContained()) {
811          if (c.getId().equals(reference.substring(1)) && (c instanceof ValueSet))
812            return (ValueSet) c;
813        }
814        return null;
815      } else {
816        long t = System.nanoTime();
817        ValueSet fr = context.fetchResource(ValueSet.class, reference, src);
818        if (fr == null) {
819          if (!Utilities.isAbsoluteUrl(reference)) {
820            reference = resolve(uri, reference);
821            fr = context.fetchResource(ValueSet.class, reference, src);
822          }
823        }
824        if (fr == null) {
825          fr = ValueSetUtilities.generateImplicitValueSet(reference);
826        } 
827       
828        timeTracker.tx(t, "vs "+uri);
829        return fr;
830      }
831    } else
832      return null;
833  }
834
835
836  private String resolve(String uri, String ref) {
837    if (isBlank(uri)) {
838      return ref;
839    }
840    String[] up = uri.split("\\/");
841    String[] rp = ref.split("\\/");
842    if (context.getResourceNames().contains(up[up.length - 2]) && context.getResourceNames().contains(rp[0])) {
843      StringBuilder b = new StringBuilder();
844      for (int i = 0; i < up.length - 2; i++) {
845        b.append(up[i]);
846        b.append("/");
847      }
848      b.append(ref);
849      return b.toString();
850    } else
851      return ref;
852  }
853
854  protected String describeReference(String reference) {
855    if (reference == null)
856      return "null";
857    return reference;
858  }
859
860  protected Base resolveInBundle(String url, Element bnd) {
861    if (bnd == null)
862      return null;
863    if (bnd.fhirType().equals(BUNDLE)) {
864      for (Element be : bnd.getChildrenByName(ENTRY)) {
865        Element res = be.getNamedChild(RESOURCE);
866        if (res != null) {
867          String fullUrl = be.getChildValue(FULL_URL);
868          String rt = res.fhirType();
869          String id = res.getChildValue(ID);
870          if (url.equals(fullUrl))
871            return res;
872          if (url.equals(rt + "/" + id))
873            return res;
874        }
875      }
876    }
877    return null;
878  }
879
880  protected Element resolveInBundle(Element bundle, List<Element> entries, String ref, String fullUrl, String type, String id) {
881    @SuppressWarnings("unchecked")
882    Map<String, Element> map = (Map<String, Element>) bundle.getUserData("validator.entrymap");
883    if (map == null) {
884      map = new HashMap<>();
885      bundle.setUserData("validator.entrymap", map);
886      for (Element entry : entries) {
887        String fu = entry.getNamedChildValue(FULL_URL);
888        map.put(fu,  entry);
889        Element resource = entry.getNamedChild(RESOURCE);
890        if (resource != null) {
891          String et = resource.getType();
892          String eid = resource.getNamedChildValue(ID);
893          map.put(et+"/"+eid,  entry);
894        }
895      }      
896    }
897    
898    if (Utilities.isAbsoluteUrl(ref)) {
899      // if the reference is absolute, then you resolve by fullUrl. No other thinking is required.
900      return map.get(ref);
901//      for (Element entry : entries) {
902//        String fu = entry.getNamedChildValue(FULL_URL);
903//        if (ref.equals(fu))
904//          return entry;
905//      }
906//      return null;
907    } else {
908      // split into base, type, and id
909      String u = null;
910      if (fullUrl != null && fullUrl.endsWith(type + "/" + id))
911        // fullUrl = complex
912        u = fullUrl.substring(0, fullUrl.length() - (type + "/" + id).length()) + ref;
913//        u = fullUrl.substring((type+"/"+id).length())+ref;
914      String[] parts = ref.split("\\/");
915      if (parts.length >= 2) {
916        String t = parts[0];
917        String i = parts[1];
918        Element res = map.get(u);
919        if (res == null) {
920          res = map.get(t+"/"+i);
921        }
922        return res;
923//        for (Element entry : entries) {
924//          String fu = entry.getNamedChildValue(FULL_URL);
925//          if (fu != null && fu.equals(u))
926//            return entry;
927//          if (u == null) {
928//            Element resource = entry.getNamedChild(RESOURCE);
929//            if (resource != null) {
930//              String et = resource.getType();
931//              String eid = resource.getNamedChildValue(ID);
932//              if (t.equals(et) && i.equals(eid))
933//                return entry;
934//            }
935//          }
936//        }
937      }
938      return null;
939    }
940  }
941
942
943  protected IndexedElement getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path, String type, boolean isTransaction) {
944    String targetUrl = null;
945    String version = "";
946    String resourceType = null;
947    if (ref.startsWith("http:") || ref.startsWith("urn:") || Utilities.isAbsoluteUrl(ref)) {
948      // We've got an absolute reference, no need to calculate
949      if (ref.contains("/_history/")) {
950        targetUrl = ref.substring(0, ref.indexOf("/_history/") - 1);
951        version = ref.substring(ref.indexOf("/_history/") + 10);
952      } else
953        targetUrl = ref;
954
955    } else if (fullUrl == null) {
956      //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
957      // but this rule doesn't apply for batches or transactions
958      rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, Utilities.existsInList(type, "batch-response", "transaction-response") || path.startsWith("Bundle.signature"), I18nConstants.BUNDLE_BUNDLE_FULLURL_MISSING);
959      return null;
960
961    } else if (ref.split("/").length != 2 && ref.split("/").length != 4) {
962      if (isTransaction) {
963        rule(errors, NO_RULE_DATE, IssueType.INVALID, -1, -1, path, isSearchUrl(ref), I18nConstants.REFERENCE_REF_FORMAT1, ref);
964      } else {
965        rule(errors, NO_RULE_DATE, IssueType.INVALID, -1, -1, path, false, I18nConstants.REFERENCE_REF_FORMAT2, ref);
966      }
967      return null;
968
969    } else {
970      String base = "";
971      if (fullUrl.startsWith("urn")) {
972        String[] parts = fullUrl.split("\\:");
973        for (int i = 0; i < parts.length - 1; i++) {
974          base = base + parts[i] + ":";
975        }
976      } else {
977        String[] parts;
978        parts = fullUrl.split("/");
979        for (int i = 0; i < parts.length - 2; i++) {
980          base = base + parts[i] + "/";
981        }
982      }
983
984      String id = null;
985      if (ref.contains("/_history/")) {
986        version = ref.substring(ref.indexOf("/_history/") + 10);
987        String[] refBaseParts = ref.substring(0, ref.indexOf("/_history/")).split("/");
988        resourceType = refBaseParts[0];
989        id = refBaseParts[1];
990      } else if (base.startsWith("urn")) {
991        resourceType = ref.split("/")[0];
992        id = ref.split("/")[1];
993      } else
994        id = ref;
995
996      targetUrl = base + id;
997    }
998
999    List<Element> entries = new ArrayList<Element>();
1000    bundle.getNamedChildren(ENTRY, entries);
1001    Element match = null;
1002    int matchIndex = -1;
1003    for (int i = 0; i < entries.size(); i++) {
1004      Element we = entries.get(i);
1005      if (targetUrl.equals(we.getChildValue(FULL_URL))) {
1006        Element r = we.getNamedChild(RESOURCE);
1007        if (version.isEmpty()) {
1008          rule(errors, NO_RULE_DATE, IssueType.FORBIDDEN, -1, -1, path, match == null, I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ref);
1009          match = r;
1010          matchIndex = i;
1011        } else {
1012          try {
1013            if (version.equals(r.getChildren(META).get(0).getChildValue("versionId"))) {
1014              rule(errors, NO_RULE_DATE, IssueType.FORBIDDEN, -1, -1, path, match == null, I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ref);
1015              match = r;
1016              matchIndex = i;
1017            }
1018          } catch (Exception e) {
1019            warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, r.getChildren(META).size() == 1 && r.getChildren(META).get(0).getChildValue("versionId") != null, I18nConstants.BUNDLE_BUNDLE_FULLURL_NEEDVERSION, targetUrl);
1020            // If one of these things is null
1021          }
1022        }
1023      }
1024    }
1025
1026    if (match != null && resourceType != null)
1027      rule(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, match.getType().equals(resourceType), I18nConstants.REFERENCE_REF_RESOURCETYPE, ref, match.getType());
1028    if (match == null) {
1029      warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, !ref.startsWith("urn"), I18nConstants.BUNDLE_BUNDLE_NOT_LOCAL, ref);
1030      if (!Utilities.isAbsoluteUrl(ref)) {
1031        String[] p = ref.split("\\/");
1032        List<Element> ml = new ArrayList<>();
1033        if (p.length >= 2 && Utilities.existsInList(p[0], context.getResourceNames()) && Utilities.isValidId(p[1])) {
1034          for (int i = 0; i < entries.size(); i++) {
1035            Element we = entries.get(i);
1036            Element r = we.getNamedChild(RESOURCE);
1037            if (r != null && p[0].equals(r.fhirType()) && p[1].equals(r.getNamedChildValue("id")) ) {
1038              ml.add(we);
1039            }
1040          }          
1041        }
1042        if (ml.size() > 1) {
1043          warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_POSSSIBLE_MATCHES, ref, targetUrl);          
1044        }
1045        for (Element e : ml) {
1046          String fu = e.getChildValue(FULL_URL);
1047          int i = entries.indexOf(e);
1048          if (fu == null) {
1049            warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_BUNDLE_POSSIBLE_MATCH_NO_FU, i, ref, targetUrl);
1050          } else {
1051            warning(errors, NO_RULE_DATE, IssueType.REQUIRED, -1, -1, path, false, I18nConstants.BUNDLE_BUNDLE_POSSIBLE_MATCH_WRONG_FU, i, ref, fu, targetUrl);            
1052          }
1053        }
1054      }
1055    }
1056    return match == null ? null : new IndexedElement(matchIndex, match, entries.get(matchIndex));
1057  }
1058
1059  private boolean isSearchUrl(String ref) {
1060    if (Utilities.noString(ref) || !ref.contains("?")) {
1061      return false;
1062    }
1063    String tn = ref.substring(0, ref.indexOf("?"));
1064    String q = ref.substring(ref.indexOf("?") + 1);
1065    if (!context.getResourceNames().contains(tn)) {
1066      return false;
1067    } else {
1068      return q.matches("([_a-zA-Z][_a-zA-Z0-9]*=[^=&]+)(&([_a-zA-Z][_a-zA-Z0-9]*=[^=&]+))*");
1069    }
1070  }
1071
1072  public Map<String, ValidationControl> getValidationControl() {
1073    return validationControl;
1074  }
1075
1076  public XVerExtensionStatus xverStatus(String url) {
1077    return xverManager.status(url);
1078  }
1079
1080  public boolean isXverUrl(String url) {
1081    return xverManager.matchingUrl(url);    
1082  }
1083  
1084  public StructureDefinition xverDefn(String url) {
1085    return xverManager.makeDefinition(url);
1086  }
1087  
1088  public String xverVersion(String url) {
1089    return xverManager.getVersion(url);
1090  }
1091
1092  public String xverElementId(String url) {
1093    return xverManager.getElementId(url);
1094  }
1095
1096  public StructureDefinition getXverExt(StructureDefinition profile, List<ValidationMessage> errors, String url) {
1097    if (isXverUrl(url)) {
1098      switch (xverStatus(url)) {
1099        case BadVersion:
1100          rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverVersion(url));
1101          return null;
1102        case Unknown:
1103          rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverElementId(url));
1104          return null;
1105        case Invalid:
1106          rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverElementId(url));
1107          return null;
1108        case Valid:
1109          StructureDefinition defn = xverDefn(url);
1110          new ContextUtilities(context).generateSnapshot(defn);
1111          context.cacheResource(defn);
1112          return defn;
1113        default:
1114          rule(errors, NO_RULE_DATE, IssueType.INVALID, profile.getId(), false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url);
1115          return null;
1116      }
1117    } else {
1118      return null;      
1119    }
1120  }
1121  
1122  public StructureDefinition getXverExt(List<ValidationMessage> errors, String path, Element element, String url) {
1123    if (isXverUrl(url)) {
1124      switch (xverStatus(url)) {
1125      case BadVersion:
1126        rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALID, url, xverVersion(url));
1127        break;
1128      case Unknown:
1129        rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INVALIDID, url, xverElementId(url));
1130        break;
1131      case Invalid:
1132        rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_NOCHANGE, url, xverElementId(url));
1133        break;
1134      case Valid:
1135        StructureDefinition ex = xverDefn(url);
1136        new ContextUtilities(context).generateSnapshot(ex);
1137        context.cacheResource(ex);
1138        return ex;
1139      default:
1140        rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, I18nConstants.EXTENSION_EXT_VERSION_INTERNAL, url);
1141        break;
1142      }
1143    }
1144    return null;
1145  }
1146  
1147  protected String versionFromCanonical(String system) {
1148    if (system == null) {
1149      return null;
1150    } else if (system.contains("|")) {
1151      return system.substring(0, system.indexOf("|"));
1152    } else {
1153      return system;
1154    }
1155  }
1156
1157  protected String systemFromCanonical(String system) {
1158    if (system == null) {
1159      return null;
1160    } else if (system.contains("|")) {
1161      return system.substring(system.indexOf("|")+1);
1162    } else {
1163      return system;
1164    }
1165  }
1166  
1167  @Override
1168  public Resource loadContainedResource(List<ValidationMessage> errors, String path, Element resource, String id, Class<? extends Resource> class1) throws FHIRException {
1169    for (Element contained : resource.getChildren("contained")) {
1170      if (contained.getIdBase().equals(id)) {
1171        return loadFoundResource(errors, path, contained, class1);
1172      }
1173    }
1174    return null;
1175  }
1176  
1177  protected Resource loadFoundResource(List<ValidationMessage> errors, String path, Element resource, Class<? extends Resource> class1) throws FHIRException {
1178    try {
1179      FhirPublication v = FhirPublication.fromCode(context.getVersion());
1180      ByteArrayOutputStream bs = new ByteArrayOutputStream();
1181      new JsonParser(context).compose(resource, bs, OutputStyle.NORMAL, resource.getIdBase());
1182      byte[] json = bs.toByteArray();
1183      Resource r5 = null;
1184      switch (v) {
1185      case DSTU1:
1186        rule(errors, NO_RULE_DATE, IssueType.INVALID, resource.line(), resource.col(), path, false, I18nConstants.UNSUPPORTED_VERSION_R1, resource.getIdBase());
1187        return null; // this can't happen
1188      case DSTU2:
1189        org.hl7.fhir.dstu2.model.Resource r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(json);
1190        r5 = VersionConvertorFactory_10_50.convertResource(r2);
1191        break;
1192      case DSTU2016May:
1193        org.hl7.fhir.dstu2016may.model.Resource r2a = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(json);
1194        r5 = VersionConvertorFactory_14_50.convertResource(r2a);
1195        break;
1196      case STU3:
1197        org.hl7.fhir.dstu3.model.Resource r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(json);
1198        r5 = VersionConvertorFactory_30_50.convertResource(r3);
1199        break;
1200      case R4:
1201        org.hl7.fhir.r4.model.Resource r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(json);
1202        r5 = VersionConvertorFactory_40_50.convertResource(r4);
1203        break;
1204      case R5:
1205        r5 = new org.hl7.fhir.r5.formats.JsonParser().parse(json);
1206        break;
1207      default:
1208        return null; // this can't happen
1209      }
1210      if (class1.isInstance(r5))
1211        return (Resource) r5;
1212      else {
1213        rule(errors, NO_RULE_DATE, IssueType.INVALID, resource.line(), resource.col(), path, false, I18nConstants.REFERENCE_REF_WRONGTARGET_LOAD, resource.getIdBase(), class1.toString(), r5.fhirType());
1214        return null;
1215      }
1216
1217    } catch (IOException e) {
1218      throw new FHIRException(e);
1219    }
1220  }
1221
1222  public void setLevel(ValidationLevel level) {
1223    this.level = level;
1224  }
1225
1226  public ValidationLevel getLevel() {
1227    return level;
1228  }
1229
1230  protected boolean isHL7(Element cr) {
1231    String url = cr.getChildValue("url");
1232    return url != null && url.contains("hl7");
1233  }
1234}