001package org.hl7.fhir.utilities.validation;
002
003import java.text.ParseException;
004import java.text.SimpleDateFormat;
005
006/*
007  Copyright (c) 2011+, HL7, Inc.
008  All rights reserved.
009
010  Redistribution and use in source and binary forms, with or without modification, 
011  are permitted provided that the following conditions are met:
012
013 * Redistributions of source code must retain the above copyright notice, this 
014     list of conditions and the following disclaimer.
015 * Redistributions in binary form must reproduce the above copyright notice, 
016     this list of conditions and the following disclaimer in the documentation 
017     and/or other materials provided with the distribution.
018 * Neither the name of HL7 nor the names of its contributors may be used to 
019     endorse or promote products derived from this software without specific 
020     prior written permission.
021
022  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
023  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
024  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
025  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
026  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
027  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
028  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
029  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
030  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
031  POSSIBILITY OF SUCH DAMAGE.
032
033 */
034
035
036
037/*
038 Copyright (c) 2011+, HL7, Inc
039 All rights reserved.
040
041 Redistribution and use in source and binary forms, with or without modification, 
042 are permitted provided that the following conditions are met:
043
044 * Redistributions of source code must retain the above copyright notice, this 
045 list of conditions and the following disclaimer.
046 * Redistributions in binary form must reproduce the above copyright notice, 
047 this list of conditions and the following disclaimer in the documentation 
048 and/or other materials provided with the distribution.
049 * Neither the name of HL7 nor the names of its contributors may be used to 
050 endorse or promote products derived from this software without specific 
051 prior written permission.
052
053 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
054 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
055 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
056 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
057 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
058 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
059 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
060 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
061 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
062 POSSIBILITY OF SUCH DAMAGE.
063
064 */
065
066import java.util.Comparator;
067import java.util.Date;
068import java.util.EnumMap;
069
070import org.apache.commons.lang3.builder.ToStringBuilder;
071import org.apache.commons.lang3.builder.ToStringStyle;
072import org.hl7.fhir.exceptions.FHIRException;
073import org.hl7.fhir.utilities.Utilities;
074
075public class ValidationMessage implements Comparator<ValidationMessage>, Comparable<ValidationMessage>
076{
077  public enum Source {
078    ExampleValidator, 
079    ProfileValidator, 
080    ResourceValidator, 
081    InstanceValidator,
082    Template,
083    Schema, 
084    Schematron, 
085    Publisher, 
086    LinkChecker,
087    Ontology, 
088    ProfileComparer, 
089    TerminologyEngine,
090    QuestionnaireResponseValidator, 
091    IPAValidator
092  }
093
094  public enum IssueSeverity {
095    /**
096     * The issue caused the action to fail, and no further checking could be performed.
097     */
098    FATAL, 
099    /**
100     * The issue is sufficiently important to cause the action to fail.
101     */
102    ERROR, 
103    /**
104     * The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.
105     */
106    WARNING, 
107    /**
108     * The issue has no relation to the degree of success of the action.
109     */
110    INFORMATION, 
111    /**
112     * added to help the parsers with the generic types
113     */
114    NULL;
115    public static IssueSeverity fromCode(String codeString) throws FHIRException {
116      if (codeString == null || "".equals(codeString))
117        return null;
118      if ("fatal".equals(codeString))
119        return FATAL;
120      if ("error".equals(codeString))
121        return ERROR;
122      if ("warning".equals(codeString))
123        return WARNING;
124      if ("information".equals(codeString))
125        return INFORMATION;
126      else
127        throw new FHIRException("Unknown IssueSeverity code '"+codeString+"'");
128    }
129    public String toCode() {
130      switch (this) {
131      case FATAL: return "fatal";
132      case ERROR: return "error";
133      case WARNING: return "warning";
134      case INFORMATION: return "information";
135      case NULL: return null;
136      default: return "?";
137      }
138    }
139    public String getSystem() {
140      switch (this) {
141      case FATAL: return "http://hl7.org/fhir/issue-severity";
142      case ERROR: return "http://hl7.org/fhir/issue-severity";
143      case WARNING: return "http://hl7.org/fhir/issue-severity";
144      case INFORMATION: return "http://hl7.org/fhir/issue-severity";
145      case NULL: return null;
146      default: return "?";
147      }
148    }
149    public String getDefinition() {
150      switch (this) {
151      case FATAL: return "The issue caused the action to fail, and no further checking could be performed.";
152      case ERROR: return "The issue is sufficiently important to cause the action to fail.";
153      case WARNING: return "The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired.";
154      case INFORMATION: return "The issue has no relation to the degree of success of the action.";
155      case NULL: return null;
156      default: return "?";
157      }
158    }
159    public String getDisplay() {
160      switch (this) {
161      case FATAL: return "Fatal";
162      case ERROR: return "Error";
163      case WARNING: return "Warning";
164      case INFORMATION: return "Information";
165      case NULL: return null;
166      default: return "?";
167      }
168    }
169    public boolean isError() {
170      return this == FATAL || this == ERROR;
171    }
172    public boolean isHint() {
173      return this == INFORMATION;
174    }
175  }
176
177  public enum IssueType {
178    /**
179     * Content invalid against the specification or a profile.
180     */
181    INVALID, 
182    DELETED,
183    /**
184     * A structural issue in the content such as wrong namespace, or unable to parse the content completely, or invalid json syntax.
185     */
186    STRUCTURE, 
187    /**
188     * A required element is missing.
189     */
190    REQUIRED, 
191    /**
192     * An element value is invalid.
193     */
194    VALUE, 
195    /**
196     * A content validation rule failed - e.g. a schematron rule.
197     */
198    INVARIANT, 
199    /**
200     * An authentication/authorization/permissions issue of some kind.
201     */
202    SECURITY, 
203    /**
204     * The client needs to initiate an authentication process.
205     */
206    LOGIN, 
207    /**
208     * The user or system was not able to be authenticated (either there is no process, or the proferred token is unacceptable).
209     */
210    MULTIPLEMATCHES,
211    UNKNOWN, 
212    /**
213     * User session expired; a login may be required.
214     */
215    EXPIRED, 
216    /**
217     * The user does not have the rights to perform this action.
218     */
219    FORBIDDEN, 
220    /**
221     * Some information was not or may not have been returned due to business rules, consent or privacy rules, or access permission constraints.  This information may be accessible through alternate processes.
222     */
223    SUPPRESSED, 
224    /**
225     * Processing issues. These are expected to be final e.g. there is no point resubmitting the same content unchanged.
226     */
227    PROCESSING, 
228    /**
229     * The resource or profile is not supported.
230     */
231    NOTSUPPORTED, 
232    /**
233     * An attempt was made to create a duplicate record.
234     */
235    DUPLICATE, 
236    /**
237     * The reference provided was not found. In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the content is not found further into the application architecture.
238     */
239    NOTFOUND, 
240    /**
241     * Provided content is too long (typically, this is a denial of service protection type of error).
242     */
243    TOOLONG, 
244    /**
245     * The code or system could not be understood, or it was not valid in the context of a particular ValueSet.code.
246     */
247    CODEINVALID, 
248    /**
249     * An extension was found that was not acceptable, could not be resolved, or a modifierExtension was not recognized.
250     */
251    EXTENSION, 
252    /**
253     * The operation was stopped to protect server resources; e.g. a request for a value set expansion on all of SNOMED CT.
254     */
255    TOOCOSTLY, 
256    /**
257     * The content/operation failed to pass some business rule, and so could not proceed.
258     */
259    BUSINESSRULE, 
260    /**
261     * Content could not be accepted because of an edit conflict (i.e. version aware updates) (In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the conflict is discovered further into the application architecture.)
262     */
263    CONFLICT, 
264    /**
265     * Not all data sources typically accessed could be reached, or responded in time, so the returned information may not be complete.
266     */
267    INCOMPLETE, 
268    /**
269     * Transient processing issues. The system receiving the error may be able to resubmit the same content once an underlying issue is resolved.
270     */
271    TRANSIENT, 
272    /**
273     * A resource/record locking failure (usually in an underlying database).
274     */
275    LOCKERROR, 
276    /**
277     * The persistent store is unavailable; e.g. the database is down for maintenance or similar action.
278     */
279    NOSTORE, 
280    /**
281     * An unexpected internal error has occurred.
282     */
283    EXCEPTION, 
284    /**
285     * An internal timeout has occurred.
286     */
287    TIMEOUT, 
288    /**
289     * The system is not prepared to handle this request due to load management.
290     */
291    THROTTLED, 
292    /**
293     * A message unrelated to the processing success of the completed operation (examples of the latter include things like reminders of password expiry, system maintenance times, etc.).
294     */
295    INFORMATIONAL, 
296    /**
297     * added to help the parsers with the generic types
298     */
299    NULL;
300    public static IssueType fromCode(String codeString) throws FHIRException {
301      if (codeString == null || "".equals(codeString))
302        return null;
303      if ("invalid".equals(codeString))
304        return INVALID;
305      if ("structure".equals(codeString))
306        return STRUCTURE;
307      if ("required".equals(codeString))
308        return REQUIRED;
309      if ("value".equals(codeString))
310        return VALUE;
311      if ("invariant".equals(codeString))
312        return INVARIANT;
313      if ("security".equals(codeString))
314        return SECURITY;
315      if ("login".equals(codeString))
316        return LOGIN;
317      if ("unknown".equals(codeString))
318        return UNKNOWN;
319      if ("expired".equals(codeString))
320        return EXPIRED;
321      if ("forbidden".equals(codeString))
322        return FORBIDDEN;
323      if ("suppressed".equals(codeString))
324        return SUPPRESSED;
325      if ("processing".equals(codeString))
326        return PROCESSING;
327      if ("not-supported".equals(codeString))
328        return NOTSUPPORTED;
329      if ("duplicate".equals(codeString))
330        return DUPLICATE;
331      if ("not-found".equals(codeString))
332        return NOTFOUND;
333      if ("too-long".equals(codeString))
334        return TOOLONG;
335      if ("code-invalid".equals(codeString))
336        return CODEINVALID;
337      if ("extension".equals(codeString))
338        return EXTENSION;
339      if ("too-costly".equals(codeString))
340        return TOOCOSTLY;
341      if ("business-rule".equals(codeString))
342        return BUSINESSRULE;
343      if ("conflict".equals(codeString))
344        return CONFLICT;
345      if ("incomplete".equals(codeString))
346        return INCOMPLETE;
347      if ("transient".equals(codeString))
348        return TRANSIENT;
349      if ("lock-error".equals(codeString))
350        return LOCKERROR;
351      if ("no-store".equals(codeString))
352        return NOSTORE;
353      if ("exception".equals(codeString))
354        return EXCEPTION;
355      if ("timeout".equals(codeString))
356        return TIMEOUT;
357      if ("throttled".equals(codeString))
358        return THROTTLED;
359      if ("informational".equals(codeString))
360        return INFORMATIONAL;
361      else
362        throw new FHIRException("Unknown IssueType code '"+codeString+"'");
363    }
364    public String toCode() {
365      switch (this) {
366      case INVALID: return "invalid";
367      case STRUCTURE: return "structure";
368      case REQUIRED: return "required";
369      case VALUE: return "value";
370      case INVARIANT: return "invariant";
371      case SECURITY: return "security";
372      case LOGIN: return "login";
373      case UNKNOWN: return "unknown";
374      case EXPIRED: return "expired";
375      case FORBIDDEN: return "forbidden";
376      case SUPPRESSED: return "suppressed";
377      case PROCESSING: return "processing";
378      case NOTSUPPORTED: return "not-supported";
379      case DUPLICATE: return "duplicate";
380      case NOTFOUND: return "not-found";
381      case TOOLONG: return "too-long";
382      case CODEINVALID: return "code-invalid";
383      case EXTENSION: return "extension";
384      case TOOCOSTLY: return "too-costly";
385      case BUSINESSRULE: return "business-rule";
386      case CONFLICT: return "conflict";
387      case INCOMPLETE: return "incomplete";
388      case TRANSIENT: return "transient";
389      case LOCKERROR: return "lock-error";
390      case NOSTORE: return "no-store";
391      case EXCEPTION: return "exception";
392      case TIMEOUT: return "timeout";
393      case THROTTLED: return "throttled";
394      case INFORMATIONAL: return "informational";
395      case NULL: return null;
396      default: return "?";
397      }
398    }
399    public String getSystem() {
400      switch (this) {
401      case INVALID: return "http://hl7.org/fhir/issue-type";
402      case STRUCTURE: return "http://hl7.org/fhir/issue-type";
403      case REQUIRED: return "http://hl7.org/fhir/issue-type";
404      case VALUE: return "http://hl7.org/fhir/issue-type";
405      case INVARIANT: return "http://hl7.org/fhir/issue-type";
406      case SECURITY: return "http://hl7.org/fhir/issue-type";
407      case LOGIN: return "http://hl7.org/fhir/issue-type";
408      case UNKNOWN: return "http://hl7.org/fhir/issue-type";
409      case EXPIRED: return "http://hl7.org/fhir/issue-type";
410      case FORBIDDEN: return "http://hl7.org/fhir/issue-type";
411      case SUPPRESSED: return "http://hl7.org/fhir/issue-type";
412      case PROCESSING: return "http://hl7.org/fhir/issue-type";
413      case NOTSUPPORTED: return "http://hl7.org/fhir/issue-type";
414      case DUPLICATE: return "http://hl7.org/fhir/issue-type";
415      case NOTFOUND: return "http://hl7.org/fhir/issue-type";
416      case TOOLONG: return "http://hl7.org/fhir/issue-type";
417      case CODEINVALID: return "http://hl7.org/fhir/issue-type";
418      case EXTENSION: return "http://hl7.org/fhir/issue-type";
419      case TOOCOSTLY: return "http://hl7.org/fhir/issue-type";
420      case BUSINESSRULE: return "http://hl7.org/fhir/issue-type";
421      case CONFLICT: return "http://hl7.org/fhir/issue-type";
422      case INCOMPLETE: return "http://hl7.org/fhir/issue-type";
423      case TRANSIENT: return "http://hl7.org/fhir/issue-type";
424      case LOCKERROR: return "http://hl7.org/fhir/issue-type";
425      case NOSTORE: return "http://hl7.org/fhir/issue-type";
426      case EXCEPTION: return "http://hl7.org/fhir/issue-type";
427      case TIMEOUT: return "http://hl7.org/fhir/issue-type";
428      case THROTTLED: return "http://hl7.org/fhir/issue-type";
429      case INFORMATIONAL: return "http://hl7.org/fhir/issue-type";
430      case NULL: return null;
431      default: return "?";
432      }
433    }
434    public String getDefinition() {
435      switch (this) {
436      case INVALID: return "Content invalid against the specification or a profile.";
437      case STRUCTURE: return "A structural issue in the content such as wrong namespace, or unable to parse the content completely, or invalid json syntax.";
438      case REQUIRED: return "A required element is missing.";
439      case VALUE: return "An element value is invalid.";
440      case INVARIANT: return "A content validation rule failed - e.g. a schematron rule.";
441      case SECURITY: return "An authentication/authorization/permissions issue of some kind.";
442      case LOGIN: return "The client needs to initiate an authentication process.";
443      case UNKNOWN: return "The user or system was not able to be authenticated (either there is no process, or the proferred token is unacceptable).";
444      case EXPIRED: return "User session expired; a login may be required.";
445      case FORBIDDEN: return "The user does not have the rights to perform this action.";
446      case SUPPRESSED: return "Some information was not or may not have been returned due to business rules, consent or privacy rules, or access permission constraints.  This information may be accessible through alternate processes.";
447      case PROCESSING: return "Processing issues. These are expected to be final e.g. there is no point resubmitting the same content unchanged.";
448      case NOTSUPPORTED: return "The resource or profile is not supported.";
449      case DUPLICATE: return "An attempt was made to create a duplicate record.";
450      case NOTFOUND: return "The reference provided was not found. In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the content is not found further into the application architecture.";
451      case TOOLONG: return "Provided content is too long (typically, this is a denial of service protection type of error).";
452      case CODEINVALID: return "The code or system could not be understood, or it was not valid in the context of a particular ValueSet.code.";
453      case EXTENSION: return "An extension was found that was not acceptable, could not be resolved, or a modifierExtension was not recognized.";
454      case TOOCOSTLY: return "The operation was stopped to protect server resources; e.g. a request for a value set expansion on all of SNOMED CT.";
455      case BUSINESSRULE: return "The content/operation failed to pass some business rule, and so could not proceed.";
456      case CONFLICT: return "Content could not be accepted because of an edit conflict (i.e. version aware updates) (In a pure RESTful environment, this would be an HTTP 404 error, but this code may be used where the conflict is discovered further into the application architecture.)";
457      case INCOMPLETE: return "Not all data sources typically accessed could be reached, or responded in time, so the returned information may not be complete.";
458      case TRANSIENT: return "Transient processing issues. The system receiving the error may be able to resubmit the same content once an underlying issue is resolved.";
459      case LOCKERROR: return "A resource/record locking failure (usually in an underlying database).";
460      case NOSTORE: return "The persistent store is unavailable; e.g. the database is down for maintenance or similar action.";
461      case EXCEPTION: return "An unexpected internal error has occurred.";
462      case TIMEOUT: return "An internal timeout has occurred.";
463      case THROTTLED: return "The system is not prepared to handle this request due to load management.";
464      case INFORMATIONAL: return "A message unrelated to the processing success of the completed operation (examples of the latter include things like reminders of password expiry, system maintenance times, etc.).";
465      case NULL: return null;
466      default: return "?";
467      }
468    }
469    public String getDisplay() {
470      switch (this) {
471      case INVALID: return "Invalid Content";
472      case STRUCTURE: return "Structural Issue";
473      case REQUIRED: return "Required element missing";
474      case VALUE: return "Element value invalid";
475      case INVARIANT: return "Validation rule failed";
476      case SECURITY: return "Security Problem";
477      case LOGIN: return "Login Required";
478      case UNKNOWN: return "Unknown User";
479      case EXPIRED: return "Session Expired";
480      case FORBIDDEN: return "Forbidden";
481      case SUPPRESSED: return "Information  Suppressed";
482      case PROCESSING: return "Processing Failure";
483      case NOTSUPPORTED: return "Content not supported";
484      case DUPLICATE: return "Duplicate";
485      case NOTFOUND: return "Not Found";
486      case TOOLONG: return "Content Too Long";
487      case CODEINVALID: return "Invalid Code";
488      case EXTENSION: return "Unacceptable Extension";
489      case TOOCOSTLY: return "Operation Too Costly";
490      case BUSINESSRULE: return "Business Rule Violation";
491      case CONFLICT: return "Edit Version Conflict";
492      case INCOMPLETE: return "Incomplete Results";
493      case TRANSIENT: return "Transient Issue";
494      case LOCKERROR: return "Lock Error";
495      case NOSTORE: return "No Store Available";
496      case EXCEPTION: return "Exception";
497      case TIMEOUT: return "Timeout";
498      case THROTTLED: return "Throttled";
499      case INFORMATIONAL: return "Informational Note";
500      case NULL: return null;
501      default: return "?";
502      }
503    }
504  }
505
506
507  private Source source;
508  private int line;
509  private int col;
510  private String location; // fhirPath
511  private String message;
512  private String messageId; // source, for grouping
513  private IssueType type;
514  private IssueSeverity level;
515  private String html;
516  private String locationLink;
517  private String txLink;
518  public String sliceHtml;
519  public String[] sliceText;
520  private boolean slicingHint;
521  private boolean signpost;
522  private boolean criticalSignpost;
523  private Date ruleDate;
524  public static final String NO_RULE_DATE = null;
525
526
527  /**
528   * Constructor
529   */
530  public ValidationMessage() {
531    // nothing
532  }
533
534  public ValidationMessage(Source source, IssueType type, String path, String message, IssueSeverity level) {
535    this();
536    this.line = -1;
537    this.col = -1;
538    this.location = path;
539    if (message == null)
540      throw new Error("message is null");
541    this.message = message;
542    this.html = Utilities.escapeXml(message);
543    this.level = level;
544    this.source = source;
545    this.type = type;
546    if (level == IssueSeverity.NULL)
547      determineLevel(path);
548    if (type == null)
549      throw new Error("A type must be provided");
550  }
551
552  public ValidationMessage(Source source, IssueType type, int line, int col, String path, String message, IssueSeverity level) {
553    this();
554    this.line = line;
555    this.col = col;
556    this.location = path;
557    this.message = message;
558    this.html = Utilities.escapeXml(message);
559    this.level = level;
560    this.source = source;
561    this.type = type;
562    if (level == IssueSeverity.NULL)
563      determineLevel(path);
564    if (type == null)
565      throw new Error("A type must be provided");
566  }
567
568  public ValidationMessage(Source source, IssueType type, String path, String message, String html, IssueSeverity level) {
569    this();
570    this.line = -1;
571    this.col = -1;
572    this.location = path;
573    if (message == null)
574      throw new Error("message is null");
575    this.message = message;
576    this.html = html;
577    this.level = level;
578    this.source = source;
579    this.type = type;
580    if (level == IssueSeverity.NULL)
581      determineLevel(path);
582    if (type == null)
583      throw new Error("A type must be provided");
584  }
585
586  public ValidationMessage(Source source, IssueType type, int line, int col, String path, String message, String html, IssueSeverity level) {
587    this();
588    this.line = line;
589    this.col = col;
590    this.location = path;
591    if (message == null)
592      throw new Error("message is null");
593    this.message = message;
594    this.html = html;
595    this.level = level;
596    this.source = source;
597    this.type = type;
598    if (level == IssueSeverity.NULL)
599      determineLevel(path);
600    if (type == null)
601      throw new Error("A type must be provided");
602  }
603
604  private IssueSeverity determineLevel(String path) {
605    if (isGrandfathered(path))
606      return IssueSeverity.WARNING;
607    else
608      return IssueSeverity.ERROR;
609  }
610
611  private boolean isGrandfathered(String path) {
612    if (path.startsWith("xds-documentmanifest."))
613      return true;
614    if (path.startsWith("observation-device-metric-devicemetricobservation."))
615      return true;
616    if (path.startsWith("medicationadministration-immunization-vaccine."))
617      return true;
618    if (path.startsWith("elementdefinition-de-dataelement."))
619      return true;
620    if (path.startsWith("dataelement-sdc-sdcelement."))
621      return true;
622    if (path.startsWith("questionnaireresponse-sdc-structureddatacaptureanswers."))
623      return true;
624    if (path.startsWith("valueset-sdc-structureddatacapturevalueset."))
625      return true;
626    if (path.startsWith("dataelement-sdc-de-sdcelement."))
627      return true;
628    if (path.startsWith("do-uslab-uslabdo."))
629      return true;
630    if (path.startsWith("."))
631      return true;
632    if (path.startsWith("."))
633      return true;
634    if (path.startsWith("."))
635      return true;
636    if (path.startsWith("."))
637      return true;
638
639    return false;
640  }
641
642  public String getMessage() {
643    return message;
644  }
645  public ValidationMessage setMessage(String message) {
646    this.message = message;
647    return this;
648  }
649
650  public IssueSeverity getLevel() {
651    return level;
652  }
653  public ValidationMessage setLevel(IssueSeverity level) {
654    this.level = level;
655    return this;
656  }
657
658  public Source getSource() {
659    return source;
660  }
661  public ValidationMessage setSource(Source source) {
662    this.source = source;
663    return this;
664  }
665
666  public int getLine() {
667    return line;
668  }
669
670  public void setLine(int theLine) {
671    line = theLine;
672  }
673
674  public int getCol() {
675    return col;
676  }
677
678  public void setCol(int theCol) {
679    col = theCol;
680  }
681
682  public String getLocation() {
683    return location;
684  }
685  public ValidationMessage setLocation(String location) {
686    this.location = location;
687    return this;
688  }
689
690  public IssueType getType() {
691    return type;
692  }
693
694  public ValidationMessage setType(IssueType type) {
695    this.type = type;
696    return this;
697  }
698
699  public String summary() {
700    return level.toString()+" @ "+location+(line>= 0 && col >= 0 ? " (line "+Integer.toString(line)+", col"+Integer.toString(col)+"): " : ": ") +message +(source != null ? " (src = "+source+")" : "");
701  }
702
703
704  public String toXML() {
705    return "<message source=\"" + source + "\" line=\"" + line + "\" col=\"" + col + "\" location=\"" + Utilities.escapeXml(location) + "\" type=\"" + type + "\" level=\"" + level + "\" display=\"" + Utilities.escapeXml(getDisplay()) + "\" ><plain>" + Utilities.escapeXml(message) + "</plain><html>" + html + "</html></message>";
706  }
707
708  public String getHtml() {
709    return html == null ? Utilities.escapeXml(message) : html;
710  }
711
712  public String getDisplay() {
713    return level + ": " + (location==null || location.isEmpty() ? "" : (location + ": ")) + message;
714  }
715
716  /**
717   * Returns a representation of this ValidationMessage suitable for logging. The values of
718   * most of the internal fields are included, so this may not be suitable for display to 
719   * an end user.
720   */
721  @Override
722  public String toString() {
723    ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
724    b.append("level", level);
725    b.append("type", type);
726    b.append("location", location);
727    b.append("message", message);
728    return b.build();
729  }
730
731  @Override
732  public boolean equals(Object o) {
733    return (this.getMessage() != null && this.getMessage().equals(((ValidationMessage)o).getMessage())) && (this.getLocation() != null && this.getLocation().equals(((ValidationMessage)o).getLocation()));
734  }
735
736  @Override
737  public int compare(ValidationMessage x, ValidationMessage y) {
738    String sx = x.getLevel().getDisplay() + x.getType().getDisplay() + String.format("%06d", x.getLine()) + x.getMessage();
739    String sy = y.getLevel().getDisplay() + y.getType().getDisplay() + String.format("%06d", y.getLine()) + y.getMessage();
740    return sx.compareTo(sy);
741  }  
742
743  @Override
744  public int compareTo(ValidationMessage y) {
745    return compare(this, y);
746  }
747
748  public String getLocationLink() {
749    return locationLink;
750  }
751
752  public ValidationMessage setLocationLink(String locationLink) {
753    this.locationLink = locationLink;
754    return this;
755  }
756
757  public String getTxLink() {
758    return txLink;
759  }
760
761  public ValidationMessage setTxLink(String txLink) {
762    this.txLink = txLink;
763    return this;
764  }
765
766  public void setHtml(String html) {
767    this.html = html;
768  }
769
770  public boolean isSlicingHint() {
771    return slicingHint;
772  }
773
774  public ValidationMessage setSlicingHint(boolean slicingHint) {
775    this.slicingHint = slicingHint;
776    return this;
777  }
778
779  public String getSliceHtml() {
780    return sliceHtml;
781  }
782
783  public ValidationMessage setSliceHtml(String sliceHtml, String[] text) {
784    this.sliceHtml = sliceHtml;
785    this.sliceText = text;
786    return this;
787  }
788
789  public String getMessageId() {
790    return messageId;
791  }
792
793  public ValidationMessage setMessageId(String messageId) {
794    this.messageId = messageId;
795    return this;
796  }
797
798  public boolean isSignpost() {
799    return signpost;
800  }
801
802  public ValidationMessage setSignpost(boolean signpost) {
803    this.signpost = signpost;
804    return this;
805  }
806
807  public boolean isCriticalSignpost() {
808    return criticalSignpost;
809  }
810
811  public ValidationMessage setCriticalSignpost(boolean criticalSignpost) {
812    this.criticalSignpost = criticalSignpost;
813    return this;
814  }
815
816  public Date getRuleDate() {
817    return ruleDate;
818  }
819
820  public ValidationMessage setRuleDate(Date ruleDate) {
821    this.ruleDate = ruleDate;
822    return this;
823  }
824
825
826  public ValidationMessage setRuleDate(String value) {
827    if (value == null) {
828      ruleDate = null;
829    } else {
830      Date d = null;
831      try {
832        d = new SimpleDateFormat("yyyy-MM-dd").parse(value);
833      } catch (ParseException e) {
834        e.printStackTrace();
835      }
836      ruleDate = d;
837    }
838    return this;
839  }
840}