001package ca.uhn.fhir.cql.dstu3.listener;
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 java.util.Collection;
024import java.util.List;
025import java.util.Map;
026
027import org.cqframework.cql.elm.execution.Library;
028import org.cqframework.cql.elm.execution.VersionedIdentifier;
029import org.hl7.fhir.instance.model.api.IIdType;
030
031import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
032import ca.uhn.fhir.jpa.cache.IResourceChangeEvent;
033import ca.uhn.fhir.jpa.cache.IResourceChangeListener;
034
035public class ElmCacheResourceChangeListener implements IResourceChangeListener {
036
037        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElmCacheResourceChangeListener.class);
038
039        private IFhirResourceDao<org.hl7.fhir.dstu3.model.Library> libraryDao;
040        private Map<VersionedIdentifier, Library> globalLibraryCache;
041
042        public ElmCacheResourceChangeListener(IFhirResourceDao<org.hl7.fhir.dstu3.model.Library> libraryDao, Map<VersionedIdentifier, Library> globalLibraryCache) {
043                this.libraryDao = libraryDao;
044                this.globalLibraryCache = globalLibraryCache;
045        }
046
047        @Override
048        public void handleInit(Collection<IIdType> theResourceIds) {
049                // Intentionally empty. Only cache ELM on eval request
050        }
051
052        @Override
053        public void handleChange(IResourceChangeEvent theResourceChangeEvent) {
054                if (theResourceChangeEvent == null) {
055                        return;
056                }
057
058                this.invalidateCacheByIds(theResourceChangeEvent.getDeletedResourceIds());
059                this.invalidateCacheByIds(theResourceChangeEvent.getUpdatedResourceIds());
060        }
061
062        private void invalidateCacheByIds(List<IIdType> theIds) {
063                if (theIds == null) {
064                        return;
065                }
066
067                for (IIdType id : theIds) {
068                        this.invalidateCacheById(id);
069                }
070        }
071
072        private void invalidateCacheById(IIdType theId) {
073                if (!theId.getResourceType().equals("Library"))  {
074                        return;
075                }
076
077                try {
078                        org.hl7.fhir.dstu3.model.Library library = this.libraryDao.read(theId);
079
080                        this.globalLibraryCache.remove(new VersionedIdentifier().withId(library.getName()).withVersion(library.getVersion()));
081                }
082                // This happens when a Library is deleted entirely so it's impossible to look up name and version.
083                catch (Exception e) {
084                        // TODO: This needs to be smarter... the issue is that ELM is cached with library name and version as the key since
085                        // that's the access path the CQL engine uses, but change notifications occur with the resource Id, which is not
086                        // necessarily tied to the resource name. In any event, if a unknown resource is deleted, clear all libraries as a workaround.
087                        // One option is to maintain a cache with multiple indices.
088                        ourLog.debug("Failed to locate resource {} to look up name and version. Clearing all libraries from cache.", theId.getValueAsString());
089                        this.globalLibraryCache.clear();
090        }
091        }
092}