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