001package ca.uhn.fhir.cql.r4.helper; 002 003/*- 004 * #%L 005 * HAPI FHIR JPA Server - Clinical Quality Language 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.cql.common.provider.LibraryContentProvider; 025import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider; 026import ca.uhn.fhir.rest.api.server.RequestDetails; 027import org.cqframework.cql.cql2elm.CqlTranslatorOptions; 028import org.cqframework.cql.cql2elm.LibraryManager; 029import org.cqframework.cql.cql2elm.ModelManager; 030import org.cqframework.cql.cql2elm.model.Model; 031import org.cqframework.cql.elm.execution.Library; 032import org.cqframework.cql.elm.execution.VersionedIdentifier; 033import org.hl7.fhir.r4.model.Attachment; 034import org.hl7.fhir.r4.model.CanonicalType; 035import org.hl7.fhir.r4.model.Coding; 036import org.hl7.fhir.r4.model.Measure; 037import org.hl7.fhir.r4.model.PlanDefinition; 038import org.hl7.fhir.r4.model.RelatedArtifact; 039import org.hl7.fhir.r4.model.Resource; 040import org.opencds.cqf.cql.engine.execution.LibraryLoader; 041import org.opencds.cqf.cql.evaluator.cql2elm.model.CacheAwareModelManager; 042import org.opencds.cqf.cql.evaluator.engine.execution.CacheAwareLibraryLoaderDecorator; 043import org.opencds.cqf.cql.evaluator.engine.execution.TranslatingLibraryLoader; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047import java.util.ArrayList; 048import java.util.Collections; 049import java.util.List; 050import java.util.Map; 051 052/** 053 * Created by Christopher on 1/11/2017. 054 */ 055public class LibraryHelper { 056 private static final Logger ourLog = LoggerFactory.getLogger(LibraryHelper.class); 057 058 private final Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache; 059 private final Map<VersionedIdentifier, Library> libraryCache; 060 private final CqlTranslatorOptions translatorOptions; 061 062 public LibraryHelper(Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache, 063 Map<VersionedIdentifier, Library> libraryCache, CqlTranslatorOptions translatorOptions) { 064 this.modelCache = modelCache; 065 this.libraryCache = libraryCache; 066 this.translatorOptions = translatorOptions; 067 } 068 069 public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader( 070 LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> provider) { 071 ModelManager modelManager = new CacheAwareModelManager(this.modelCache); 072 LibraryManager libraryManager = new LibraryManager(modelManager); 073 libraryManager.getLibrarySourceLoader().clearProviders(); 074 List<org.opencds.cqf.cql.evaluator.cql2elm.content.LibraryContentProvider> contentProviders = Collections 075 .singletonList(new LibraryContentProvider<org.hl7.fhir.r4.model.Library, Attachment>(provider, 076 x -> x.getContent(), x -> x.getContentType(), x -> x.getData())); 077 078 return new CacheAwareLibraryLoaderDecorator( 079 new TranslatingLibraryLoader(modelManager, contentProviders, translatorOptions), libraryCache); 080 } 081 082 public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader( 083 org.cqframework.cql.cql2elm.LibrarySourceProvider provider) { 084 ModelManager modelManager = new CacheAwareModelManager(this.modelCache); 085 LibraryManager libraryManager = new LibraryManager(modelManager); 086 libraryManager.getLibrarySourceLoader().clearProviders(); 087 088 libraryManager.getLibrarySourceLoader().registerProvider(provider); 089 090 return new CacheAwareLibraryLoaderDecorator(new TranslatingLibraryLoader(modelManager, null, translatorOptions), 091 libraryCache); 092 } 093 094 public org.hl7.fhir.r4.model.Library resolveLibraryReference( 095 LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, String reference, RequestDetails theRequestDetails) { 096 // Raw references to Library/libraryId or libraryId 097 if (reference.startsWith("Library/") || !reference.contains("/")) { 098 return libraryResourceProvider.resolveLibraryById(reference.replace("Library/", ""), theRequestDetails); 099 } 100 // Full url (e.g. http://hl7.org/fhir/us/Library/FHIRHelpers) 101 else if (reference.contains(("/Library/"))) { 102 return libraryResourceProvider.resolveLibraryByCanonicalUrl(reference, theRequestDetails); 103 } 104 105 return null; 106 } 107 108 public List<org.cqframework.cql.elm.execution.Library> loadLibraries(Measure measure, 109 LibraryLoader libraryLoader, 110 LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) { 111 List<org.cqframework.cql.elm.execution.Library> libraries = new ArrayList<org.cqframework.cql.elm.execution.Library>(); 112 113 // load libraries 114 // TODO: if there's a bad measure argument, this blows up for an obscure error 115 org.hl7.fhir.r4.model.Library primaryLibrary = null; 116 for (CanonicalType ref : measure.getLibrary()) { 117 // if library is contained in measure, load it into server 118 String id = ref.getValue(); // CanonicalHelper.getId(ref); 119 if (id.startsWith("#")) { 120 id = id.substring(1); 121 for (Resource resource : measure.getContained()) { 122 if (resource instanceof org.hl7.fhir.r4.model.Library 123 && resource.getIdElement().getIdPart().equals(id)) { 124 libraryResourceProvider.update((org.hl7.fhir.r4.model.Library) resource); 125 } 126 } 127 } 128 129 // We just loaded it into the server so we can access it by Id 130 org.hl7.fhir.r4.model.Library library = resolveLibraryReference(libraryResourceProvider, id, theRequestDetails); 131 if (primaryLibrary == null) { 132 primaryLibrary = library; 133 } 134 135 if (library != null && isLogicLibrary(library)) { 136 libraries.add(libraryLoader 137 .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()))); 138 } 139 } 140 141 if (libraries.isEmpty()) { 142 throw new IllegalArgumentException(Msg.code(1677) + String.format("Could not load library source for libraries referenced in %s.", measure.getId())); 143 } 144 145 for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) { 146 if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) 147 && artifact.hasResource()) { 148 org.hl7.fhir.r4.model.Library library = null; 149 library = resolveLibraryReference(libraryResourceProvider, artifact.getResource(), theRequestDetails); 150 151 if (library != null) { 152 if (isLogicLibrary(library)) { 153 libraries.add(libraryLoader 154 .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()))); 155 } else { 156 ourLog.warn("Library {} not included as part of evaluation context. Only Libraries with the 'logic-library' type are included.", library.getId()); 157 } 158 } 159 } 160 } 161 162 return libraries; 163 } 164 165 private boolean isLogicLibrary(org.hl7.fhir.r4.model.Library library) { 166 if (library == null) { 167 return false; 168 } 169 170 if (!library.hasType()) { 171 // If no type is specified, assume it is a logic library based on whether there 172 // is a CQL content element. 173 if (library.hasContent()) { 174 for (Attachment a : library.getContent()) { 175 if (a.hasContentType() 176 && (a.getContentType().equals("text/cql") || a.getContentType().equals("application/elm+xml") 177 || a.getContentType().equals("application/elm+json"))) { 178 return true; 179 } 180 } 181 } 182 return false; 183 } 184 185 if (!library.getType().hasCoding()) { 186 return false; 187 } 188 189 for (Coding c : library.getType().getCoding()) { 190 if (c.hasSystem() && c.getSystem().equals("http://terminology.hl7.org/CodeSystem/library-type") && c.hasCode() 191 && c.getCode().equals("logic-library")) { 192 return true; 193 } 194 } 195 196 return false; 197 } 198 199 public Library resolveLibraryById(String libraryId, LibraryLoader libraryLoader, 200 LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) { 201 202 org.hl7.fhir.r4.model.Library fhirLibrary = libraryResourceProvider.resolveLibraryById(libraryId, theRequestDetails); 203 return libraryLoader 204 .load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion())); 205 } 206 207 public Library resolvePrimaryLibrary(Measure measure, 208 LibraryLoader libraryLoader, 209 LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) { 210 // default is the first library reference 211 String id = CanonicalHelper.getId(measure.getLibrary().get(0)); 212 213 Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider, theRequestDetails); 214 215 if (library == null) { 216 throw new IllegalArgumentException(Msg.code(1678) + String.format("Could not resolve primary library for Measure/%s.", measure.getIdElement().getIdPart())); 217 } 218 219 return library; 220 } 221 222 public Library resolvePrimaryLibrary(PlanDefinition planDefinition, 223 LibraryLoader libraryLoader, 224 LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) { 225 String id = CanonicalHelper.getId(planDefinition.getLibrary().get(0)); 226 227 Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider, theRequestDetails); 228 229 if (library == null) { 230 throw new IllegalArgumentException(Msg.code(1679) + String.format("Could not resolve primary library for PlanDefinition/%s", 231 planDefinition.getIdElement().getIdPart())); 232 } 233 234 return library; 235 } 236}