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}