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}