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.batch2; 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.dao.DaoRegistry; 027import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 028import ca.uhn.fhir.jpa.api.pid.IResourcePidList; 029import ca.uhn.fhir.jpa.api.pid.IResourcePidStream; 030import ca.uhn.fhir.jpa.api.pid.StreamTemplate; 031import ca.uhn.fhir.jpa.api.pid.TypedResourcePid; 032import ca.uhn.fhir.jpa.api.pid.TypedResourceStream; 033import ca.uhn.fhir.jpa.api.svc.IBatch2DaoSvc; 034import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; 035import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; 036import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService; 037import ca.uhn.fhir.jpa.model.dao.JpaPid; 038import ca.uhn.fhir.jpa.searchparam.MatchUrlService; 039import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 040import ca.uhn.fhir.model.primitive.IdDt; 041import ca.uhn.fhir.rest.api.Constants; 042import ca.uhn.fhir.rest.api.SortSpec; 043import ca.uhn.fhir.rest.api.server.SystemRequestDetails; 044import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 045import ca.uhn.fhir.util.DateRangeUtil; 046import ca.uhn.fhir.util.Logs; 047import jakarta.annotation.Nonnull; 048import jakarta.annotation.Nullable; 049import org.apache.commons.lang3.StringUtils; 050import org.apache.commons.lang3.Validate; 051import org.hl7.fhir.instance.model.api.IIdType; 052 053import java.util.Date; 054import java.util.function.Supplier; 055import java.util.stream.Stream; 056 057public class Batch2DaoSvcImpl implements IBatch2DaoSvc { 058 private static final org.slf4j.Logger ourLog = Logs.getBatchTroubleshootingLog(); 059 060 private final IResourceTableDao myResourceTableDao; 061 062 private final IResourceLinkDao myResourceLinkDao; 063 064 private final MatchUrlService myMatchUrlService; 065 066 private final DaoRegistry myDaoRegistry; 067 068 private final FhirContext myFhirContext; 069 070 private final IHapiTransactionService myTransactionService; 071 072 @Override 073 public boolean isAllResourceTypeSupported() { 074 return true; 075 } 076 077 public Batch2DaoSvcImpl( 078 IResourceTableDao theResourceTableDao, 079 IResourceLinkDao theResourceLinkDao, 080 MatchUrlService theMatchUrlService, 081 DaoRegistry theDaoRegistry, 082 FhirContext theFhirContext, 083 IHapiTransactionService theTransactionService) { 084 myResourceTableDao = theResourceTableDao; 085 myResourceLinkDao = theResourceLinkDao; 086 myMatchUrlService = theMatchUrlService; 087 myDaoRegistry = theDaoRegistry; 088 myFhirContext = theFhirContext; 089 myTransactionService = theTransactionService; 090 } 091 092 @Override 093 public IResourcePidStream fetchResourceIdStream( 094 Date theStart, Date theEnd, RequestPartitionId theRequestPartitionId, String theUrl) { 095 if (StringUtils.isBlank(theUrl)) { 096 return makeStreamResult( 097 theRequestPartitionId, () -> streamResourceIdsNoUrl(theStart, theEnd, theRequestPartitionId)); 098 } else { 099 return makeStreamResult( 100 theRequestPartitionId, 101 () -> streamResourceIdsWithUrl(theStart, theEnd, theUrl, theRequestPartitionId)); 102 } 103 } 104 105 @Override 106 public Stream<IdDt> streamSourceIdsThatReferenceTargetId(IIdType theTargetId) { 107 return myResourceLinkDao.streamSourceIdsForTargetFhirId(theTargetId.getResourceType(), theTargetId.getIdPart()); 108 } 109 110 private Stream<TypedResourcePid> streamResourceIdsWithUrl( 111 Date theStart, Date theEnd, String theUrl, RequestPartitionId theRequestPartitionId) { 112 validateUrl(theUrl); 113 114 SearchParameterMap searchParamMap = parseQuery(theUrl); 115 searchParamMap.setLastUpdated(DateRangeUtil.narrowDateRange(searchParamMap.getLastUpdated(), theStart, theEnd)); 116 117 String resourceType = theUrl.substring(0, theUrl.indexOf('?')); 118 IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType); 119 120 SystemRequestDetails request = new SystemRequestDetails().setRequestPartitionId(theRequestPartitionId); 121 122 return dao.searchForIdStream(searchParamMap, request, null).map(pid -> new TypedResourcePid(resourceType, pid)); 123 } 124 125 private static TypedResourcePid typedPidFromQueryArray(Object[] thePidTypeDateArray) { 126 JpaPid pid = (JpaPid) thePidTypeDateArray[0]; 127 String resourceType = (String) thePidTypeDateArray[1]; 128 return new TypedResourcePid(resourceType, pid); 129 } 130 131 @Nonnull 132 private TypedResourceStream makeStreamResult( 133 RequestPartitionId theRequestPartitionId, Supplier<Stream<TypedResourcePid>> streamSupplier) { 134 135 IHapiTransactionService.IExecutionBuilder txSettings = 136 myTransactionService.withSystemRequest().withRequestPartitionId(theRequestPartitionId); 137 138 StreamTemplate<TypedResourcePid> streamTemplate = 139 StreamTemplate.fromSupplier(streamSupplier).withTransactionAdvice(txSettings); 140 141 return new TypedResourceStream(theRequestPartitionId, streamTemplate); 142 } 143 144 /** 145 * At the moment there is no use-case for this method. 146 * This can be cleaned up at a later point in time if there is no use for it. 147 */ 148 @Nonnull 149 private Stream<TypedResourcePid> streamResourceIdsNoUrl( 150 Date theStart, Date theEnd, RequestPartitionId theRequestPartitionId) { 151 Stream<Object[]> rowStream; 152 if (theRequestPartitionId == null || theRequestPartitionId.isAllPartitions()) { 153 ourLog.debug("Search for resources - all partitions"); 154 rowStream = myResourceTableDao.streamIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldest( 155 theStart, theEnd); 156 } else if (theRequestPartitionId.isDefaultPartition()) { 157 ourLog.debug("Search for resources - default partition"); 158 rowStream = 159 myResourceTableDao 160 .streamIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldestForDefaultPartition( 161 theStart, theEnd); 162 } else { 163 ourLog.debug("Search for resources - partition {}", theRequestPartitionId); 164 rowStream = 165 myResourceTableDao 166 .streamIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldestForPartitionIds( 167 theStart, theEnd, theRequestPartitionId.getPartitionIds()); 168 } 169 170 return rowStream.map(Batch2DaoSvcImpl::typedPidFromQueryArray); 171 } 172 173 @Deprecated(since = "6.11", forRemoval = true) // delete once the default method in the interface is gone. 174 @Override 175 public IResourcePidList fetchResourceIdsPage( 176 Date theStart, Date theEnd, @Nullable RequestPartitionId theRequestPartitionId, @Nullable String theUrl) { 177 Validate.isTrue(false, "Unimplemented"); 178 return null; 179 } 180 181 private static void validateUrl(@Nonnull String theUrl) { 182 if (!theUrl.contains("?")) { 183 throw new InternalErrorException(Msg.code(2422) + "this should never happen: URL is missing a '?'"); 184 } 185 } 186 187 @Nonnull 188 private SearchParameterMap parseQuery(String theUrl) { 189 String resourceType = theUrl.substring(0, theUrl.indexOf('?')); 190 RuntimeResourceDefinition def = myFhirContext.getResourceDefinition(resourceType); 191 192 SearchParameterMap searchParamMap = myMatchUrlService.translateMatchUrl(theUrl, def); 193 // this matches idx_res_type_del_updated 194 searchParamMap.setSort(new SortSpec(Constants.PARAM_LASTUPDATED).setChain(new SortSpec(Constants.PARAM_PID))); 195 // TODO this limits us to 2G resources. 196 searchParamMap.setLoadSynchronousUpTo(Integer.MAX_VALUE); 197 return searchParamMap; 198 } 199}