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