001package org.hl7.fhir.r5.context; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010import java.util.UUID; 011 012import org.hl7.fhir.exceptions.FHIRException; 013import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy; 014import org.hl7.fhir.r5.context.IWorkerContext.PackageVersion; 015import org.hl7.fhir.r5.model.CanonicalResource; 016import org.hl7.fhir.r5.model.CodeSystem; 017import org.hl7.fhir.r5.model.DomainResource; 018import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 019import org.hl7.fhir.utilities.VersionUtilities; 020 021/** 022 * This manages a cached list of resources, and provides high speed access by URL / URL+version, and assumes that patch version doesn't matter for access 023 * note, though, that not all resources have semver versions 024 * 025 * @author graha 026 * 027 */ 028 029public class CanonicalResourceManager<T extends CanonicalResource> { 030 031 public static abstract class CanonicalResourceProxy { 032 private String type; 033 private String id; 034 private String url; 035 private String version; 036 private CanonicalResource resource; 037 038 public CanonicalResourceProxy(String type, String id, String url, String version) { 039 super(); 040 this.type = type; 041 this.id = id; 042 this.url = url; 043 this.version = version; 044 } 045 046 public String getType() { 047 return type; 048 } 049 050 public String getId() { 051 return id; 052 } 053 054 public String getUrl() { 055 return url; 056 } 057 058 public String getVersion() { 059 return version; 060 } 061 062 public boolean hasId() { 063 return id != null; 064 } 065 066 public boolean hasUrl() { 067 return url != null; 068 } 069 070 public boolean hasVersion() { 071 return version != null; 072 } 073 074 public CanonicalResource getResource() throws FHIRException { 075 if (resource == null) { 076 resource = loadResource(); 077 if (resource instanceof CodeSystem) { 078 CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) resource); 079 } 080 } 081 return resource; 082 } 083 084 public void setResource(CanonicalResource resource) { 085 this.resource = resource; 086 } 087 088 public abstract CanonicalResource loadResource() throws FHIRException; 089 090 @Override 091 public String toString() { 092 return type+"/"+id+": "+url+"|"+version; 093 } 094 } 095 096 public class CanonicalListSorter implements Comparator<CanonicalResource> { 097 098 @Override 099 public int compare(CanonicalResource arg0, CanonicalResource arg1) { 100 String u0 = arg0.getUrl(); 101 String u1 = arg1.getUrl(); 102 return u0.compareTo(u1); 103 } 104 } 105 106 private class CachedCanonicalResource<T1 extends CanonicalResource> { 107 private T1 resource; 108 private CanonicalResourceProxy proxy; 109 private PackageVersion packageInfo; 110 111 public CachedCanonicalResource(T1 resource, PackageVersion packageInfo) { 112 super(); 113 this.resource = resource; 114 this.packageInfo = packageInfo; 115 } 116 117 public CachedCanonicalResource(CanonicalResourceProxy proxy, PackageVersion packageInfo) { 118 super(); 119 this.proxy = proxy; 120 this.packageInfo = packageInfo; 121 } 122 123 public T1 getResource() { 124 if (resource == null) { 125 @SuppressWarnings("unchecked") 126 T1 res = (T1) proxy.getResource(); 127 if (res == null) { 128 throw new Error("Proxy loading a resource from "+packageInfo+" failed and returned null"); 129 } 130 synchronized (this) { 131 resource = res; 132 } 133 proxy = null; 134 } 135 return resource; 136 } 137 138 public PackageVersion getPackageInfo() { 139 return packageInfo; 140 } 141 public String getUrl() { 142 return resource != null ? resource.getUrl() : proxy.getUrl(); 143 } 144 public String getId() { 145 return resource != null ? resource.getId() : proxy.getId(); 146 } 147 public String getVersion() { 148 return resource != null ? resource.getVersion() : proxy.getVersion(); 149 } 150 public boolean hasVersion() { 151 return resource != null ? resource.hasVersion() : proxy.getVersion() != null; 152 } 153 154 @Override 155 public String toString() { 156 return resource != null ? resource.fhirType()+"/"+resource.getId()+": "+resource.getUrl()+"|"+resource.getVersion() : proxy.toString(); 157 } 158 159 } 160 161 public class MetadataResourceVersionComparator<T1 extends CachedCanonicalResource<T>> implements Comparator<T1> { 162 @Override 163 public int compare(T1 arg1, T1 arg2) { 164 String v1 = arg1.getVersion(); 165 String v2 = arg2.getVersion(); 166 if (v1 == null && v2 == null) { 167 return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order 168 } else if (v1 == null) { 169 return -1; 170 } else if (v2 == null) { 171 return 1; 172 } else { 173 String mm1 = VersionUtilities.getMajMin(v1); 174 String mm2 = VersionUtilities.getMajMin(v2); 175 if (mm1 == null || mm2 == null) { 176 return v1.compareTo(v2); 177 } else { 178 return mm1.compareTo(mm2); 179 } 180 } 181 } 182 } 183 184 private boolean enforceUniqueId; 185 private List<CachedCanonicalResource<T>> list = new ArrayList<>(); 186 private Map<String, CachedCanonicalResource<T>> map = new HashMap<>(); 187 188 189 public CanonicalResourceManager(boolean enforceUniqueId) { 190 super(); 191 this.enforceUniqueId = enforceUniqueId; 192 } 193 194 public void copy(CanonicalResourceManager<T> source) { 195 list.clear(); 196 map.clear(); 197 list.addAll(source.list); 198 map.putAll(source.map); 199 } 200 201 public void register(CanonicalResourceProxy r, PackageVersion packgeInfo) { 202 if (!r.hasId()) { 203 throw new FHIRException("An id is required for a deferred load resource"); 204 } 205 CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo); 206 see(cr); 207 } 208 209 public void see(T r, PackageVersion packgeInfo) { 210 if (r != null) { 211 if (!r.hasId()) { 212 r.setId(UUID.randomUUID().toString()); 213 } 214 CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo); 215 see(cr); 216 } 217 } 218 219 public void see(CachedCanonicalResource<T> cr) { 220 // ignore UTG NUCC erroneous code system 221 if (cr.getPackageInfo() != null && cr.getPackageInfo().getId() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology") && "http://nucc.org/provider-taxonomy".equals(cr.getUrl())) { 222 return; 223 } 224 225 if (enforceUniqueId && map.containsKey(cr.getId())) { 226 drop(cr.getId()); 227 } 228 229 // special case logic for UTG support prior to version 5 230 if (cr.getPackageInfo() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology")) { 231 List<CachedCanonicalResource<T>> toDrop = new ArrayList<>(); 232 for (CachedCanonicalResource<T> n : list) { 233 if (n.getUrl() != null && n.getUrl().equals(cr.getUrl()) && isBasePackage(n.getPackageInfo())) { 234 toDrop.add(n); 235 } 236 } 237 for (CachedCanonicalResource<T> n : toDrop) { 238 drop(n.getId()); 239 } 240 } 241 CachedCanonicalResource<T> existing = cr.hasVersion() ? map.get(cr.getUrl()+"|"+cr.getVersion()) : map.get(cr.getUrl()+"|#0"); 242 if (map.get(cr.getUrl()) != null && (cr.getPackageInfo() != null && cr.getPackageInfo().isExamplesPackage())) { 243 return; 244 } 245 if (existing != null) { 246 list.remove(existing); 247 } 248 249 list.add(cr); 250 map.put(cr.getId(), cr); // we do this so we can drop by id 251 map.put(cr.getUrl(), cr); 252 253 if (cr.getUrl() != null) { 254 // first, this is the correct reosurce for this version (if it has a version) 255 if (cr.hasVersion()) { 256 map.put(cr.getUrl()+"|"+cr.getVersion(), cr); 257 } else { 258 map.put(cr.getUrl()+"|#0", cr); 259 } 260 updateList(cr.getUrl(), cr.getVersion()); 261 } 262 } 263 264 private boolean isBasePackage(PackageVersion packageInfo) { 265 return packageInfo == null ? false : VersionUtilities.isCorePackage(packageInfo.getId()); 266 } 267 268 private void updateList(String url, String version) { 269 List<CachedCanonicalResource<T>> rl = new ArrayList<>(); 270 for (CachedCanonicalResource<T> t : list) { 271 if (url.equals(t.getUrl()) && !rl.contains(t)) { 272 rl.add(t); 273 } 274 } 275 if (rl.size() > 0) { 276 // sort by version as much as we are able 277 Collections.sort(rl, new MetadataResourceVersionComparator<CachedCanonicalResource<T>>()); 278 // the current is the latest 279 map.put(url, rl.get(rl.size()-1)); 280 // now, also, the latest for major/minor 281 if (version != null) { 282 CachedCanonicalResource<T> latest = null; 283 for (CachedCanonicalResource<T> t : rl) { 284 if (VersionUtilities.versionsCompatible(t.getVersion(), version)) { 285 latest = t; 286 } 287 } 288 if (latest != null) { // might be null if it's not using semver 289 String lv = VersionUtilities.getMajMin(latest.getVersion()); 290 if (lv != null && !lv.equals(version)) 291 map.put(url+"|"+lv, rl.get(rl.size()-1)); 292 } 293 } 294 } 295 } 296 297 298 public T get(String url) { 299 return map.containsKey(url) ? map.get(url).getResource() : null; 300 } 301 302 public PackageVersion getPackageInfo(String system, String version) { 303 if (version == null) { 304 return map.containsKey(system) ? map.get(system).getPackageInfo() : null; 305 } else { 306 if (map.containsKey(system+"|"+version)) 307 return map.get(system+"|"+version).getPackageInfo(); 308 String mm = VersionUtilities.getMajMin(version); 309 if (mm != null && map.containsKey(system+"|"+mm)) 310 return map.get(system+"|"+mm).getPackageInfo(); 311 else 312 return null; 313 } 314 } 315 316 public boolean has(String url) { 317 return map.containsKey(url); 318 } 319 320 public T get(String system, String version) { 321 if (version == null) { 322 return get(system); 323 } else { 324 if (map.containsKey(system+"|"+version)) 325 return map.get(system+"|"+version).getResource(); 326 String mm = VersionUtilities.getMajMin(version); 327 if (mm != null && map.containsKey(system+"|"+mm)) 328 return map.get(system+"|"+mm).getResource(); 329 else 330 return null; 331 } 332 } 333 334 public boolean has(String system, String version) { 335 if (map.containsKey(system+"|"+version)) 336 return true; 337 String mm = VersionUtilities.getMajMin(version); 338 if (mm != null) 339 return map.containsKey(system+"|"+mm); 340 else 341 return false; 342 } 343 344 public int size() { 345 return list.size(); 346 } 347 348 public void drop(String id) { 349 CachedCanonicalResource<T> res = null; 350 do { 351 res = null; 352 for (CachedCanonicalResource<T> t : list) { 353 if (t.getId().equals(id)) { 354 res = t; 355 } 356 } 357 if (res != null) { 358 list.remove(res); 359 map.remove(id); 360 map.remove(res.getUrl()); 361 if (res.hasVersion()) { 362 map.remove(res.getUrl()+"|"+res.getVersion()); 363 String mm = VersionUtilities.getMajMin(res.getVersion()); 364 if (mm != null) { 365 map.remove(res.getUrl()+"|"+mm); 366 } 367 } 368 updateList(res.getUrl(), res.getVersion()); 369 } 370 } while (res != null); 371 } 372 373 374 public void listAll(List<T> result) { 375 for (CachedCanonicalResource<T> t : list) { 376 result.add(t.getResource()); 377 } 378 } 379 380 public void listAllM(List<CanonicalResource> result) { 381 for (CachedCanonicalResource<T> t : list) { 382 result.add(t.getResource()); 383 } 384 } 385 386 public void clear() { 387 list.clear(); 388 map.clear(); 389 390 } 391 392 public List<T> getList() { 393 List<T> res = new ArrayList<>(); 394 for (CachedCanonicalResource<T> t : list) { 395 if (!res.contains(t.getResource())) { 396 res.add(t.getResource()); 397 } 398 } 399 return res; 400 } 401 402 public List<T> getSortedList() { 403 List<T> res = getList(); 404 Collections.sort(res, new CanonicalListSorter()); 405 return res; 406 } 407 408 public Set<String> keys() { 409 return map.keySet(); 410 } 411 412 public boolean isEnforceUniqueId() { 413 return enforceUniqueId; 414 } 415 416}