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}