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}