
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}