001package org.hl7.fhir.r4b.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.r4b.context.CanonicalResourceManager.CanonicalResourceProxy; 014import org.hl7.fhir.r4b.context.IWorkerContext.PackageVersion; 015import org.hl7.fhir.r4b.model.CanonicalResource; 016import org.hl7.fhir.r4b.model.CodeSystem; 017import org.hl7.fhir.r4b.model.DomainResource; 018import org.hl7.fhir.r4b.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 synchronized (this) { 128 resource = res; 129 } 130 proxy = null; 131 } 132 return resource; 133 } 134 135 public PackageVersion getPackageInfo() { 136 return packageInfo; 137 } 138 public String getUrl() { 139 return resource != null ? resource.getUrl() : proxy.getUrl(); 140 } 141 public String getId() { 142 return resource != null ? resource.getId() : proxy.getId(); 143 } 144 public String getVersion() { 145 return resource != null ? resource.getVersion() : proxy.getVersion(); 146 } 147 public boolean hasVersion() { 148 return resource != null ? resource.hasVersion() : proxy.getVersion() != null; 149 } 150 151 @Override 152 public String toString() { 153 return resource != null ? resource.fhirType()+"/"+resource.getId()+": "+resource.getUrl()+"|"+resource.getVersion() : proxy.toString(); 154 } 155 156 } 157 158 public class MetadataResourceVersionComparator<T1 extends CachedCanonicalResource<T>> implements Comparator<T1> { 159 @Override 160 public int compare(T1 arg1, T1 arg2) { 161 String v1 = arg1.getVersion(); 162 String v2 = arg2.getVersion(); 163 if (v1 == null && v2 == null) { 164 return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order 165 } else if (v1 == null) { 166 return -1; 167 } else if (v2 == null) { 168 return 1; 169 } else { 170 String mm1 = VersionUtilities.getMajMin(v1); 171 String mm2 = VersionUtilities.getMajMin(v2); 172 if (mm1 == null || mm2 == null) { 173 return v1.compareTo(v2); 174 } else { 175 return mm1.compareTo(mm2); 176 } 177 } 178 } 179 } 180 181 private boolean enforceUniqueId; 182 private List<CachedCanonicalResource<T>> list = new ArrayList<>(); 183 private Map<String, CachedCanonicalResource<T>> map = new HashMap<>(); 184 185 186 public CanonicalResourceManager(boolean enforceUniqueId) { 187 super(); 188 this.enforceUniqueId = enforceUniqueId; 189 } 190 191 public void copy(CanonicalResourceManager<T> source) { 192 list.clear(); 193 map.clear(); 194 list.addAll(source.list); 195 map.putAll(source.map); 196 } 197 198 public void register(CanonicalResourceProxy r, PackageVersion packgeInfo) { 199 if (!r.hasId()) { 200 throw new FHIRException("An id is required for a deferred load resource"); 201 } 202 CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo); 203 see(cr); 204 } 205 206 public void see(T r, PackageVersion packgeInfo) { 207 if (r != null) { 208 if (!r.hasId()) { 209 r.setId(UUID.randomUUID().toString()); 210 } 211 CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo); 212 see(cr); 213 } 214 } 215 216 public void see(CachedCanonicalResource<T> cr) { 217 // ignore UTG NUCC erroneous code system 218 if (cr.getPackageInfo() != null && cr.getPackageInfo().getId() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology") && "http://nucc.org/provider-taxonomy".equals(cr.getUrl())) { 219 return; 220 } 221 222 if (enforceUniqueId && map.containsKey(cr.getId())) { 223 drop(cr.getId()); 224 } 225 226 // special case logic for UTG support prior to version 5 227 if (cr.getPackageInfo() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology")) { 228 List<CachedCanonicalResource<T>> toDrop = new ArrayList<>(); 229 for (CachedCanonicalResource<T> n : list) { 230 if (n.getUrl() != null && n.getUrl().equals(cr.getUrl()) && isBasePackage(n.getPackageInfo())) { 231 toDrop.add(n); 232 } 233 } 234 for (CachedCanonicalResource<T> n : toDrop) { 235 drop(n.getId()); 236 } 237 } 238 CachedCanonicalResource<T> existing = cr.hasVersion() ? map.get(cr.getUrl()+"|"+cr.getVersion()) : map.get(cr.getUrl()+"|#0"); 239 if (map.get(cr.getUrl()) != null && (cr.getPackageInfo() != null && cr.getPackageInfo().isExamplesPackage())) { 240 return; 241 } 242 if (existing != null) { 243 list.remove(existing); 244 } 245 246 list.add(cr); 247 map.put(cr.getId(), cr); // we do this so we can drop by id 248 map.put(cr.getUrl(), cr); 249 250 if (cr.getUrl() != null) { 251 // first, this is the correct reosurce for this version (if it has a version) 252 if (cr.hasVersion()) { 253 map.put(cr.getUrl()+"|"+cr.getVersion(), cr); 254 } else { 255 map.put(cr.getUrl()+"|#0", cr); 256 } 257 updateList(cr.getUrl(), cr.getVersion()); 258 } 259 } 260 261 private boolean isBasePackage(PackageVersion packageInfo) { 262 return packageInfo == null ? false : VersionUtilities.isCorePackage(packageInfo.getId()); 263 } 264 265 private void updateList(String url, String version) { 266 List<CachedCanonicalResource<T>> rl = new ArrayList<>(); 267 for (CachedCanonicalResource<T> t : list) { 268 if (url.equals(t.getUrl()) && !rl.contains(t)) { 269 rl.add(t); 270 } 271 } 272 if (rl.size() > 0) { 273 // sort by version as much as we are able 274 Collections.sort(rl, new MetadataResourceVersionComparator<CachedCanonicalResource<T>>()); 275 // the current is the latest 276 map.put(url, rl.get(rl.size()-1)); 277 // now, also, the latest for major/minor 278 if (version != null) { 279 CachedCanonicalResource<T> latest = null; 280 for (CachedCanonicalResource<T> t : rl) { 281 if (VersionUtilities.versionsCompatible(t.getVersion(), version)) { 282 latest = t; 283 } 284 } 285 if (latest != null) { // might be null if it's not using semver 286 String lv = VersionUtilities.getMajMin(latest.getVersion()); 287 if (lv != null && !lv.equals(version)) 288 map.put(url+"|"+lv, rl.get(rl.size()-1)); 289 } 290 } 291 } 292 } 293 294 295 public T get(String url) { 296 return map.containsKey(url) ? map.get(url).getResource() : null; 297 } 298 299 public PackageVersion getPackageInfo(String system, String version) { 300 if (version == null) { 301 return map.containsKey(system) ? map.get(system).getPackageInfo() : null; 302 } else { 303 if (map.containsKey(system+"|"+version)) 304 return map.get(system+"|"+version).getPackageInfo(); 305 String mm = VersionUtilities.getMajMin(version); 306 if (mm != null && map.containsKey(system+"|"+mm)) 307 return map.get(system+"|"+mm).getPackageInfo(); 308 else 309 return null; 310 } 311 } 312 313 public boolean has(String url) { 314 return map.containsKey(url); 315 } 316 317 public T get(String system, String version) { 318 if (version == null) { 319 return get(system); 320 } else { 321 if (map.containsKey(system+"|"+version)) 322 return map.get(system+"|"+version).getResource(); 323 String mm = VersionUtilities.getMajMin(version); 324 if (mm != null && map.containsKey(system+"|"+mm)) 325 return map.get(system+"|"+mm).getResource(); 326 else 327 return null; 328 } 329 } 330 331 public boolean has(String system, String version) { 332 if (map.containsKey(system+"|"+version)) 333 return true; 334 String mm = VersionUtilities.getMajMin(version); 335 if (mm != null) 336 return map.containsKey(system+"|"+mm); 337 else 338 return false; 339 } 340 341 public int size() { 342 return list.size(); 343 } 344 345 public void drop(String id) { 346 CachedCanonicalResource<T> res = null; 347 do { 348 res = null; 349 for (CachedCanonicalResource<T> t : list) { 350 if (t.getId().equals(id)) { 351 res = t; 352 } 353 } 354 if (res != null) { 355 list.remove(res); 356 map.remove(id); 357 map.remove(res.getUrl()); 358 if (res.hasVersion()) { 359 map.remove(res.getUrl()+"|"+res.getVersion()); 360 String mm = VersionUtilities.getMajMin(res.getVersion()); 361 if (mm != null) { 362 map.remove(res.getUrl()+"|"+mm); 363 } 364 } 365 updateList(res.getUrl(), res.getVersion()); 366 } 367 } while (res != null); 368 } 369 370 371 public void listAll(List<T> result) { 372 for (CachedCanonicalResource<T> t : list) { 373 result.add(t.getResource()); 374 } 375 } 376 377 public void listAllM(List<CanonicalResource> result) { 378 for (CachedCanonicalResource<T> t : list) { 379 result.add(t.getResource()); 380 } 381 } 382 383 public void clear() { 384 list.clear(); 385 map.clear(); 386 387 } 388 389 public List<T> getList() { 390 List<T> res = new ArrayList<>(); 391 for (CachedCanonicalResource<T> t : list) { 392 if (!res.contains(t.getResource())) { 393 res.add(t.getResource()); 394 } 395 } 396 return res; 397 } 398 399 public List<T> getSortedList() { 400 List<T> res = getList(); 401 Collections.sort(res, new CanonicalListSorter()); 402 return res; 403 } 404 405 public Set<String> keys() { 406 return map.keySet(); 407 } 408 409 public boolean isEnforceUniqueId() { 410 return enforceUniqueId; 411 } 412 413}