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