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.reindex; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.context.RuntimeResourceDefinition; 024import ca.uhn.fhir.i18n.Msg; 025import ca.uhn.fhir.interceptor.model.RequestPartitionId; 026import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 027import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 028import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 029import ca.uhn.fhir.jpa.api.pid.EmptyResourcePidList; 030import ca.uhn.fhir.jpa.api.pid.HomogeneousResourcePidList; 031import ca.uhn.fhir.jpa.api.pid.IResourcePidList; 032import ca.uhn.fhir.jpa.api.pid.MixedResourcePidList; 033import ca.uhn.fhir.jpa.api.svc.IBatch2DaoSvc; 034import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; 035import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService; 036import ca.uhn.fhir.jpa.model.dao.JpaPid; 037import ca.uhn.fhir.jpa.searchparam.MatchUrlService; 038import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 039import ca.uhn.fhir.rest.api.Constants; 040import ca.uhn.fhir.rest.api.SortOrderEnum; 041import ca.uhn.fhir.rest.api.SortSpec; 042import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 043import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 044import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 045import org.springframework.data.domain.Pageable; 046import org.springframework.data.domain.Slice; 047 048import java.util.ArrayList; 049import java.util.Date; 050import java.util.List; 051import java.util.stream.Collectors; 052import javax.annotation.Nonnull; 053import javax.annotation.Nullable; 054 055public class Batch2DaoSvcImpl implements IBatch2DaoSvc { 056 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Batch2DaoSvcImpl.class); 057 058 private final IResourceTableDao myResourceTableDao; 059 060 private final MatchUrlService myMatchUrlService; 061 062 private final DaoRegistry myDaoRegistry; 063 064 private final FhirContext myFhirContext; 065 066 private final IHapiTransactionService myTransactionService; 067 068 private final JpaStorageSettings myJpaStorageSettings; 069 070 @Override 071 public boolean isAllResourceTypeSupported() { 072 return true; 073 } 074 075 public Batch2DaoSvcImpl( 076 IResourceTableDao theResourceTableDao, 077 MatchUrlService theMatchUrlService, 078 DaoRegistry theDaoRegistry, 079 FhirContext theFhirContext, 080 IHapiTransactionService theTransactionService, 081 JpaStorageSettings theJpaStorageSettings) { 082 myResourceTableDao = theResourceTableDao; 083 myMatchUrlService = theMatchUrlService; 084 myDaoRegistry = theDaoRegistry; 085 myFhirContext = theFhirContext; 086 myTransactionService = theTransactionService; 087 myJpaStorageSettings = theJpaStorageSettings; 088 } 089 090 @Override 091 public IResourcePidList fetchResourceIdsPage( 092 Date theStart, Date theEnd, @Nullable RequestPartitionId theRequestPartitionId, @Nullable String theUrl) { 093 return myTransactionService 094 .withSystemRequest() 095 .withRequestPartitionId(theRequestPartitionId) 096 .execute(() -> { 097 if (theUrl == null) { 098 return fetchResourceIdsPageNoUrl(theStart, theEnd, theRequestPartitionId); 099 } else { 100 return fetchResourceIdsPageWithUrl(theEnd, theUrl, theRequestPartitionId); 101 } 102 }); 103 } 104 105 @Nonnull 106 private HomogeneousResourcePidList fetchResourceIdsPageWithUrl( 107 Date theEnd, @Nonnull String theUrl, @Nullable RequestPartitionId theRequestPartitionId) { 108 if (!theUrl.contains("?")) { 109 throw new InternalErrorException(Msg.code(2422) + "this should never happen: URL is missing a '?'"); 110 } 111 112 final Integer internalSynchronousSearchSize = myJpaStorageSettings.getInternalSynchronousSearchSize(); 113 114 if (internalSynchronousSearchSize == null || internalSynchronousSearchSize <= 0) { 115 throw new InternalErrorException(Msg.code(2423) 116 + "this should never happen: internalSynchronousSearchSize is null or less than or equal to 0"); 117 } 118 119 List<IResourcePersistentId> currentIds = fetchResourceIdsPageWithUrl(0, theUrl, theRequestPartitionId); 120 ourLog.debug("FIRST currentIds: {}", currentIds.size()); 121 122 final List<IResourcePersistentId> allIds = new ArrayList<>(currentIds); 123 124 while (internalSynchronousSearchSize < currentIds.size()) { 125 // Ensure the offset is set to the last ID in the cumulative List, otherwise, we'll be stuck in an infinite 126 // loop here: 127 currentIds = fetchResourceIdsPageWithUrl(allIds.size(), theUrl, theRequestPartitionId); 128 ourLog.debug("NEXT currentIds: {}", currentIds.size()); 129 130 allIds.addAll(currentIds); 131 } 132 133 final String resourceType = theUrl.substring(0, theUrl.indexOf('?')); 134 135 return new HomogeneousResourcePidList(resourceType, allIds, theEnd, theRequestPartitionId); 136 } 137 138 private List<IResourcePersistentId> fetchResourceIdsPageWithUrl( 139 int theOffset, String theUrl, RequestPartitionId theRequestPartitionId) { 140 String resourceType = theUrl.substring(0, theUrl.indexOf('?')); 141 RuntimeResourceDefinition def = myFhirContext.getResourceDefinition(resourceType); 142 143 SearchParameterMap searchParamMap = myMatchUrlService.translateMatchUrl(theUrl, def); 144 searchParamMap.setSort(new SortSpec(Constants.PARAM_ID, SortOrderEnum.ASC)); 145 searchParamMap.setOffset(theOffset); 146 searchParamMap.setLoadSynchronousUpTo(myJpaStorageSettings.getInternalSynchronousSearchSize() + 1); 147 148 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType); 149 SystemRequestDetails request = new SystemRequestDetails(); 150 request.setRequestPartitionId(theRequestPartitionId); 151 152 return dao.searchForIds(searchParamMap, request); 153 } 154 155 @Nonnull 156 private IResourcePidList fetchResourceIdsPageNoUrl( 157 Date theStart, Date theEnd, RequestPartitionId theRequestPartitionId) { 158 final Pageable page = Pageable.unpaged(); 159 Slice<Object[]> slice; 160 if (theRequestPartitionId == null || theRequestPartitionId.isAllPartitions()) { 161 slice = myResourceTableDao.findIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldest( 162 page, theStart, theEnd); 163 } else if (theRequestPartitionId.isDefaultPartition()) { 164 slice = 165 myResourceTableDao 166 .findIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldestForDefaultPartition( 167 page, theStart, theEnd); 168 } else { 169 slice = 170 myResourceTableDao 171 .findIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldestForPartitionIds( 172 page, theStart, theEnd, theRequestPartitionId.getPartitionIds()); 173 } 174 175 List<Object[]> content = slice.getContent(); 176 if (content.isEmpty()) { 177 return new EmptyResourcePidList(); 178 } 179 180 List<IResourcePersistentId> ids = 181 content.stream().map(t -> JpaPid.fromId((Long) t[0])).collect(Collectors.toList()); 182 183 List<String> types = content.stream().map(t -> (String) t[1]).collect(Collectors.toList()); 184 185 Date lastDate = (Date) content.get(content.size() - 1)[2]; 186 187 return new MixedResourcePidList(types, ids, lastDate, theRequestPartitionId); 188 } 189}