001/*- 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2023 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.model.entity.ResourceTable; 029import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 030import ca.uhn.fhir.jpa.util.QueryChunker; 031import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 032import org.hl7.fhir.instance.model.api.IIdType; 033import org.slf4j.Logger; 034import org.springframework.beans.factory.annotation.Autowired; 035import org.springframework.stereotype.Service; 036import org.springframework.transaction.annotation.Transactional; 037 038import java.util.ArrayList; 039import java.util.Collection; 040import java.util.HashMap; 041import java.util.List; 042import java.util.Optional; 043import java.util.stream.Collectors; 044import javax.annotation.Nonnull; 045 046import static org.slf4j.LoggerFactory.getLogger; 047 048/** 049 * This service builds a map of resource ids to versions based on a SearchParameterMap. 050 * It is used by the in-memory resource-version cache to detect when resource versions have been changed by remote processes. 051 */ 052@Service 053public class ResourceVersionSvcDaoImpl implements IResourceVersionSvc { 054 private static final Logger ourLog = getLogger(ResourceVersionSvcDaoImpl.class); 055 056 @Autowired 057 DaoRegistry myDaoRegistry; 058 059 @Autowired 060 IResourceTableDao myResourceTableDao; 061 062 @Autowired 063 IIdHelperService<JpaPid> myIdHelperService; 064 065 @Override 066 @Nonnull 067 @Transactional 068 public ResourceVersionMap getVersionMap( 069 RequestPartitionId theRequestPartitionId, String theResourceName, SearchParameterMap theSearchParamMap) { 070 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceName); 071 072 if (ourLog.isDebugEnabled()) { 073 ourLog.debug("About to retrieve version map for resource type: {}", theResourceName); 074 } 075 076 List<JpaPid> jpaPids = dao.searchForIds( 077 theSearchParamMap, new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId)); 078 List<Long> matchingIds = jpaPids.stream().map(JpaPid::getId).collect(Collectors.toList()); 079 080 List<ResourceTable> allById = new ArrayList<>(); 081 new QueryChunker<Long>().chunk(matchingIds, t -> { 082 List<ResourceTable> nextBatch = myResourceTableDao.findAllById(t); 083 allById.addAll(nextBatch); 084 }); 085 086 return ResourceVersionMap.fromResourceTableEntities(allById); 087 } 088 089 @Override 090 /** 091 * Retrieves the latest versions for any resourceid that are found. 092 * If they are not found, they will not be contained in the returned map. 093 * The key should be the same value that was passed in to allow 094 * consumer to look up the value using the id they already have. 095 * 096 * This method should not throw, so it can safely be consumed in 097 * transactions. 098 * 099 * @param theRequestPartitionId - request partition id 100 * @param theIds - list of IIdTypes for resources of interest. 101 * @return 102 */ 103 public ResourcePersistentIdMap getLatestVersionIdsForResourceIds( 104 RequestPartitionId theRequestPartitionId, List<IIdType> theIds) { 105 ResourcePersistentIdMap idToPID = new ResourcePersistentIdMap(); 106 HashMap<String, List<IIdType>> resourceTypeToIds = new HashMap<>(); 107 108 for (IIdType id : theIds) { 109 String resourceType = id.getResourceType(); 110 if (!resourceTypeToIds.containsKey(resourceType)) { 111 resourceTypeToIds.put(resourceType, new ArrayList<>()); 112 } 113 resourceTypeToIds.get(resourceType).add(id); 114 } 115 116 for (String resourceType : resourceTypeToIds.keySet()) { 117 ResourcePersistentIdMap idAndPID = 118 getIdsOfExistingResources(theRequestPartitionId, resourceTypeToIds.get(resourceType)); 119 idToPID.putAll(idAndPID); 120 } 121 122 return idToPID; 123 } 124 125 /** 126 * Helper method to determine if some resources exist in the DB (without throwing). 127 * Returns a set that contains the IIdType for every resource found. 128 * If it's not found, it won't be included in the set. 129 * 130 * @param theIds - list of IIdType ids (for the same resource) 131 * @return 132 */ 133 private ResourcePersistentIdMap getIdsOfExistingResources( 134 RequestPartitionId thePartitionId, Collection<IIdType> theIds) { 135 // these are the found Ids that were in the db 136 ResourcePersistentIdMap retval = new ResourcePersistentIdMap(); 137 138 if (theIds == null || theIds.isEmpty()) { 139 return retval; 140 } 141 142 List<JpaPid> jpaPids = 143 myIdHelperService.resolveResourcePersistentIdsWithCache(thePartitionId, new ArrayList<>(theIds)); 144 145 // we'll use this map to fetch pids that require versions 146 HashMap<Long, JpaPid> pidsToVersionToResourcePid = new HashMap<>(); 147 148 // fill in our map 149 for (JpaPid pid : jpaPids) { 150 if (pid.getVersion() == null) { 151 pidsToVersionToResourcePid.put(pid.getId(), pid); 152 } 153 Optional<IIdType> idOp = theIds.stream() 154 .filter(i -> 155 i.getIdPart().equals(pid.getAssociatedResourceId().getIdPart())) 156 .findFirst(); 157 // this should always be present 158 // since it was passed in. 159 // but land of optionals... 160 idOp.ifPresent(id -> { 161 retval.put(id, pid); 162 }); 163 } 164 165 // set any versions we don't already have 166 if (!pidsToVersionToResourcePid.isEmpty()) { 167 Collection<Object[]> resourceEntries = 168 myResourceTableDao.getResourceVersionsForPid(new ArrayList<>(pidsToVersionToResourcePid.keySet())); 169 170 for (Object[] record : resourceEntries) { 171 // order matters! 172 Long retPid = (Long) record[0]; 173 String resType = (String) record[1]; 174 Long version = (Long) record[2]; 175 pidsToVersionToResourcePid.get(retPid).setVersion(version); 176 } 177 } 178 179 return retval; 180 } 181}