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