001package org.hl7.fhir.r5.utils;
002
003import java.util.HashSet;
004import java.util.List;
005import java.util.Set;
006
007import lombok.extern.slf4j.Slf4j;
008import org.hl7.fhir.r5.context.IWorkerContext;
009import org.hl7.fhir.r5.model.CanonicalResource;
010import org.hl7.fhir.r5.model.CanonicalType;
011import org.hl7.fhir.r5.model.CapabilityStatement;
012import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent;
013import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
014import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceOperationComponent;
015import org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
016import org.hl7.fhir.r5.model.CodeSystem;
017import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
018import org.hl7.fhir.r5.model.ConceptMap;
019import org.hl7.fhir.r5.model.ConceptMap.AdditionalAttributeComponent;
020import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
021import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
022import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
023import org.hl7.fhir.r5.model.DomainResource;
024import org.hl7.fhir.r5.model.ElementDefinition;
025import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
026import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
027import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
028import org.hl7.fhir.r5.model.NamingSystem;
029import org.hl7.fhir.r5.model.OperationDefinition;
030import org.hl7.fhir.r5.model.OperationDefinition.OperationDefinitionParameterComponent;
031import org.hl7.fhir.r5.model.Questionnaire;
032import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemAnswerOptionComponent;
033import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent;
034import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemEnableWhenComponent;
035import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemInitialComponent;
036import org.hl7.fhir.r5.model.Reference;
037import org.hl7.fhir.r5.model.RelatedArtifact;
038import org.hl7.fhir.r5.model.Resource;
039import org.hl7.fhir.r5.model.SearchParameter;
040import org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent;
041import org.hl7.fhir.r5.model.StructureDefinition;
042import org.hl7.fhir.r5.model.UsageContext;
043import org.hl7.fhir.r5.model.ValueSet;
044import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
045import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
046import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
047import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
048import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
049import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
050import org.hl7.fhir.utilities.Utilities;
051
052@MarkedToMoveToAdjunctPackage
053@Slf4j
054public class ResourceDependencyWalker {
055
056  public interface IResourceDependencyNotifier {
057    public void seeResource(Resource resource, String summaryId);
058    public void brokenLink(String link);
059  }
060  
061  public class NullResourceDependencyNotifier implements IResourceDependencyNotifier {
062
063    @Override
064    public void seeResource(Resource resource, String summaryId) {
065      log.info(summaryId);
066    }
067
068    @Override
069    public void brokenLink(String link) {
070      log.error("Broken Link: " +link);
071    }
072  }
073  
074  private IResourceDependencyNotifier notifier = new NullResourceDependencyNotifier();
075  private IWorkerContext context;
076  private Set<String> processedLinks = new HashSet<>();
077  private Set<Resource> processedResources = new HashSet<>();
078  
079  public ResourceDependencyWalker(IWorkerContext context, IResourceDependencyNotifier notifier) {
080    super();
081    this.notifier = notifier;
082    this.context = context;
083  }
084
085  public ResourceDependencyWalker(IWorkerContext context) {
086    super();
087    this.context = context;
088  }
089  
090  private void notify(Resource resource, String prefix) {
091    String summary = null;
092    if (resource instanceof CanonicalResource) {
093      summary = ((CanonicalResource) resource).getVersionedUrl();
094    } else {
095      summary = resource.fhirType()+"/"+resource.getIdPart();
096    }
097    if (resource.getSourcePackage() != null) {
098      summary = summary + " from "+resource.getSourcePackage();
099    }
100    notifier.seeResource(resource, prefix+summary);
101  }
102    
103  public void walk(Resource res) {
104    notify(res, "Find Dependencies for ");
105    processedResources.add(res);
106    doWalk(res);
107  }
108
109  private void walkIntoLink(String value, CanonicalResource source) {
110    if (value != null ) {
111      String key = source.getSourcePackage() == null ? value : value+" from "+source.getSourcePackage().getVID();
112      if (!processedLinks.contains(key)) {
113        processedLinks.add(key);
114        Resource tgt = context.fetchResource(Resource.class, value, source);
115        if (tgt == null && Utilities.charCount(value, '/') == 1) {
116          tgt = context.fetchResourceById(value.substring(0, value.indexOf('/')), value.substring(value.indexOf('/')+1));
117        }
118        if (tgt == null) {
119          notifier.brokenLink(key);
120        } else {
121          if (!processedResources.contains(tgt) && !isCore(tgt)) {
122            processedResources.add(tgt);
123            notify(tgt, "Depends On ");
124            doWalk(tgt);
125          }
126        }
127      }
128    }
129  }
130
131  private boolean isCore(Resource tgt) {
132    return tgt.hasSourcePackage() && "hl7.fhir.r5.core".equals(tgt.getSourcePackage().getId());
133  }
134
135  private void doWalk(Resource res) {
136    if (res instanceof StructureDefinition) {
137      walkSD((StructureDefinition) res);
138    } else if (res instanceof ValueSet) {
139      walkVS((ValueSet) res);
140    } else if (res instanceof CodeSystem) {
141      walkCS((CodeSystem) res);
142    } else if (res instanceof CapabilityStatement) {
143      walkCS((CapabilityStatement) res);
144    } else if (res instanceof ConceptMap) {
145      walkCM((ConceptMap) res);
146    } else if (res instanceof NamingSystem) {
147      walkNS((NamingSystem) res);
148    } else if (res instanceof OperationDefinition) {
149      walkOD((OperationDefinition) res);
150    } else if (res instanceof SearchParameter) {
151      walkSP((SearchParameter) res);
152    } else if (res instanceof Questionnaire) {
153      walkQ((Questionnaire) res);
154    } else {
155      throw new Error("Resource "+res.fhirType()+" not Processed yet");
156    }
157  }
158  
159
160  private void walkSP(SearchParameter sp) {
161    walkCR(sp);
162    walkIntoLink(sp.getDerivedFrom(), sp);
163    for (SearchParameterComponentComponent spc : sp.getComponent()) {
164      walkIntoLink(spc.getDefinition(), sp);
165    }
166  }
167
168  private void walkQ(Questionnaire q) {
169    walkCR(q);
170    walkCT(q.getDerivedFrom(), q);
171    for (QuestionnaireItemComponent item : q.getItem()) {
172      walkQItem(item, q);
173    }
174  }
175
176  private void walkQItem(QuestionnaireItemComponent item, Questionnaire q) {
177    walkIntoLink(item.getDefinition(), q);
178    walkIntoLink(item.getAnswerValueSet(), q);
179    for (QuestionnaireItemEnableWhenComponent ew : item.getEnableWhen()) {
180      if (ew.hasAnswerReference()) {
181        walkIntoLink(ew.getAnswerReference().getReference(), q);
182      }
183    }
184    for (QuestionnaireItemAnswerOptionComponent ao : item.getAnswerOption()) {
185      if (ao.hasValueReference()) {
186        walkIntoLink(ao.getValueReference().getReference(), q);
187      }
188    }
189    for (QuestionnaireItemInitialComponent iv : item.getInitial()) {
190      if (iv.hasValueReference()) {
191        walkIntoLink(iv.getValueReference().getReference(), q);
192      }
193    }
194    walkIntoLink(item.getDefinition(), q);
195    for (QuestionnaireItemComponent child : item.getItem()) {
196      walkQItem(child, q);
197    }
198  }
199
200  private void walkOD(OperationDefinition od) {
201    walkCR(od);
202    walkIntoLink(od.getBase(), od);
203    walkIntoLink(od.getInputProfile(), od);
204    walkIntoLink(od.getOutputProfile(), od);
205
206    for (OperationDefinitionParameterComponent p : od.getParameter()) {
207      walkODP(od, p);
208    }
209  }
210
211  private void walkODP(OperationDefinition od, OperationDefinitionParameterComponent p) {
212    walkCT(p.getTargetProfile(), od);
213    walkIntoLink(p.getBinding().getValueSet(), od);
214    for (OperationDefinitionParameterComponent pp : p.getPart()) {
215      walkODP(od, pp);
216    }
217  }
218
219  private void walkNS(NamingSystem ns) {
220    walkCR(ns);
221  }
222
223  private void walkCM(ConceptMap cm) {
224    walkCR(cm);
225    walkRA(cm.getRelatedArtifact(), cm);
226    
227    for (org.hl7.fhir.r5.model.ConceptMap.PropertyComponent prop : cm.getProperty()) {
228     walkIntoLink(prop.getUri(), cm);
229    }
230    for (AdditionalAttributeComponent attr : cm.getAdditionalAttribute()) {
231      walkIntoLink(attr.getUri(), cm);
232    }
233    walkIntoLink(cm.getSourceScope().primitiveValue(), cm);
234    walkIntoLink(cm.getTargetScope().primitiveValue(), cm);
235    
236    for (ConceptMapGroupComponent group : cm.getGroup()) {
237      walkIntoLink(group.getSource(), cm);
238      walkIntoLink(group.getTarget(), cm);
239      walkIntoLink(group.getUnmapped().getValueSet(), cm);
240      walkIntoLink(group.getUnmapped().getOtherMap(), cm);
241      for (SourceElementComponent elem : group.getElement()) {
242        walkIntoLink(elem.getValueSet(), cm);
243        for (TargetElementComponent tgt : elem.getTarget()) {
244          walkIntoLink(tgt.getValueSet(), cm);          
245        }
246      }
247    }
248  }
249
250  private void walkCS(CapabilityStatement cs) {
251    walkCR(cs);
252    walkCT(cs.getInstantiates(), cs);
253    walkCT(cs.getImports(), cs);
254    walkCT(cs.getImplementationGuide(), cs);
255    
256    for (CapabilityStatementRestComponent rest : cs.getRest()) {
257      
258      for (CapabilityStatementRestResourceComponent res : rest.getResource()) {
259        walkIntoLink(res.getProfile(), cs);
260        walkCT(res.getSupportedProfile(), cs);
261        for (CapabilityStatementRestResourceSearchParamComponent srch : res.getSearchParam()) {
262          walkIntoLink(srch.getDefinition(), cs);
263        }
264        for (CapabilityStatementRestResourceOperationComponent op : res.getOperation()) {
265          walkIntoLink(op.getDefinition(), cs);
266        }
267      }
268      for (CapabilityStatementRestResourceSearchParamComponent srch : rest.getSearchParam()) {
269        walkIntoLink(srch.getDefinition(), cs);
270      }
271      for (CapabilityStatementRestResourceOperationComponent op : rest.getOperation()) {
272        walkIntoLink(op.getDefinition(), cs);
273      }
274    }
275  }
276  
277  private void walkCS(CodeSystem cs) {
278    walkCR(cs);
279    walkRA(cs.getRelatedArtifact(), cs);
280    if (cs.hasValueSet()) {
281      walkIntoLink(cs.getValueSet(), cs);
282    }
283    if (cs.hasSupplements()) {
284      walkIntoLink(cs.getSupplements(), cs);      
285    }
286
287    for (PropertyComponent p : cs.getProperty()) {
288      if (p.hasUri()) {
289        walkIntoLink(p.getUri(), cs);
290      }
291    }
292  }
293
294  private void walkVS(ValueSet vs) {
295    walkCR(vs);
296    walkRA(vs.getRelatedArtifact(), vs);
297    for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
298      walkVSInc(inc, vs);
299    }
300    for (ConceptSetComponent inc : vs.getCompose().getExclude()) {
301      walkVSInc(inc, vs);
302    }
303    if (vs.hasExpansion()) {
304      ValueSetExpansionComponent exp = vs.getExpansion();
305      for (ValueSetExpansionParameterComponent p : exp.getParameter()) {
306        if (p.hasValueUriType()) {
307          walkIntoLink(p.getValueUriType().primitiveValue(), vs);
308        }
309      }
310      for (ValueSetExpansionPropertyComponent p : exp.getProperty()) {
311        if (p.hasUri()) {
312          walkIntoLink(p.getUri(), vs);
313        }
314      }
315      for (ValueSetExpansionContainsComponent cc : exp.getContains()) {
316        walkCC(cc, vs);
317      }
318    }
319  }
320
321  private void walkCC(ValueSetExpansionContainsComponent cc, ValueSet vs) {
322    walkIntoLink(cc.getSystem(), vs);
323    for (ValueSetExpansionContainsComponent ccc : cc.getContains()) {
324      walkCC(ccc, vs);
325    }
326  }
327
328  private void walkVSInc(ConceptSetComponent inc, ValueSet vs) {
329    walkCT(inc.getValueSet(), vs);
330    walkIntoLink(inc.getSystem(), vs);
331  }
332
333  private void walkCT(List<CanonicalType> list, CanonicalResource source) {
334    for (CanonicalType ct : list) {
335      walkIntoLink(ct.getValue(), source);
336    }
337  }
338
339  private void walkRA(List<RelatedArtifact> list, CanonicalResource source) {
340    for (RelatedArtifact ra : list) {
341      walkRA(ra, source);
342    }
343  }
344
345  private void walkRA(RelatedArtifact ra, CanonicalResource source) {
346    if (ra.hasResource()) {
347      walkIntoLink(ra.getResource(), source);
348    }
349    if (ra.hasResourceReference()) {
350      walkIntoLink(ra.getResourceReference().getReference(), source);
351    }    
352  }
353
354  private void walkSD(StructureDefinition sd) {
355    walkCR(sd);
356    walkIntoLink(sd.getBaseDefinition(), sd);
357    for (ElementDefinition ed : sd.getDifferential().getElement()) {
358      walkED(ed, sd);
359    }
360  }
361
362  private void walkED(ElementDefinition ed, StructureDefinition sd) {
363    for (TypeRefComponent type : ed.getType()) {
364      if (Utilities.isAbsoluteUrl(type.getCode())) {
365        walkIntoLink(type.getCode(), sd);
366      }
367      walkCT(type.getProfile(), sd);
368      walkCT(type.getTargetProfile(), sd);        
369    }
370    walkCT(ed.getValueAlternatives(), sd);        
371    if (ed.hasBinding()) {
372      ElementDefinitionBindingComponent b = ed.getBinding();
373      if (b.hasValueSet()) {
374        walkIntoLink(b.getValueSet(), sd);
375      }
376      for (ElementDefinitionBindingAdditionalComponent ab : b.getAdditional()) {
377        if (ab.hasValueSet()) {
378          walkIntoLink(ab.getValueSet(), sd);
379        }
380        if (ab.hasUsage()) {
381          walkUsage(ab.getUsage(), sd);
382        }
383      }
384    }
385  }
386
387  private void walkUsage(List<UsageContext> usageList, CanonicalResource source) {
388    for (UsageContext usage : usageList) {
389      walkUsage(usage, source);
390    }
391  }
392
393  private void walkUsage(UsageContext usage, CanonicalResource source) {
394    if (usage.hasValueReference()) {
395      walkReference(usage.getValueReference(), source);
396    }
397  }
398
399  private void walkReference(Reference ref, CanonicalResource source) {
400    if (ref.hasReference()) {
401      walkIntoLink(ref.getReference(), source);
402    }
403  }
404
405  private void walkCR(CanonicalResource cr) {
406    walkDR(cr);
407    walkUsage(cr.getUseContext(), cr);
408  }
409
410  private void walkDR(DomainResource dr) {
411    walkRes(dr);
412    for (Resource res : dr.getContained()) {
413      walk(res);
414    }
415  }
416
417  private void walkRes(Resource res) {
418    // nothing
419  }
420  
421  
422}