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