001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.jpa.cache; 021 022import ca.uhn.fhir.interceptor.model.RequestPartitionId; 023import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 024import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 025import ca.uhn.fhir.jpa.api.svc.IIdHelperService; 026import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; 027import ca.uhn.fhir.jpa.model.dao.JpaPid; 028import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 029import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 030import jakarta.annotation.Nonnull; 031import org.hl7.fhir.instance.model.api.IIdType; 032import org.slf4j.Logger; 033import org.springframework.beans.factory.annotation.Autowired; 034import org.springframework.stereotype.Service; 035 036import java.util.ArrayList; 037import java.util.Collection; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Optional; 041 042import static org.slf4j.LoggerFactory.getLogger; 043 044/** 045 * This service builds a map of resource ids to versions based on a SearchParameterMap. 046 * It is used by the in-memory resource-version cache to detect when resource versions have been changed by remote processes. 047 */ 048@Service 049public class ResourceVersionSvcDaoImpl implements IResourceVersionSvc { 050 private static final Logger ourLog = getLogger(ResourceVersionSvcDaoImpl.class); 051 052 @Autowired 053 DaoRegistry myDaoRegistry; 054 055 @Autowired 056 IResourceTableDao myResourceTableDao; 057 058 @Autowired 059 IIdHelperService<JpaPid> myIdHelperService; 060 061 @Override 062 @Nonnull 063 public ResourceVersionMap getVersionMap( 064 RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap) { 065 if (ourLog.isDebugEnabled()) { 066 ourLog.debug("About to retrieve version map for resource type: {}", theResourceName); 067 } 068 069 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceName); 070 SystemRequestDetails request = new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId); 071 072 List<IIdType> fhirIds = dao.searchForResourceIds(theSearchParamMap, request); 073 074 return ResourceVersionMap.fromIdsWithVersions(fhirIds); 075 } 076 077 /** 078 * Retrieves the latest versions for any resourceid that are found. 079 * If they are not found, they will not be contained in the returned map. 080 * The key should be the same value that was passed in to allow 081 * consumer to look up the value using the id they already have. 082 * 083 * This method should not throw, so it can safely be consumed in 084 * transactions. 085 * 086 * @param theRequestPartitionId - request partition id 087 * @param theIds - list of IIdTypes for resources of interest. 088 */ 089 @Override 090 public ResourcePersistentIdMap getLatestVersionIdsForResourceIds( 091 RequestPartitionId theRequestPartitionId, List<IIdType> theIds) { 092 ResourcePersistentIdMap idToPID = new ResourcePersistentIdMap(); 093 HashMap<String, List<IIdType>> resourceTypeToIds = new HashMap<>(); 094 095 for (IIdType id : theIds) { 096 String resourceType = id.getResourceType(); 097 if (!resourceTypeToIds.containsKey(resourceType)) { 098 resourceTypeToIds.put(resourceType, new ArrayList<>()); 099 } 100 resourceTypeToIds.get(resourceType).add(id); 101 } 102 103 for (List<IIdType> nextIds : resourceTypeToIds.values()) { 104 ResourcePersistentIdMap idAndPID = getIdsOfExistingResources(theRequestPartitionId, nextIds); 105 idToPID.putAll(idAndPID); 106 } 107 108 return idToPID; 109 } 110 111 /** 112 * Helper method to determine if some resources exist in the DB (without throwing). 113 * Returns a set that contains the IIdType for every resource found. 114 * If it's not found, it won't be included in the set. 115 * 116 * @param theIds - list of IIdType ids (for the same resource) 117 */ 118 private ResourcePersistentIdMap getIdsOfExistingResources( 119 RequestPartitionId thePartitionId, Collection<IIdType> theIds) { 120 // these are the found Ids that were in the db 121 ResourcePersistentIdMap retval = new ResourcePersistentIdMap(); 122 123 if (theIds == null || theIds.isEmpty()) { 124 return retval; 125 } 126 127 List<JpaPid> jpaPids = 128 myIdHelperService.resolveResourcePersistentIdsWithCache(thePartitionId, new ArrayList<>(theIds)); 129 130 // we'll use this map to fetch pids that require versions 131 HashMap<Long, JpaPid> pidsToVersionToResourcePid = new HashMap<>(); 132 133 // fill in our map 134 for (JpaPid pid : jpaPids) { 135 if (pid.getVersion() == null) { 136 pidsToVersionToResourcePid.put(pid.getId(), pid); 137 } 138 Optional<IIdType> idOp = theIds.stream() 139 .filter(i -> 140 i.getIdPart().equals(pid.getAssociatedResourceId().getIdPart())) 141 .findFirst(); 142 // this should always be present 143 // since it was passed in. 144 // but land of optionals... 145 idOp.ifPresent(id -> retval.put(id, pid)); 146 } 147 148 // set any versions we don't already have 149 if (!pidsToVersionToResourcePid.isEmpty()) { 150 Collection<Object[]> resourceEntries = 151 myResourceTableDao.getResourceVersionsForPid(new ArrayList<>(pidsToVersionToResourcePid.keySet())); 152 153 for (Object[] nextRecord : resourceEntries) { 154 // order matters! 155 Long retPid = (Long) nextRecord[0]; 156 String resType = (String) nextRecord[1]; 157 Long version = (Long) nextRecord[2]; 158 pidsToVersionToResourcePid.get(retPid).setVersion(version); 159 } 160 } 161 162 return retval; 163 } 164}