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.search; 021 022import ca.uhn.fhir.interceptor.model.RequestPartitionId; 023import ca.uhn.fhir.jpa.dao.ISearchBuilder; 024import ca.uhn.fhir.jpa.entity.Search; 025import ca.uhn.fhir.jpa.entity.SearchTypeEnum; 026import ca.uhn.fhir.jpa.model.dao.JpaPid; 027import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; 028import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask; 029import ca.uhn.fhir.jpa.util.QueryParameterUtils; 030import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; 031import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; 032import ca.uhn.fhir.rest.api.server.RequestDetails; 033import org.hl7.fhir.instance.model.api.IBaseResource; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import java.util.List; 038import java.util.Set; 039import java.util.stream.Collectors; 040import javax.annotation.Nonnull; 041 042public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider { 043 private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class); 044 private final SearchTask mySearchTask; 045 private final ISearchBuilder mySearchBuilder; 046 047 /** 048 * Constructor 049 */ 050 public PersistedJpaSearchFirstPageBundleProvider( 051 Search theSearch, 052 SearchTask theSearchTask, 053 ISearchBuilder theSearchBuilder, 054 RequestDetails theRequest, 055 RequestPartitionId theRequestPartitionId) { 056 super(theRequest, theSearch.getUuid()); 057 058 assert theSearch.getSearchType() != SearchTypeEnum.HISTORY; 059 060 setSearchEntity(theSearch); 061 mySearchTask = theSearchTask; 062 mySearchBuilder = theSearchBuilder; 063 super.setRequestPartitionId(theRequestPartitionId); 064 } 065 066 @Nonnull 067 @Override 068 public List<IBaseResource> getResources(int theFromIndex, int theToIndex) { 069 ensureSearchEntityLoaded(); 070 QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(getSearchEntity()); 071 072 mySearchTask.awaitInitialSync(); 073 074 ourLog.trace("Fetching search resource PIDs from task: {}", mySearchTask.getClass()); 075 final List<JpaPid> pids = mySearchTask.getResourcePids(theFromIndex, theToIndex); 076 ourLog.trace("Done fetching search resource PIDs"); 077 078 RequestPartitionId requestPartitionId = getRequestPartitionId(); 079 080 List<IBaseResource> retVal = myTxService 081 .withRequest(myRequest) 082 .withRequestPartitionId(requestPartitionId) 083 .execute(() -> toResourceList(mySearchBuilder, pids)); 084 085 long totalCountWanted = theToIndex - theFromIndex; 086 long totalCountMatch = (int) retVal.stream().filter(t -> !isInclude(t)).count(); 087 088 if (totalCountMatch < totalCountWanted) { 089 if (getSearchEntity().getStatus() == SearchStatusEnum.PASSCMPLET 090 || ((getSearchEntity().getStatus() == SearchStatusEnum.FINISHED 091 && getSearchEntity().getNumFound() >= theToIndex))) { 092 093 /* 094 * This is a bit of complexity to account for the possibility that 095 * the consent service has filtered some results. 096 */ 097 Set<String> existingIds = retVal.stream() 098 .map(t -> t.getIdElement().getValue()) 099 .filter(t -> t != null) 100 .collect(Collectors.toSet()); 101 102 long remainingWanted = totalCountWanted - totalCountMatch; 103 long fromIndex = theToIndex - remainingWanted; 104 List<IBaseResource> remaining = super.getResources((int) fromIndex, theToIndex); 105 remaining.forEach(t -> { 106 if (!existingIds.contains(t.getIdElement().getValue())) { 107 retVal.add(t); 108 } 109 }); 110 } 111 } 112 ourLog.trace("Loaded resources to return"); 113 114 return retVal; 115 } 116 117 private boolean isInclude(IBaseResource theResource) { 118 BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(theResource); 119 return BundleEntrySearchModeEnum.INCLUDE.equals(searchMode); 120 } 121 122 @Override 123 public Integer size() { 124 ourLog.trace("size() - Waiting for initial sync"); 125 Integer size = mySearchTask.awaitInitialSync(); 126 ourLog.trace("size() - Finished waiting for local sync"); 127 128 ensureSearchEntityLoaded(); 129 QueryParameterUtils.verifySearchHasntFailedOrThrowInternalErrorException(getSearchEntity()); 130 if (size != null) { 131 return size; 132 } 133 return super.size(); 134 } 135}