001package org.hl7.fhir.r5.utils.sql; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Set; 008 009import org.hl7.fhir.exceptions.FHIRException; 010import org.hl7.fhir.r5.context.IWorkerContext; 011import org.hl7.fhir.r5.fhirpath.ExpressionNode; 012import org.hl7.fhir.r5.fhirpath.FHIRPathEngine; 013import org.hl7.fhir.r5.fhirpath.TypeDetails; 014import org.hl7.fhir.r5.fhirpath.ExpressionNode.CollectionStatus; 015import org.hl7.fhir.r5.fhirpath.FHIRPathEngine.IssueMessage; 016import org.hl7.fhir.utilities.Utilities; 017import org.hl7.fhir.utilities.json.model.JsonArray; 018import org.hl7.fhir.utilities.json.model.JsonBoolean; 019import org.hl7.fhir.utilities.json.model.JsonElement; 020import org.hl7.fhir.utilities.json.model.JsonNumber; 021import org.hl7.fhir.utilities.json.model.JsonObject; 022import org.hl7.fhir.utilities.json.model.JsonProperty; 023import org.hl7.fhir.utilities.json.model.JsonString; 024import org.hl7.fhir.utilities.validation.ValidationMessage; 025import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 026import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 027import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 028 029public class Validator { 030 031 private IWorkerContext context; 032 private FHIRPathEngine fpe; 033 private List<String> prohibitedNames = new ArrayList<String>(); 034 private List<ValidationMessage> issues = new ArrayList<ValidationMessage>(); 035 private Boolean arrays; 036 private Boolean complexTypes; 037 private Boolean needsName; 038 039 private String resourceName; 040 private String name; 041 042 public Validator(IWorkerContext context, FHIRPathEngine fpe, List<String> prohibitedNames, Boolean arrays, Boolean complexTypes, Boolean needsName) { 043 super(); 044 this.context = context; 045 this.fpe = fpe; 046 this.prohibitedNames = prohibitedNames; 047 this.arrays = arrays; 048 this.complexTypes = complexTypes; 049 this.needsName = needsName; 050 } 051 052 public String getResourceName() { 053 return resourceName; 054 } 055 056 057 public void checkViewDefinition(String path, JsonObject viewDefinition) { 058 checkProperties(viewDefinition, path, "resourceType", "url", "identifier", "name", "version", "title", "status", "experimental", "date", "publisher", "contact", "description", "useContext", "copyright", "resource", "constant", "select", "where"); 059 060 JsonElement nameJ = viewDefinition.get("name"); 061 if (nameJ == null) { 062 if (needsName == null) { 063 hint(path, viewDefinition, "No name provided. A name is required in many contexts where a ViewDefinition is used"); 064 } else if (needsName) { 065 error(path, viewDefinition, "No name provided", IssueType.REQUIRED); 066 } 067 } else if (!(nameJ instanceof JsonString)) { 068 error(path, viewDefinition, "name must be a string", IssueType.INVALID); 069 } else { 070 name = nameJ.asString(); 071 if (!isValidName(name)) { 072 error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT); 073 } 074 if (prohibitedNames.contains(name)) { 075 error(path, nameJ, "The name '"+name+"' on the viewDefinition is not allowed in this context", IssueType.BUSINESSRULE); 076 } 077 } 078 079 List<Column> columns = new ArrayList<>(); 080 viewDefinition.setUserData("columns", columns); 081 082 JsonElement resourceNameJ = viewDefinition.get("resource"); 083 if (resourceNameJ == null) { 084 error(path, viewDefinition, "No resource specified", IssueType.REQUIRED); 085 } else if (!(resourceNameJ instanceof JsonString)) { 086 error(path, viewDefinition, "resource must be a string", IssueType.INVALID); 087 } else { 088 resourceName = resourceNameJ.asString(); 089 if (!context.getResourceNamesAsSet().contains(resourceName)) { 090 error(path+".name", nameJ, "The name '"+resourceName+"' is not a valid resource", IssueType.BUSINESSRULE); 091 } else { 092 int i = 0; 093 if (checkAllObjects(path, viewDefinition, "constant")) { 094 for (JsonObject constant : viewDefinition.getJsonObjects("constant")) { 095 checkConstant(path+".constant["+i+"]", constant); 096 i++; 097 } 098 } 099 i = 0; 100 if (checkAllObjects(path, viewDefinition, "where")) { 101 for (JsonObject where : viewDefinition.getJsonObjects("where")) { 102 checkWhere(path+".where["+i+"]", where); 103 i++; 104 } 105 } 106 TypeDetails t = new TypeDetails(CollectionStatus.SINGLETON, resourceName); 107 108 i = 0; 109 if (checkAllObjects(path, viewDefinition, "select")) { 110 for (JsonObject select : viewDefinition.getJsonObjects("select")) { 111 columns.addAll(checkSelect(path+".select["+i+"]", select, t)); 112 i++; 113 } 114 if (i == 0) { 115 error(path, viewDefinition, "No select statements found", IssueType.REQUIRED); 116 } 117 } 118 } 119 } 120 } 121 122 private List<Column> checkSelect(String path, JsonObject select, TypeDetails t) { 123 List<Column> columns = new ArrayList<>(); 124 select.setUserData("columns", columns); 125 checkProperties(select, path, "column", "select", "forEach", "forEachOrNull", "unionAll"); 126 127 if (select.has("forEach")) { 128 t = checkForEach(path, select, select.get("forEach"), t); 129 } else if (select.has("forEachOrNull")) { 130 t = checkForEachOrNull(path, select, select.get("forEachOrNull"), t); 131 } 132 133 if (t != null) { 134 135 if (select.has("column")) { 136 JsonElement a = select.get("column"); 137 if (!(a instanceof JsonArray)) { 138 error(path+".column", a, "column is not an array", IssueType.INVALID); 139 } else { 140 int i = 0; 141 for (JsonElement e : ((JsonArray) a)) { 142 if (!(e instanceof JsonObject)) { 143 error(path+".column["+i+"]", a, "column["+i+"] is a "+e.type().toName()+" not an object", IssueType.INVALID); 144 } else { 145 columns.add(checkColumn(path+".column["+i+"]", (JsonObject) e, t)); 146 } 147 } 148 } 149 } 150 151 if (select.has("select")) { 152 JsonElement a = select.get("select"); 153 if (!(a instanceof JsonArray)) { 154 error(path+".select", a, "select is not an array", IssueType.INVALID); 155 } else { 156 int i = 0; 157 for (JsonElement e : ((JsonArray) a)) { 158 if (!(e instanceof JsonObject)) { 159 error(path+".select["+i+"]", e, "select["+i+"] is not an object", IssueType.INVALID); 160 } else { 161 columns.addAll(checkSelect(path+".select["+i+"]", (JsonObject) e, t)); 162 } 163 } 164 } 165 } 166 167 if (select.has("unionAll")) { 168 columns.addAll(checkUnion(path, select, select.get("unionAll"), t)); 169 } 170 if (columns.isEmpty()) { 171 error(path, select, "The select has no columns or selects", IssueType.REQUIRED); 172 } else { 173 checkColumnNamesUnique(select, path, columns); 174 } 175 } 176 return columns; 177 } 178 179 180 private void checkColumnNamesUnique(JsonObject select, String path, List<Column> columns) { 181 Set<String> names = new HashSet<>(); 182 for (Column col : columns) { 183 if (col != null) { 184 if (!names.contains(col.getName())) { 185 names.add(col.getName()); 186 } else if (!col.isDuplicateReported()) { 187 col.setDuplicateReported(true); 188 error(path, select, "Duplicate Column Name '"+col.getName()+"'", IssueType.BUSINESSRULE); 189 } 190 } 191 } 192 } 193 194 private List<Column> checkUnion(String path, JsonObject focus, JsonElement expression, TypeDetails t) { 195 JsonElement a = focus.get("unionAll"); 196 if (!(a instanceof JsonArray)) { 197 error(path+".unionAll", a, "union is not an array", IssueType.INVALID); 198 return null; 199 } else { 200 List<List<Column>> unionColumns = new ArrayList<>(); 201 int i = 0; 202 for (JsonElement e : ((JsonArray) a)) { 203 if (!(e instanceof JsonObject)) { 204 error(path+".unionAll["+i+"]", e, "unionAll["+i+"] is not an object", IssueType.INVALID); 205 } else { 206 unionColumns.add(checkSelect(path+".unionAll["+i+"]", (JsonObject) e, t)); 207 } 208 i++; 209 } 210 if (i < 2) { 211 warning(path+".unionAll", a, "unionAll should have more than one item"); 212 } 213 if (unionColumns.size() > 1) { 214 List<Column> columns = unionColumns.get(0); 215 for (int ic = 1; ic < unionColumns.size(); ic++) { 216 String diff = columnDiffs(columns, unionColumns.get(ic)); 217 if (diff != null) { 218 error(path+".unionAll["+i+"]", ((JsonArray) a).get(ic), "unionAll["+i+"] column definitions do not match: "+diff, IssueType.INVALID); 219 } 220 } 221 a.setUserData("colunms", columns); 222 return columns; 223 } 224 } 225 return null; 226 } 227 228 private String columnDiffs(List<Column> list1, List<Column> list2) { 229 if (list1.size() == list2.size()) { 230 for (int i = 0; i < list1.size(); i++) { 231 if (list1.get(i) == null || list2.get(i) == null) { 232 return null; // just suppress any addition errors 233 } 234 String diff = list1.get(i).diff(list2.get(i)); 235 if (diff != null) { 236 return diff+" at #"+i; 237 } 238 } 239 return null; 240 } else { 241 return "Column counts differ: "+list1.size()+" vs "+list2.size(); 242 } 243 } 244 245 private Column checkColumn(String path, JsonObject column, TypeDetails t) { 246 checkProperties(column, path, "path", "name", "description", "collection", "type", "tag"); 247 248 if (!column.has("path")) { 249 error(path, column, "no path found", IssueType.INVALID); 250 } else { 251 JsonElement expression = column.get("path"); 252 if (!(expression instanceof JsonString)) { 253 error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID); 254 } else { 255 String expr = expression.asString(); 256 257 List<IssueMessage> warnings = new ArrayList<>(); 258 TypeDetails td = null; 259 ExpressionNode node = null; 260 try { 261 node = fpe.parse(expr); 262 column.setUserData("path", node); 263 td = fpe.checkOnTypes(null, resourceName, t, node, warnings); 264 } catch (Exception e) { 265 error(path, expression, e.getMessage(), IssueType.INVALID); 266 } 267 if (td != null && node != null) { 268 for (IssueMessage s : warnings) { 269 warning(path+".path", expression, s.getMessage()); 270 } 271 String columnName = null; 272 JsonElement nameJ = column.get("name"); 273 if (nameJ != null) { 274 if (nameJ instanceof JsonString) { 275 columnName = nameJ.asString(); 276 if (!isValidName(columnName)) { 277 error(path+".name", nameJ, "The name '"+columnName+"' is not valid", IssueType.VALUE); 278 } 279 } else { 280 error(path+".name", nameJ, "name must be a string", IssueType.INVALID); 281 } 282 } 283 if (columnName == null) { 284 List<String> names = node.getDistalNames(); 285 if (names.size() == 1 && names.get(0) != null) { 286 columnName = names.get(0); 287 if (!isValidName(columnName)) { 288 error(path+".path", expression, "A column name is required. The natural name to chose is '"+columnName+"' (from the path)", IssueType.INVARIANT); 289 } else { 290 error(path, column, "A column name is required", IssueType.REQUIRED); 291 } 292 } else { 293 error(path, column, "A column name is required", IssueType.REQUIRED); 294 } 295 } 296 // ok, name is sorted! 297 if (columnName != null) { 298 column.setUserData("name", columnName); 299 boolean isColl = (td.getCollectionStatus() != CollectionStatus.SINGLETON); 300 if (column.has("collection")) { 301 JsonElement collectionJ = column.get("collection"); 302 if (!(collectionJ instanceof JsonBoolean)) { 303 error(path+".collection", collectionJ, "collection is not a boolean", IssueType.INVALID); 304 } else { 305 boolean collection = collectionJ.asJsonBoolean().asBoolean(); 306 if (!collection && isColl) { 307 isColl = false; 308 warning(path, column, "collection is false, but the path statement(s) might return multiple values for the column '"+columnName+"' some inputs"); 309 } 310 } 311 } 312 if (isColl) { 313 if (arrays == null) { 314 warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path. Collections are not supported in all execution contexts"); 315 } else if (!arrays) { 316 warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path, but this is not allowed in the current execution context"); 317 } 318 } 319 Set<String> types = new HashSet<>(); 320 if (node.isNullSet()) { 321 types.add("null"); 322 } else { 323 // ok collection is sorted 324 for (String type : td.getTypes()) { 325 types.add(simpleType(type)); 326 } 327 328 JsonElement typeJ = column.get("type"); 329 if (typeJ != null) { 330 if (typeJ instanceof JsonString) { 331 String type = typeJ.asString(); 332 if (!td.hasType(type)) { 333 error(path+".type", typeJ, "The path expression does not return a value of the type '"+type, IssueType.VALUE); 334 } else { 335 types.clear(); 336 types.add(simpleType(type)); 337 } 338 } else { 339 error(path+".type", typeJ, "type must be a string", IssueType.INVALID); 340 } 341 } 342 } 343 if (types.size() != 1) { 344 error(path, column, "Unable to determine a type (found "+td.describe()+")", IssueType.BUSINESSRULE); 345 } else { 346 String type = types.iterator().next(); 347 boolean ok = false; 348 if (!isSimpleType(type) && !"null".equals(type)) { 349 if (complexTypes) { 350 warning(path, expression, "Column is a complex type. This is not supported in some Runners"); 351 } else if (!complexTypes) { 352 error(path, expression, "Column is a complex type but this is not allowed in this context", IssueType.BUSINESSRULE); 353 } else { 354 ok = true; 355 } 356 } else { 357 ok = true; 358 } 359 if (ok) { 360 Column col = new Column(columnName, isColl, type, kindForType(type)); 361 column.setUserData("column", col); 362 return col; 363 } 364 } 365 } 366 } 367 } 368 } 369 return null; 370 } 371 372 private ColumnKind kindForType(String type) { 373 switch (type) { 374 case "null": return ColumnKind.Null; 375 case "dateTime": return ColumnKind.DateTime; 376 case "boolean": return ColumnKind.Boolean; 377 case "integer": return ColumnKind.Integer; 378 case "decimal": return ColumnKind.Decimal; 379 case "string": return ColumnKind.String; 380 case "base64Binary": return ColumnKind.Binary; 381 case "time": return ColumnKind.Time; 382 default: return ColumnKind.Complex; 383 } 384 } 385 386 private boolean isSimpleType(String type) { 387 return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary"); 388 } 389 390 private String simpleType(String type) { 391 type = type.replace("http://hl7.org/fhirpath/System.", "").replace("http://hl7.org/fhir/StructureDefinition/", ""); 392 if (Utilities.existsInList(type, "date", "dateTime", "instant")) { 393 return "dateTime"; 394 } 395 if (Utilities.existsInList(type, "Boolean", "boolean")) { 396 return "boolean"; 397 } 398 if (Utilities.existsInList(type, "Integer", "integer", "integer64")) { 399 return "integer"; 400 } 401 if (Utilities.existsInList(type, "Decimal", "decimal")) { 402 return "decimal"; 403 } 404 if (Utilities.existsInList(type, "String", "string", "code")) { 405 return "string"; 406 } 407 if (Utilities.existsInList(type, "Time", "time")) { 408 return "time"; 409 } 410 if (Utilities.existsInList(type, "base64Binary")) { 411 return "base64Binary"; 412 } 413 return type; 414 } 415 416 private TypeDetails checkForEach(String path, JsonObject focus, JsonElement expression, TypeDetails t) { 417 if (!(expression instanceof JsonString)) { 418 error(path+".forEach", expression, "forEach is not a string", IssueType.INVALID); 419 return null; 420 } else { 421 String expr = expression.asString(); 422 423 List<IssueMessage> warnings = new ArrayList<>(); 424 TypeDetails td = null; 425 try { 426 ExpressionNode n = fpe.parse(expr); 427 focus.setUserData("forEach", n); 428 td = fpe.checkOnTypes(null, resourceName, t, n, warnings); 429 } catch (Exception e) { 430 error(path, expression, e.getMessage(), IssueType.INVALID); 431 } 432 if (td != null) { 433 for (IssueMessage s : warnings) { 434 warning(path+".forEach", expression, s.getMessage()); 435 } 436 } 437 return td; 438 } 439 } 440 441 private TypeDetails checkForEachOrNull(String path, JsonObject focus, JsonElement expression, TypeDetails t) { 442 if (!(expression instanceof JsonString)) { 443 error(path+".forEachOrNull", expression, "forEachOrNull is not a string", IssueType.INVALID); 444 return null; 445 } else { 446 String expr = expression.asString(); 447 448 List<IssueMessage> warnings = new ArrayList<>(); 449 TypeDetails td = null; 450 try { 451 ExpressionNode n = fpe.parse(expr); 452 focus.setUserData("forEachOrNull", n); 453 td = fpe.checkOnTypes(null, resourceName, t, n, warnings); 454 } catch (Exception e) { 455 error(path, expression, e.getMessage(), IssueType.INVALID); 456 } 457 if (td != null) { 458 for (IssueMessage s : warnings) { 459 warning(path+".forEachOrNull", expression, s.getMessage()); 460 } 461 } 462 return td; 463 } 464 } 465 466 private void checkConstant(String path, JsonObject constant) { 467 checkProperties(constant, path, "name", "valueBase64Binary", "valueBoolean", "valueCanonical", "valueCode", "valueDate", "valueDateTime", "valueDecimal", "valueId", "valueInstant", "valueInteger", "valueInteger64", "valueOid", "valueString", "valuePositiveInt", "valueTime", "valueUnsignedInt", "valueUri", "valueUrl", "valueUuid"); 468 JsonElement nameJ = constant.get("name"); 469 if (nameJ == null) { 470 error(path, constant, "No name provided", IssueType.REQUIRED); 471 } else if (!(nameJ instanceof JsonString)) { 472 error(path, constant, "Name must be a string", IssueType.INVALID); 473 } else { 474 String name = constant.asString("name"); 475 if (!isValidName(name)) { 476 error(path+".name", nameJ, "The name '"+name+"' is not valid", IssueType.INVARIANT); 477 } 478 } 479 if (constant.has("valueBase64Binary")) { 480 checkIsString(path, constant, "valueBase64Binary"); 481 } else if (constant.has("valueBoolean")) { 482 checkIsBoolean(path, constant, "valueBoolean"); 483 } else if (constant.has("valueCanonical")) { 484 checkIsString(path, constant, "valueCanonical"); 485 } else if (constant.has("valueCode")) { 486 checkIsString(path, constant, "valueCode"); 487 } else if (constant.has("valueDate")) { 488 checkIsString(path, constant, "valueDate"); 489 } else if (constant.has("valueDateTime")) { 490 checkIsString(path, constant, "valueDateTime"); 491 } else if (constant.has("valueDecimal")) { 492 checkIsNumber(path, constant, "valueDecimal"); 493 } else if (constant.has("valueId")) { 494 checkIsString(path, constant, "valueId"); 495 } else if (constant.has("valueInstant")) { 496 checkIsString(path, constant, "valueInstant"); 497 } else if (constant.has("valueInteger")) { 498 checkIsNumber(path, constant, "valueInteger"); 499 } else if (constant.has("valueInteger64")) { 500 checkIsNumber(path, constant, "valueInteger64"); 501 } else if (constant.has("valueOid")) { 502 checkIsString(path, constant, "valueOid"); 503 } else if (constant.has("valueString")) { 504 checkIsString(path, constant, "valueString"); 505 } else if (constant.has("valuePositiveInt")) { 506 checkIsNumber(path, constant, "valuePositiveInt"); 507 } else if (constant.has("valueTime")) { 508 checkIsString(path, constant, "valueTime"); 509 } else if (constant.has("valueUnsignedInt")) { 510 checkIsNumber(path, constant, "valueUnsignedInt"); 511 } else if (constant.has("valueUri")) { 512 checkIsString(path, constant, "valueUri"); 513 } else if (constant.has("valueUrl")) { 514 checkIsString(path, constant, "valueUrl"); 515 } else if (constant.has("valueUuid")) { 516 checkIsString(path, constant, "valueUuid"); 517 } else { 518 error(path, constant, "No value found", IssueType.REQUIRED); 519 } 520 } 521 522 private void checkIsString(String path, JsonObject constant, String name) { 523 JsonElement j = constant.get(name); 524 if (!(j instanceof JsonString)) { 525 error(path+"."+name, j, name+" must be a string", IssueType.INVALID); 526 } 527 } 528 529 private void checkIsBoolean(String path, JsonObject constant, String name) { 530 JsonElement j = constant.get(name); 531 if (!(j instanceof JsonBoolean)) { 532 error(path+"."+name, j, name+" must be a boolean", IssueType.INVALID); 533 } 534 } 535 536 private void checkIsNumber(String path, JsonObject constant, String name) { 537 JsonElement j = constant.get(name); 538 if (!(j instanceof JsonNumber)) { 539 error(path+"."+name, j, name+" must be a number", IssueType.INVALID); 540 } 541 } 542 private void checkWhere(String path, JsonObject where) { 543 checkProperties(where, path, "path", "description"); 544 545 String expr = where.asString("path"); 546 if (expr == null) { 547 error(path, where, "No path provided", IssueType.REQUIRED); 548 } 549 List<String> types = new ArrayList<>(); 550 List<IssueMessage> warnings = new ArrayList<>(); 551 types.add(resourceName); 552 TypeDetails td = null; 553 try { 554 ExpressionNode n = fpe.parse(expr); 555 where.setUserData("path", n); 556 td = fpe.checkOnTypes(null, resourceName, types, n, warnings); 557 } catch (Exception e) { 558 error(path, where.get("path"), e.getMessage(), IssueType.INVALID); 559 } 560 if (td != null) { 561 if (td.getCollectionStatus() != CollectionStatus.SINGLETON || td.getTypes().size() != 1 || !td.hasType("boolean")) { 562 error(path+".path", where.get("path"), "A where path must return a boolean, but the expression "+expr+" returns a "+td.describe(), IssueType.BUSINESSRULE); 563 } else { 564 for (IssueMessage s : warnings) { 565 warning(path+".path", where.get("path"), s.getMessage()); 566 } 567 } 568 } 569 } 570 571 private void checkProperties(JsonObject obj, String path, String... names) { 572 for (JsonProperty p : obj.getProperties()) { 573 boolean nameOk = "extension".equals(p.getName()); 574 for (String name : names) { 575 nameOk = nameOk || name.equals(p.getName()); 576 } 577 if (!nameOk) { 578 error(path+"."+p.getName(), p.getValue(), "Unknown JSON property "+p.getName(), IssueType.UNKNOWN); 579 } 580 } 581 582 } 583 584 private boolean isValidName(String name) { 585 boolean first = true; 586 for (char c : name.toCharArray()) { 587 if (!(Character.isAlphabetic(c) || Character.isDigit(c) || (!first && c == '_'))) { 588 return false; 589 } 590 first = false; 591 } 592 return true; 593 } 594 595 596 private boolean checkAllObjects(String path, JsonObject focus, String name) { 597 if (!focus.has(name)) { 598 return true; 599 } else if (!(focus.get(name) instanceof JsonArray)) { 600 error(path+"."+name, focus.get(name), name+" must be an array", IssueType.INVALID); 601 return false; 602 } else { 603 JsonArray arr = focus.getJsonArray(name); 604 int i = 0; 605 boolean ok = true; 606 for (JsonElement e : arr) { 607 if (!(e instanceof JsonObject)) { 608 error(path+"."+name+"["+i+"]", e, name+"["+i+"] must be an object", IssueType.INVALID); 609 ok = false; 610 } 611 } 612 return ok; 613 } 614 } 615 616 private void error(String path, JsonElement e, String issue, IssueType type) { 617 ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, type, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.ERROR); 618 issues.add(vm); 619 620 } 621 622 private void warning(String path, JsonElement e, String issue) { 623 ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.WARNING); 624 issues.add(vm); 625 } 626 627 private void hint(String path, JsonElement e, String issue) { 628 ValidationMessage vm = new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, e.getStart().getLine(), e.getStart().getCol(), path, issue, IssueSeverity.INFORMATION); 629 issues.add(vm); 630 } 631 632 public void dump() { 633 for (ValidationMessage vm : issues) { 634 System.out.println(vm.summary()); 635 } 636 637 } 638 639 public void check() { 640 if (!isOk()) { 641 throw new FHIRException("View Definition is not valid"); 642 } 643 644 } 645 646 public String getName() { 647 return name; 648 } 649 650 public List<ValidationMessage> getIssues() { 651 return issues; 652 } 653 654 public boolean isOk() { 655 boolean ok = true; 656 for (ValidationMessage vm : issues) { 657 if (vm.isError()) { 658 ok = false; 659 } 660 } 661 return ok; 662 } 663}