001package org.hl7.fhir.validation.instance.type; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.List; 007 008import org.hl7.fhir.r5.context.IWorkerContext; 009import org.hl7.fhir.r5.elementmodel.Element; 010import org.hl7.fhir.r5.model.Coding; 011import org.hl7.fhir.r5.model.ExpressionNode; 012import org.hl7.fhir.r5.model.ExpressionNode.Kind; 013import org.hl7.fhir.r5.model.ExpressionNode.Operation; 014import org.hl7.fhir.r5.model.SearchParameter; 015import org.hl7.fhir.r5.utils.FHIRPathEngine; 016import org.hl7.fhir.r5.utils.XVerExtensionManager; 017import org.hl7.fhir.utilities.Utilities; 018import org.hl7.fhir.utilities.i18n.I18nConstants; 019import org.hl7.fhir.utilities.validation.ValidationMessage; 020import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 021import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 022import org.hl7.fhir.validation.BaseValidator; 023import org.hl7.fhir.validation.TimeTracker; 024import org.hl7.fhir.validation.instance.utils.NodeStack; 025 026public class SearchParameterValidator extends BaseValidator { 027 028 public class FhirPathSorter implements Comparator<ExpressionNode> { 029 030 @Override 031 public int compare(ExpressionNode arg0, ExpressionNode arg1) { 032 return arg0.toString().compareTo(arg1.toString()); 033 } 034 035 } 036 037 private FHIRPathEngine fpe; 038 039 public SearchParameterValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, XVerExtensionManager xverManager, Coding jurisdiction) { 040 super(context, xverManager); 041 source = Source.InstanceValidator; 042 this.fpe = fpe; 043 this.timeTracker = timeTracker; 044 this.jurisdiction = jurisdiction; 045 } 046 047 public boolean validateSearchParameter(List<ValidationMessage> errors, Element cs, NodeStack stack) { 048 boolean ok = true; 049 String url = cs.getNamedChildValue("url"); 050 String master = cs.getNamedChildValue("derivedFrom"); 051 052 if (!Utilities.noString(master)) { 053 SearchParameter sp = context.fetchResource(SearchParameter.class, master); 054 if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE,stack.getLiteralPath(), sp != null, I18nConstants.SEARCHPARAMETER_NOTFOUND, master)) { 055 // base must be in the master list of base 056 List<Element> bl = cs.getChildren("base"); 057 for (Element b : bl) { 058 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE,stack.getLiteralPath(), sp.hasBase(b.primitiveValue()) || sp.hasBase("Resource"), I18nConstants.SEARCHPARAMETER_BASE_WRONG, master, b.primitiveValue()) && ok; 059 } 060 ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE,stack.getLiteralPath(), !cs.hasChild("type") || sp.getType().toCode().equals(cs.getNamedChildValue("type")), I18nConstants.SEARCHPARAMETER_TYPE_WRONG, master, sp.getType().toCode(), cs.getNamedChildValue("type")) && ok; 061 if (sp.hasExpression() && cs.hasChild("expression") && !sp.getExpression().equals(cs.getNamedChildValue("expression"))) { 062 List<String> bases = new ArrayList<>(); 063 for (Element b : cs.getChildren("base")) { 064 bases.add(b.primitiveValue()); 065 } 066 String expThis = canonicalise(cs.getNamedChildValue("expression"), bases); 067 String expOther = canonicalise(sp.getExpression(), bases); 068 warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE,stack.getLiteralPath(), expThis.equals(expOther), I18nConstants.SEARCHPARAMETER_EXP_WRONG, master, sp.getExpression(), cs.getNamedChildValue("expression")); 069 } 070 // todo: check compositions 071 } 072 } 073 return ok; 074 } 075 076 private String canonicalise(String path, List<String> bases) { 077 ExpressionNode exp = fpe.parse(path); 078 List<ExpressionNode> pass = new ArrayList<>(); 079 while (exp != null) { 080 if ((exp.getKind() != Kind.Name && !(exp.getKind() == Kind.Group && exp.getGroup().getKind() == Kind.Name))) { 081 return path; 082 } 083 if (exp.getOperation() != null && exp.getOperation() != Operation.Union) { 084 return path; 085 } 086 ExpressionNode nexp = exp.getOpNext(); 087 exp.setOperation(null); 088 exp.setOpNext(null); 089 String name = exp.getKind() == Kind.Name ? exp.getName() : exp.getGroup().getName(); 090 if (context.getResourceNames().contains(name)) { 091 if (bases.contains(name)) { 092 pass.add(exp); 093 } 094 } else { 095 pass.add(exp); 096 } 097 exp = nexp; 098 } 099 Collections.sort(pass, new FhirPathSorter()); 100 for (int i = 0; i < pass.size()-1; i++) { 101 pass.get(i).setOperation(Operation.Union); 102 pass.get(i).setOpNext(pass.get(i+1)); 103 } 104 return pass.get(0).toString(); 105 } 106 107}