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