All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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