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}