
001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2025 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.api.svc.ResolveIdentityMode; 027import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; 028import ca.uhn.fhir.jpa.model.cross.IResourceLookup; 029import ca.uhn.fhir.jpa.model.dao.JpaPid; 030import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; 031import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 032import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 033import jakarta.annotation.Nonnull; 034import org.hl7.fhir.instance.model.api.IIdType; 035import org.slf4j.Logger; 036import org.springframework.beans.factory.annotation.Autowired; 037import org.springframework.stereotype.Service; 038 039import java.util.ArrayList; 040import java.util.Collection; 041import java.util.HashMap; 042import java.util.List; 043import java.util.Map; 044 045import static org.slf4j.LoggerFactory.getLogger; 046 047/** 048 * This service builds a map of resource ids to versions based on a SearchParameterMap. 049 * It is used by the in-memory resource-version cache to detect when resource versions have been changed by remote processes. 050 */ 051@Service 052public class ResourceVersionSvcDaoImpl implements IResourceVersionSvc { 053 private static final Logger ourLog = getLogger(ResourceVersionSvcDaoImpl.class); 054 055 @Autowired 056 DaoRegistry myDaoRegistry; 057 058 @Autowired 059 IResourceTableDao myResourceTableDao; 060 061 @Autowired 062 IIdHelperService<JpaPid> myIdHelperService; 063 064 @Autowired 065 IRequestPartitionHelperSvc myRequestPartitionHelperSvc; 066 067 @Override 068 @Nonnull 069 public ResourceVersionMap getVersionMap( 070 RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap) { 071 if (ourLog.isDebugEnabled()) { 072 ourLog.debug("About to retrieve version map for resource type: {}", theResourceName); 073 } 074 075 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceName); 076 SystemRequestDetails request = new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId); 077 078 List<IIdType> fhirIds = dao.searchForResourceIds(theSearchParamMap, request); 079 080 return ResourceVersionMap.fromIdsWithVersions(fhirIds); 081 } 082 083 /** 084 * Retrieves the latest versions for any resourceid that are found. 085 * If they are not found, they will not be contained in the returned map. 086 * The key should be the same value that was passed in to allow 087 * consumer to look up the value using the id they already have. 088 * 089 * This method should not throw, so it can safely be consumed in 090 * transactions. 091 * 092 * @param theRequestPartitionId - request partition id 093 * @param theIds - list of IIdTypes for resources of interest. 094 */ 095 @Override 096 public ResourcePersistentIdMap getLatestVersionIdsForResourceIds( 097 RequestPartitionId theRequestPartitionId, List<IIdType> theIds) { 098 ResourcePersistentIdMap idToPID = new ResourcePersistentIdMap(); 099 100 ResourcePersistentIdMap idAndPID = getIdsOfExistingResources(theRequestPartitionId, theIds); 101 idToPID.putAll(idAndPID); 102 103 return idToPID; 104 } 105 106 /** 107 * Helper method to determine if some resources exist in the DB (without throwing). 108 * Returns a set that contains the IIdType for every resource found. 109 * If it's not found, it won't be included in the set. 110 * 111 * @param theIds - list of IIdType ids (for the same resource) 112 */ 113 private ResourcePersistentIdMap getIdsOfExistingResources( 114 RequestPartitionId thePartitionId, Collection<IIdType> theIds) { 115 // these are the found Ids that were in the db 116 ResourcePersistentIdMap retVal = new ResourcePersistentIdMap(); 117 118 if (theIds == null || theIds.isEmpty()) { 119 return retVal; 120 } 121 122 Map<IIdType, IResourceLookup<JpaPid>> identities = myIdHelperService.resolveResourceIdentities( 123 thePartitionId, 124 new ArrayList<>(theIds), 125 ResolveIdentityMode.includeDeleted().cacheOk()); 126 127 Map<String, JpaPid> entriesWithoutVersion = new HashMap<>(identities.size()); 128 129 for (Map.Entry<IIdType, IResourceLookup<JpaPid>> entry : identities.entrySet()) { 130 IResourceLookup<JpaPid> lookup = entry.getValue(); 131 JpaPid persistentId = lookup.getPersistentId(); 132 retVal.put(entry.getKey(), persistentId); 133 if (persistentId.getVersion() == null) { 134 entriesWithoutVersion.put( 135 entry.getKey().toUnqualifiedVersionless().getValue(), persistentId); 136 } 137 } 138 139 // set any versions we don't already have 140 if (!entriesWithoutVersion.isEmpty()) { 141 Collection<Object[]> resourceEntries = 142 myResourceTableDao.getResourceVersionsForPid(entriesWithoutVersion.values()); 143 144 for (Object[] nextRecord : resourceEntries) { 145 // order matters! 146 JpaPid retPid = (JpaPid) nextRecord[0]; 147 String resType = (String) nextRecord[1]; 148 String fhirId = (String) nextRecord[2]; 149 Long version = (Long) nextRecord[3]; 150 JpaPid jpaPid = entriesWithoutVersion.get(resType + "/" + fhirId); 151 if (jpaPid != null) { 152 jpaPid.setVersion(version); 153 } 154 } 155 } 156 157 return retVal; 158 } 159}