org.hl7.fhir.r5.context.CanonicalResourceManager Maven / Gradle / Ivy
package org.hl7.fhir.r5.context;
import java.util.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
import org.hl7.fhir.r5.model.PackageInformation;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.VersionUtilities;
/**
* 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
* note, though, that not all resources have semver versions
*
* @author graha
*
*/
@MarkedToMoveToAdjunctPackage
public class CanonicalResourceManager {
private final String[] INVALID_TERMINOLOGY_URLS = {
"http://snomed.info/sct",
"http://dicom.nema.org/resources/ontology/DCM",
"http://nucc.org/provider-taxonomy"
};
public static abstract class CanonicalResourceProxy {
private String type;
private String id;
private String url;
private String version;
private String supplements;
private String derivation;
private CanonicalResource resource;
private boolean hacked;
private String content;
public CanonicalResourceProxy(String type, String id, String url, String version, String supplements, String derivation, String content) {
super();
this.type = type;
this.id = id;
this.url = url;
this.version = version;
this.supplements = supplements;
this.content = content;
this.derivation = derivation;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
public boolean hasId() {
return id != null;
}
public boolean hasUrl() {
return url != null;
}
public boolean hasVersion() {
return version != null;
}
public String getSupplements() {
return supplements;
}
public String getContent() {
return content;
}
public String getDerivation() {
return derivation;
}
public void setDerivation(String derivation) {
this.derivation = derivation;
}
public CanonicalResource getResource() throws FHIRException {
if (resource == null) {
resource = loadResource();
if (hacked) {
resource.setUrl(url).setVersion(version);
}
if (resource instanceof CodeSystem) {
CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) resource);
}
}
return resource;
}
public void setResource(CanonicalResource resource) {
this.resource = resource;
}
public abstract CanonicalResource loadResource() throws FHIRException;
@Override
public String toString() {
return type+"/"+id+": "+url+"|"+version;
}
public void hack(String url, String version) {
this.url = url;
this.version = version;
this.hacked = true;
}
}
public static class CanonicalListSorter implements Comparator {
@Override
public int compare(CanonicalResource arg0, CanonicalResource arg1) {
String u0 = arg0.getUrl();
String u1 = arg1.getUrl();
return u0.compareTo(u1);
}
}
public class CachedCanonicalResource {
private T1 resource;
private CanonicalResourceProxy proxy;
private PackageInformation packageInfo;
public CachedCanonicalResource(T1 resource, PackageInformation packageInfo) {
super();
this.resource = resource;
this.packageInfo = packageInfo;
}
public CachedCanonicalResource(CanonicalResourceProxy proxy, PackageInformation packageInfo) {
super();
this.proxy = proxy;
this.packageInfo = packageInfo;
}
public T1 getResource() {
if (resource == null) {
@SuppressWarnings("unchecked")
T1 res = (T1) proxy.getResource();
if (res == null) {
throw new Error("Proxy loading a resource from "+packageInfo+" failed and returned null");
}
synchronized (this) {
resource = res;
}
resource.setSourcePackage(packageInfo);
proxy = null;
}
return resource;
}
public PackageInformation getPackageInfo() {
return packageInfo;
}
public String getUrl() {
return resource != null ? resource.getUrl() : proxy.getUrl();
}
public String getId() {
return resource != null ? resource.getId() : proxy.getId();
}
public String getVersion() {
return resource != null ? resource.getVersion() : proxy.getVersion();
}
public boolean hasVersion() {
return resource != null ? resource.hasVersion() : proxy.getVersion() != null;
}
public String getContent() {
if (resource != null && resource instanceof CodeSystem) {
CodeSystemContentMode cnt = ((CodeSystem) resource).getContent();
return cnt == null ? null : cnt.toCode();
} else if (proxy != null) {
return proxy.getContent();
} else {
return null;
}
}
@Override
public String toString() {
return resource != null ? resource.fhirType()+"/"+resource.getId()+"["+resource.getUrl()+"|"+resource.getVersion()+"]" : proxy.toString();
}
public String supplements() {
if (resource == null) {
return proxy.getSupplements();
} else {
return resource instanceof CodeSystem ? ((CodeSystem) resource).getSupplements() : null;
}
}
public Object getDerivation() {
if (resource == null) {
return proxy.getDerivation();
} else {
return resource instanceof StructureDefinition ? ((StructureDefinition) resource).getDerivationElement().primitiveValue() : null;
}
}
public void unload() {
if (proxy != null) {
resource = null;
}
}
}
public class MetadataResourceVersionComparator> implements Comparator {
@Override
public int compare(T1 arg1, T1 arg2) {
String c1 = arg1.getContent();
String c2 = arg2.getContent();
if (c1 != null && c2 != null && !c1.equals(c2)) {
int i1 = orderOfContent(c1);
int i2 = orderOfContent(c2);
return Integer.compare(i1, i2);
}
String v1 = arg1.getVersion();
String v2 = arg2.getVersion();
if (v1 == null && v2 == null) {
return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order
} else if (v1 == null) {
return -1;
} else if (v2 == null) {
return 1;
} else {
String mm1 = VersionUtilities.getMajMin(v1);
String mm2 = VersionUtilities.getMajMin(v2);
if (mm1 == null || mm2 == null) {
return v1.compareTo(v2);
} else {
return mm1.compareTo(mm2);
}
}
}
private int orderOfContent(String c) {
switch (c) {
case "not-present": return 1;
case "example": return 2;
case "fragment": return 3;
case "complete": return 5;
case "supplement": return 4;
}
return 0;
}
}
private boolean minimalMemory;
private boolean enforceUniqueId;
private List> list = new ArrayList<>();
private Map>> listForId;
private Map>> listForUrl;
private Map> map;
private Map>> supplements; // general index based on CodeSystem.supplements
private String version; // for debugging purposes
public CanonicalResourceManager(boolean enforceUniqueId, boolean minimalMemory) {
super();
this.enforceUniqueId = enforceUniqueId;
this.minimalMemory = minimalMemory;
list = new ArrayList<>();
listForId = new HashMap<>();
listForUrl = new HashMap<>();
map = new HashMap<>();
supplements = new HashMap<>(); // general index based on CodeSystem.supplements
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public void copy(CanonicalResourceManager source) {
list.clear();
map.clear();
list.addAll(source.list);
map.putAll(source.map);
}
public void register(CanonicalResourceProxy r, PackageInformation packgeInfo) {
if (!r.hasId()) {
throw new FHIRException("An id is required for a deferred load resource");
}
CanonicalResourceManager.CachedCanonicalResource cr = new CachedCanonicalResource(r, packgeInfo);
see(cr);
}
public void see(T r, PackageInformation packgeInfo) {
if (r != null) {
if (!r.hasId()) {
r.setId(UUID.randomUUID().toString());
}
CanonicalResourceManager.CachedCanonicalResource cr = new CachedCanonicalResource(r, packgeInfo);
see(cr);
}
}
public void see(CachedCanonicalResource cr) {
// -- 1. exit conditions -----------------------------------------------------------------------------
// ignore UTG NUCC erroneous code system
if (cr.getPackageInfo() != null
&& cr.getPackageInfo().getId() != null
&& cr.getPackageInfo().getId().startsWith("hl7.terminology")
&& Arrays.stream(INVALID_TERMINOLOGY_URLS).anyMatch((it)->it.equals(cr.getUrl()))) {
return;
}
if (map.get(cr.getUrl()) != null && (cr.getPackageInfo() != null && cr.getPackageInfo().isExamplesPackage())) {
return;
}
// -- 2. preparation -----------------------------------------------------------------------------
if (cr.resource != null && cr.getPackageInfo() != null) {
cr.resource.setSourcePackage(cr.getPackageInfo());
}
// -- 3. deleting existing content ---------------------------------------------------------------
if (enforceUniqueId && map.containsKey(cr.getId())) {
drop(cr.getId());
}
// special case logic for UTG support prior to version 5
if (cr.getPackageInfo() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology")) {
List> toDrop = new ArrayList<>();
for (CachedCanonicalResource n : list) {
if (n.getUrl() != null && n.getUrl().equals(cr.getUrl()) && isBasePackage(n.getPackageInfo())) {
toDrop.add(n);
}
}
for (CachedCanonicalResource n : toDrop) {
drop(n);
}
}
// CachedCanonicalResource existing = cr.hasVersion() ? map.get(cr.getUrl()+"|"+cr.getVersion()) : map.get(cr.getUrl()+"|#0");
// if (existing != null) {
// drop(existing); // was list.remove(existing)
// }
// -- 4. ok we add it to the list ---------------------------------------------------------------
if (!enforceUniqueId) {
if (!listForId.containsKey(cr.getId())) {
listForId.put(cr.getId(), new ArrayList<>());
}
List> set = listForId.get(cr.getId());
set.add(cr);
}
list.add(cr);
if (!listForUrl.containsKey(cr.getUrl())) {
listForUrl.put(cr.getUrl(), new ArrayList<>());
}
addToSupplements(cr);
List> set = listForUrl.get(cr.getUrl());
set.add(cr);
if (set.size() > 1) {
Collections.sort(set, new MetadataResourceVersionComparator>());
}
// -- 4. add to the map all the ways ---------------------------------------------------------------
String pv = cr.getPackageInfo() != null ? cr.getPackageInfo().getVID() : null;
map.put(cr.getId(), cr); // we do this so we can drop by id - if not enforcing id, it's just the most recent resource with this id
map.put(cr.hasVersion() ? cr.getUrl()+"|"+cr.getVersion() : cr.getUrl()+"|#0", cr);
if (pv != null) {
map.put(pv+":"+(cr.hasVersion() ? cr.getUrl()+"|"+cr.getVersion() : cr.getUrl()+"|#0"), cr);
}
int ndx = set.indexOf(cr);
if (ndx == set.size()-1) {
map.put(cr.getUrl(), cr);
if (pv != null) {
map.put(pv+":"+cr.getUrl(), cr);
}
}
String mm = VersionUtilities.getMajMin(cr.getVersion());
if (mm != null) {
if (pv != null) {
map.put(pv+":"+cr.getUrl()+"|"+mm, cr);
}
if (set.size() - 1 == ndx) {
map.put(cr.getUrl()+"|"+mm, cr);
} else {
for (int i = set.size() - 1; i > ndx; i--) {
if (mm.equals(VersionUtilities.getMajMin(set.get(i).getVersion()))) {
return;
}
map.put(cr.getUrl()+"|"+mm, cr);
}
}
}
}
private void addToSupplements(CanonicalResourceManager.CachedCanonicalResource cr) {
String surl = cr.supplements();
if (surl != null) {
List.CachedCanonicalResource> list = supplements.get(surl);
if (list == null) {
list = new ArrayList<>();
supplements.put(surl, list);
}
list.add(cr);
}
}
public void drop(CachedCanonicalResource cr) {
while (map.values().remove(cr));
while (listForId.values().remove(cr));
while (listForUrl.values().remove(cr));
String surl = cr.supplements();
if (surl != null) {
supplements.get(surl).remove(cr);
}
list.remove(cr);
List> set = listForUrl.get(cr.getUrl());
if (set != null) { // it really should be
boolean last = set.indexOf(cr) == set.size()-1;
set.remove(cr);
if (!set.isEmpty()) {
CachedCanonicalResource crl = set.get(set.size()-1);
if (last) {
map.put(crl.getUrl(), crl);
}
String mm = VersionUtilities.getMajMin(cr.getVersion());
if (mm != null) {
for (int i = set.size()-1; i >= 0; i--) {
if (mm.equals(VersionUtilities.getMajMin(set.get(i).getVersion()))) {
map.put(cr.getUrl()+"|"+mm, set.get(i));
break;
}
}
}
}
}
}
public void drop(String id) {
if (enforceUniqueId) {
CachedCanonicalResource cr = map.get(id);
if (cr != null) {
drop(cr);
}
} else {
List> set = listForId.get(id);
if (set != null) { // it really should be
for (CachedCanonicalResource i : set) {
drop(i);
}
}
}
}
private boolean isBasePackage(PackageInformation packageInfo) {
return packageInfo == null ? false : VersionUtilities.isCorePackage(packageInfo.getId());
}
private void updateList(String url, String version) {
List> rl = new ArrayList<>();
for (CachedCanonicalResource t : list) {
if (url.equals(t.getUrl()) && !rl.contains(t)) {
rl.add(t);
}
}
if (rl.size() > 0) {
// sort by version as much as we are able
// the current is the latest
map.put(url, rl.get(rl.size()-1));
// now, also, the latest for major/minor
if (version != null) {
CachedCanonicalResource latest = null;
for (CachedCanonicalResource t : rl) {
if (VersionUtilities.versionsCompatible(t.getVersion(), version)) {
latest = t;
}
}
if (latest != null) { // might be null if it's not using semver
String lv = VersionUtilities.getMajMin(latest.getVersion());
if (lv != null && !lv.equals(version))
map.put(url+"|"+lv, rl.get(rl.size()-1));
}
}
}
}
public boolean has(String url) {
return map.containsKey(url);
}
public boolean has(String system, String version) {
if (map.containsKey(system+"|"+version))
return true;
String mm = VersionUtilities.getMajMin(version);
if (mm != null)
return map.containsKey(system+"|"+mm);
else
return false;
}
public T get(String url) {
return map.containsKey(url) ? map.get(url).getResource() : null;
}
public T get(String system, String version) {
if (version == null) {
return get(system);
} else {
if (map.containsKey(system+"|"+version))
return map.get(system+"|"+version).getResource();
String mm = VersionUtilities.getMajMin(version);
if (mm != null && map.containsKey(system+"|"+mm))
return map.get(system+"|"+mm).getResource();
else
return null;
}
}
public List getForUrl(String url) {
List res = new ArrayList<>();
List.CachedCanonicalResource> list = listForUrl.get(url);
if (list != null) {
for (CanonicalResourceManager.CachedCanonicalResource t : list) {
res.add(t.getResource());
}
}
return res;
}
/**
* This is asking for a packaged version aware resolution
*
* if we can resolve the reference in the package dependencies, we will. if we can't
* then we fall back to the non-package approach
*
* The context has to prepare the pvlist based on the original package
* @param url
* @param srcInfo
* @return
*/
public T get(String url, List pvlist) {
for (String pv : pvlist) {
if (map.containsKey(pv+":"+url)) {
return map.get(pv+":"+url).getResource();
}
}
return map.containsKey(url) ? map.get(url).getResource() : null;
}
public T get(String system, String version, List pvlist) {
if (version == null) {
return get(system, pvlist);
} else {
for (String pv : pvlist) {
if (map.containsKey(pv+":"+system+"|"+version))
return map.get(pv+":"+system+"|"+version).getResource();
}
String mm = VersionUtilities.getMajMin(version);
if (mm != null && map.containsKey(system+"|"+mm))
for (String pv : pvlist) {
if (map.containsKey(pv+":"+system+"|"+mm))
return map.get(pv+":"+system+"|"+mm).getResource();
}
if (map.containsKey(system+"|"+version))
return map.get(system+"|"+version).getResource();
if (mm != null && map.containsKey(system+"|"+mm))
return map.get(system+"|"+mm).getResource();
else
return null;
}
}
public PackageInformation getPackageInfo(String system, String version) {
if (version == null) {
return map.containsKey(system) ? map.get(system).getPackageInfo() : null;
} else {
if (map.containsKey(system+"|"+version))
return map.get(system+"|"+version).getPackageInfo();
String mm = VersionUtilities.getMajMin(version);
if (mm != null && map.containsKey(system+"|"+mm))
return map.get(system+"|"+mm).getPackageInfo();
else
return null;
}
}
public int size() {
return list.size();
}
public void listAll(List result) {
for (CachedCanonicalResource t : list) {
result.add(t.getResource());
}
}
public void listAllM(List result) {
for (CachedCanonicalResource t : list) {
result.add(t.getResource());
}
}
public List getSupplements(T cr) {
if (cr == null) {
return new ArrayList();
}
if (cr.hasSourcePackage()) {
List pvl = new ArrayList<>();
pvl.add(cr.getSourcePackage().getVID());
return getSupplements(cr.getUrl(), cr.getVersion(), pvl);
} else {
return getSupplements(cr.getUrl(), cr.getVersion(), null);
}
}
public List getSupplements(String url) {
return getSupplements(url, null, null);
}
public List getSupplements(String url, String version) {
return getSupplements(url, version, null);
}
public List getSupplements(String url, String version, List pvlist) {
boolean possibleMatches = false;
List res = new ArrayList<>();
if (version != null) {
List.CachedCanonicalResource> list = supplements.get(url+"|"+version);
if (list != null) {
for (CanonicalResourceManager.CachedCanonicalResource t : list) {
possibleMatches = true;
if (pvlist == null || pvlist.contains(t.getPackageInfo().getVID())) {
res.add(t.getResource());
}
}
}
}
List.CachedCanonicalResource> list = supplements.get(url);
if (list != null) {
for (CanonicalResourceManager.CachedCanonicalResource t : list) {
possibleMatches = true;
if (pvlist == null || t.getPackageInfo() == null || pvlist.contains(t.getPackageInfo().getVID())) {
res.add(t.getResource());
}
}
}
if (res.isEmpty() && pvlist != null && possibleMatches) {
return getSupplements(url, version, null);
} else {
return res;
}
}
public void clear() {
list.clear();
map.clear();
}
public List> getCachedList() {
return list;
}
public List getList() {
List res = new ArrayList<>();
for (CachedCanonicalResource t : list) {
if (!res.contains(t.getResource())) {
res.add(t.getResource());
}
}
return res;
}
public List getSortedList() {
List res = getList();
Collections.sort(res, new CanonicalListSorter());
return res;
}
public Set keys() {
return map.keySet();
}
public boolean isEnforceUniqueId() {
return enforceUniqueId;
}
public void unload() {
for (CachedCanonicalResource t : list) {
t.unload();
}
}
} © 2015 - 2025 Weber Informatics LLC | Privacy Policy