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