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}