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}