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