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