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}