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}