001package ca.uhn.fhir.jpa.search;
002
003/*-
004 * #%L
005 * HAPI FHIR JPA Server
006 * %%
007 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.jpa.dao.ISearchBuilder;
024import ca.uhn.fhir.jpa.entity.Search;
025import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
026import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
027import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.SearchTask;
028import ca.uhn.fhir.model.api.IResource;
029import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
030import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
031import ca.uhn.fhir.rest.api.server.RequestDetails;
032import org.hl7.fhir.instance.model.api.IAnyResource;
033import org.hl7.fhir.instance.model.api.IBaseResource;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036import org.springframework.transaction.TransactionDefinition;
037import org.springframework.transaction.support.TransactionTemplate;
038
039import javax.annotation.Nonnull;
040import java.util.List;
041import java.util.Set;
042import java.util.stream.Collectors;
043
044public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider {
045        private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class);
046        private SearchTask mySearchTask;
047        private ISearchBuilder mySearchBuilder;
048        private Search mySearch;
049
050        /**
051         * Constructor
052         */
053        public PersistedJpaSearchFirstPageBundleProvider(Search theSearch, SearchTask theSearchTask, ISearchBuilder theSearchBuilder, RequestDetails theRequest) {
054                super(theRequest, theSearch.getUuid());
055                setSearchEntity(theSearch);
056                mySearchTask = theSearchTask;
057                mySearchBuilder = theSearchBuilder;
058                mySearch = theSearch;
059        }
060
061        @Nonnull
062        @Override
063        public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
064                SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
065
066                mySearchTask.awaitInitialSync();
067
068                ourLog.trace("Fetching search resource PIDs from task: {}", mySearchTask.getClass());
069                final List<ResourcePersistentId> pids = mySearchTask.getResourcePids(theFromIndex, theToIndex);
070                ourLog.trace("Done fetching search resource PIDs");
071
072                TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
073                txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
074                List<IBaseResource> retVal = txTemplate.execute(theStatus -> toResourceList(mySearchBuilder, pids));
075
076                long totalCountWanted = theToIndex - theFromIndex;
077                long totalCountMatch = (int) retVal
078                        .stream()
079                        .filter(t -> !isInclude(t))
080                        .count();
081
082                if (totalCountMatch < totalCountWanted) {
083                        if (mySearch.getStatus() == SearchStatusEnum.PASSCMPLET) {
084
085                                /*
086                                 * This is a bit of complexity to account for the possibility that
087                                 * the consent service has filtered some results.
088                                 */
089                                Set<String> existingIds = retVal
090                                        .stream()
091                                        .map(t -> t.getIdElement().getValue())
092                                        .filter(t -> t != null)
093                                        .collect(Collectors.toSet());
094
095                                long remainingWanted = totalCountWanted - totalCountMatch;
096                                long fromIndex = theToIndex - remainingWanted;
097                                List<IBaseResource> remaining = super.getResources((int) fromIndex, theToIndex);
098                                remaining.forEach(t -> {
099                                        if (!existingIds.contains(t.getIdElement().getValue())) {
100                                                retVal.add(t);
101                                        }
102                                });
103                        }
104                }
105                ourLog.trace("Loaded resources to return");
106
107                return retVal;
108        }
109
110        private boolean isInclude(IBaseResource theResource) {
111                if (theResource instanceof IAnyResource) {
112                        return "include".equals(ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(((IAnyResource) theResource)));
113                }
114                BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(((IResource) theResource));
115                return BundleEntrySearchModeEnum.INCLUDE.equals(searchMode);
116        }
117
118        @Override
119        public Integer size() {
120                ourLog.trace("Waiting for initial sync");
121                Integer size = mySearchTask.awaitInitialSync();
122                ourLog.trace("Finished waiting for local sync");
123
124                SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
125                if (size != null) {
126                        return size;
127                }
128                return super.size();
129        }
130
131}