001package ca.uhn.fhir.cql.dstu3.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.apache.commons.lang3.StringUtils;
028import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
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.dstu3.model.Attachment;
034import org.hl7.fhir.dstu3.model.Coding;
035import org.hl7.fhir.dstu3.model.Measure;
036import org.hl7.fhir.dstu3.model.Reference;
037import org.hl7.fhir.dstu3.model.RelatedArtifact;
038import org.hl7.fhir.dstu3.model.Resource;
039import org.opencds.cqf.cql.engine.execution.LibraryLoader;
040import org.opencds.cqf.cql.evaluator.cql2elm.model.CacheAwareModelManager;
041import org.opencds.cqf.cql.evaluator.engine.execution.CacheAwareLibraryLoaderDecorator;
042import org.opencds.cqf.cql.evaluator.engine.execution.TranslatingLibraryLoader;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046import java.util.ArrayList;
047import java.util.Collections;
048import java.util.List;
049import java.util.Map;
050
051public class LibraryHelper {
052        private static final Logger ourLog = LoggerFactory.getLogger(LibraryHelper.class);
053
054        private final Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache;
055        private final Map<VersionedIdentifier, Library> libraryCache;
056        private final CqlTranslatorOptions translatorOptions;
057
058
059        public LibraryHelper(Map<org.hl7.elm.r1.VersionedIdentifier, Model> modelCache, Map<VersionedIdentifier, Library> libraryCache, CqlTranslatorOptions translatorOptions) {
060                this.modelCache = modelCache;
061                this.libraryCache = libraryCache;
062                this.translatorOptions = translatorOptions;
063        }
064
065        public org.opencds.cqf.cql.engine.execution.LibraryLoader createLibraryLoader(
066                LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> provider) {
067                ModelManager modelManager = new CacheAwareModelManager(this.modelCache);
068
069                List<org.opencds.cqf.cql.evaluator.cql2elm.content.LibraryContentProvider> contentProviders = Collections.singletonList(new LibraryContentProvider<org.hl7.fhir.dstu3.model.Library, Attachment>(
070                        provider, x -> x.getContent(), x -> x.getContentType(), x -> x.getData()));
071
072                return new CacheAwareLibraryLoaderDecorator(new TranslatingLibraryLoader(modelManager, contentProviders, translatorOptions), libraryCache);
073        }
074
075        public List<Library> loadLibraries(Measure measure,
076                                                                                                  LibraryLoader libraryLoader,
077                                                                                                  LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) {
078                List<org.cqframework.cql.elm.execution.Library> libraries = new ArrayList<Library>();
079                List<String> messages = new ArrayList<>();
080
081                // load libraries
082                //TODO: if there's a bad measure argument, this blows up for an obscure error
083                for (Reference ref : measure.getLibrary()) {
084                        // if library is contained in measure, load it into server
085                        if (ref.getReferenceElement().getIdPart().startsWith("#")) {
086                                for (Resource resource : measure.getContained()) {
087                                        if (resource instanceof org.hl7.fhir.dstu3.model.Library && resource.getIdElement().getIdPart()
088                                                .equals(ref.getReferenceElement().getIdPart().substring(1))) {
089                                                libraryResourceProvider.update((org.hl7.fhir.dstu3.model.Library) resource);
090                                        }
091                                }
092                        }
093
094                        // We just loaded it into the server so we can access it by Id
095                        String id = ref.getReferenceElement().getIdPart();
096                        if (id.startsWith("#")) {
097                                id = id.substring(1);
098                        }
099
100                        org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(id, theRequestDetails);
101                        if (library != null) {
102                                if (isLogicLibrary(library)) {
103                                        libraries.add(libraryLoader
104                                                .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())));
105                                } else {
106                                        String message = "Skipping library " + library.getId() + " is not a logic library.  Probably missing type.coding.system=\"http://hl7.org/fhir/library-type\"";
107                                        messages.add(message);
108                                        ourLog.warn(message);
109                                }
110                        }
111                }
112
113                if (libraries.isEmpty()) {
114                        throw new IllegalArgumentException(Msg.code(1651) + String
115                                .format("Could not load library source for libraries referenced in %s:\n%s", measure.getId(), StringUtils.join("\n", messages)));
116                }
117
118                VersionedIdentifier primaryLibraryId = libraries.get(0).getIdentifier();
119                org.hl7.fhir.dstu3.model.Library primaryLibrary = libraryResourceProvider.resolveLibraryByName(primaryLibraryId.getId(), primaryLibraryId.getVersion());
120                for (RelatedArtifact artifact : primaryLibrary.getRelatedArtifact()) {
121                        if (artifact.hasType() && artifact.getType().equals(RelatedArtifact.RelatedArtifactType.DEPENDSON) && artifact.hasResource() && artifact.getResource().hasReference()) {
122                                if (artifact.getResource().getReferenceElement().getResourceType().equals("Library")) {
123                                        org.hl7.fhir.dstu3.model.Library library = libraryResourceProvider.resolveLibraryById(artifact.getResource().getReferenceElement().getIdPart(), theRequestDetails);
124                                        if (library != null) {
125                                                if (isLogicLibrary(library)) {
126                                                        libraries.add(libraryLoader
127                                                                .load(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion())));
128                                                } else {
129                                                        ourLog.warn("Library {} not included as part of evaluation context. Only Libraries with the 'logic-library' type are included.", library.getId());
130                                                }
131                                        }
132                                }
133                        }
134                }
135
136                return libraries;
137        }
138
139        private boolean isLogicLibrary(org.hl7.fhir.dstu3.model.Library library) {
140                if (library == null) {
141                        return false;
142                }
143
144                if (!library.hasType()) {
145                        // If no type is specified, assume it is a logic library based on whether there is a CQL content element.
146                        if (library.hasContent()) {
147                                for (Attachment a : library.getContent()) {
148                                        if (a.hasContentType() && (a.getContentType().equals("text/cql")
149                                                || a.getContentType().equals("application/elm+xml")
150                                                || a.getContentType().equals("application/elm+json"))) {
151                                                return true;
152                                        }
153                                }
154                        }
155                        return false;
156                }
157
158                if (!library.getType().hasCoding()) {
159                        return false;
160                }
161
162                for (Coding c : library.getType().getCoding()) {
163                        if (c.hasSystem() && c.getSystem().equals("http://hl7.org/fhir/library-type")
164                                && c.hasCode() && c.getCode().equals("logic-library")) {
165                                return true;
166                        }
167                }
168
169                return false;
170        }
171
172        public Library resolveLibraryById(String libraryId,
173                                                                                                 LibraryLoader libraryLoader,
174                                                                                                 LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) {
175                // Library library = null;
176
177                org.hl7.fhir.dstu3.model.Library fhirLibrary = libraryResourceProvider.resolveLibraryById(libraryId, theRequestDetails);
178                return libraryLoader
179                        .load(new VersionedIdentifier().withId(fhirLibrary.getName()).withVersion(fhirLibrary.getVersion()));
180        }
181
182        public Library resolvePrimaryLibrary(Measure measure,
183                                                                                                         LibraryLoader libraryLoader,
184                                                                                                         LibraryResolutionProvider<org.hl7.fhir.dstu3.model.Library> libraryResourceProvider, RequestDetails theRequestDetails) {
185                // default is the first library reference
186                String id = measure.getLibraryFirstRep().getReferenceElement().getIdPart();
187
188                Library library = resolveLibraryById(id, libraryLoader, libraryResourceProvider, theRequestDetails);
189
190                if (library == null) {
191                        throw new IllegalArgumentException(Msg.code(1652) + String.format("Could not resolve primary library for Measure/%s.",
192                                measure.getIdElement().getIdPart()));
193                }
194
195                return library;
196        }
197}