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.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; 043import java.util.Optional; 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 @Override 065 @Nonnull 066 public ResourceVersionMap getVersionMap( 067 RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap) { 068 if (ourLog.isDebugEnabled()) { 069 ourLog.debug("About to retrieve version map for resource type: {}", theResourceName); 070 } 071 072 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceName); 073 SystemRequestDetails request = new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId); 074 075 List<IIdType> fhirIds = dao.searchForResourceIds(theSearchParamMap, request); 076 077 return ResourceVersionMap.fromIdsWithVersions(fhirIds); 078 } 079 080 /** 081 * Retrieves the latest versions for any resourceid that are found. 082 * If they are not found, they will not be contained in the returned map. 083 * The key should be the same value that was passed in to allow 084 * consumer to look up the value using the id they already have. 085 * 086 * This method should not throw, so it can safely be consumed in 087 * transactions. 088 * 089 * @param theRequestPartitionId - request partition id 090 * @param theIds - list of IIdTypes for resources of interest. 091 */ 092 @Override 093 public ResourcePersistentIdMap getLatestVersionIdsForResourceIds( 094 RequestPartitionId theRequestPartitionId, List<IIdType> theIds) { 095 ResourcePersistentIdMap idToPID = new ResourcePersistentIdMap(); 096 097 ResourcePersistentIdMap idAndPID = getIdsOfExistingResources(theRequestPartitionId, theIds); 098 idToPID.putAll(idAndPID); 099 100 return idToPID; 101 } 102 103 /** 104 * Helper method to determine if some resources exist in the DB (without throwing). 105 * Returns a set that contains the IIdType for every resource found. 106 * If it's not found, it won't be included in the set. 107 * 108 * @param theIds - list of IIdType ids (for the same resource) 109 */ 110 private ResourcePersistentIdMap getIdsOfExistingResources( 111 RequestPartitionId thePartitionId, Collection<IIdType> theIds) { 112 // these are the found Ids that were in the db 113 ResourcePersistentIdMap retval = new ResourcePersistentIdMap(); 114 115 if (theIds == null || theIds.isEmpty()) { 116 return retval; 117 } 118 119 Map<IIdType, IResourceLookup<JpaPid>> identities = myIdHelperService.resolveResourceIdentities( 120 thePartitionId, 121 new ArrayList<>(theIds), 122 ResolveIdentityMode.includeDeleted().cacheOk()); 123 124 // we'll use this map to fetch pids that require versions 125 HashMap<Long, JpaPid> pidsToVersionToResourcePid = new HashMap<>(); 126 127 // fill in our map 128 for (IResourceLookup<JpaPid> identity : identities.values()) { 129 JpaPid pid = identity.getPersistentId(); 130 if (pid.getVersion() == null) { 131 pidsToVersionToResourcePid.put(pid.getId(), pid); 132 } 133 Optional<IIdType> idOp = theIds.stream() 134 .filter(i -> 135 i.getIdPart().equals(pid.getAssociatedResourceId().getIdPart())) 136 .findFirst(); 137 // this should always be present 138 // since it was passed in. 139 // but land of optionals... 140 idOp.ifPresent(id -> retval.put(id, pid)); 141 } 142 143 // set any versions we don't already have 144 if (!pidsToVersionToResourcePid.isEmpty()) { 145 Collection<Object[]> resourceEntries = 146 myResourceTableDao.getResourceVersionsForPid(new ArrayList<>(pidsToVersionToResourcePid.keySet())); 147 148 for (Object[] nextRecord : resourceEntries) { 149 // order matters! 150 Long retPid = (Long) nextRecord[0]; 151 String resType = (String) nextRecord[1]; 152 Long version = (Long) nextRecord[2]; 153 pidsToVersionToResourcePid.get(retPid).setVersion(version); 154 } 155 } 156 157 return retval; 158 } 159}