
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.search; 021 022import ca.uhn.fhir.interceptor.model.RequestPartitionId; 023import ca.uhn.fhir.jpa.dao.HistoryBuilder; 024import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory; 025import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; 026import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService; 027import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; 028import ca.uhn.fhir.model.primitive.InstantDt; 029import ca.uhn.fhir.rest.api.server.IBundleProvider; 030import ca.uhn.fhir.rest.server.method.ResponsePage; 031import jakarta.annotation.Nonnull; 032import jakarta.annotation.Nullable; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.hl7.fhir.instance.model.api.IPrimitiveType; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037import org.springframework.beans.factory.annotation.Autowired; 038 039import java.util.ArrayList; 040import java.util.Date; 041import java.util.List; 042import java.util.UUID; 043 044/** 045 * Bundle provider that can search and paginate history entries for specific resource IDs. 046 */ 047public class PersistedJpaIdSearchBundleProvider implements IBundleProvider { 048 049 private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaIdSearchBundleProvider.class); 050 051 private final String myUuid; 052 private final @Nonnull String myResourceType; 053 private final @Nonnull List<String> myResourceIds; 054 private final RequestPartitionId myPartitionId; 055 private final Date myRangeStartInclusive; 056 private final @Nonnull Date myRangeEndInclusive; 057 058 @Autowired 059 private HistoryBuilderFactory myHistoryBuilderFactory; 060 061 @Autowired 062 private IJpaStorageResourceParser myJpaStorageResourceParser; 063 064 @Autowired 065 private IHapiTransactionService myTransactionService; 066 067 public PersistedJpaIdSearchBundleProvider( 068 @Nonnull String theResourceType, 069 @Nonnull List<String> theResourceIds, 070 RequestPartitionId thePartitionId, 071 @Nullable Date theRangeStartInclusive, 072 @Nonnull Date theRangeEndInclusive) { 073 myUuid = UUID.randomUUID().toString(); 074 myResourceType = theResourceType; 075 myResourceIds = theResourceIds; 076 myPartitionId = thePartitionId; 077 myRangeStartInclusive = theRangeStartInclusive; 078 myRangeEndInclusive = theRangeEndInclusive; 079 } 080 081 @Override 082 public IPrimitiveType<Date> getPublished() { 083 return new InstantDt(new Date()); 084 } 085 086 @Nonnull 087 @Override 088 public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { 089 return getResources(theFromIndex, theToIndex, new ResponsePage.ResponsePageBuilder()); 090 } 091 092 @Override 093 public List<IBaseResource> getResources( 094 int theFromIndex, int theToIndex, @Nonnull ResponsePage.ResponsePageBuilder theResponsePageBuilder) { 095 096 ourLog.debug( 097 "Invoked with fromIndex={}, toIndex={}, resourceType={}, partitionId={}", 098 theFromIndex, 099 theToIndex, 100 myResourceType, 101 myPartitionId); 102 103 return myTransactionService.withSystemRequestOnDefaultPartition().execute(() -> { 104 HistoryBuilder historyBuilder = myHistoryBuilderFactory.newHistoryBuilder( 105 myResourceType, myResourceIds, myRangeStartInclusive, myRangeEndInclusive); 106 107 RequestPartitionId partitionId = myPartitionId; 108 if (partitionId == null) { 109 partitionId = RequestPartitionId.allPartitions(); 110 } 111 112 // Use null offset and pass indexes directly to get proper pagination 113 List<ResourceHistoryTable> results = 114 historyBuilder.fetchEntities(partitionId, null, theFromIndex, theToIndex, null); 115 116 ourLog.debug( 117 "HistoryBuilder.fetchEntities returned {} results for range {}-{}", 118 results.size(), 119 theFromIndex, 120 theToIndex); 121 122 List<IBaseResource> retVal = new ArrayList<>(); 123 for (ResourceHistoryTable next : results) { 124 retVal.add(myJpaStorageResourceParser.toResource(next, true)); 125 } 126 127 ourLog.debug("returning {} resources", retVal.size()); 128 129 theResponsePageBuilder.setTotalRequestedResourcesFetched(results.size()); 130 return retVal; 131 }); 132 } 133 134 @Override 135 public String getUuid() { 136 return myUuid; 137 } 138 139 @Override 140 public Integer preferredPageSize() { 141 return null; 142 } 143 144 @Override 145 public Integer size() { 146 // Return null to allow unlimited pagination 147 return null; 148 } 149}