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.reindex;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.context.RuntimeResourceDefinition;
024import ca.uhn.fhir.i18n.Msg;
025import ca.uhn.fhir.interceptor.model.RequestPartitionId;
026import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
027import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
028import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
029import ca.uhn.fhir.jpa.api.pid.EmptyResourcePidList;
030import ca.uhn.fhir.jpa.api.pid.HomogeneousResourcePidList;
031import ca.uhn.fhir.jpa.api.pid.IResourcePidList;
032import ca.uhn.fhir.jpa.api.pid.MixedResourcePidList;
033import ca.uhn.fhir.jpa.api.svc.IBatch2DaoSvc;
034import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
035import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
036import ca.uhn.fhir.jpa.model.dao.JpaPid;
037import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
038import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
039import ca.uhn.fhir.rest.api.Constants;
040import ca.uhn.fhir.rest.api.SortOrderEnum;
041import ca.uhn.fhir.rest.api.SortSpec;
042import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
043import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
044import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
045import org.springframework.data.domain.Pageable;
046import org.springframework.data.domain.Slice;
047
048import java.util.ArrayList;
049import java.util.Date;
050import java.util.List;
051import java.util.stream.Collectors;
052import javax.annotation.Nonnull;
053import javax.annotation.Nullable;
054
055public class Batch2DaoSvcImpl implements IBatch2DaoSvc {
056        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Batch2DaoSvcImpl.class);
057
058        private final IResourceTableDao myResourceTableDao;
059
060        private final MatchUrlService myMatchUrlService;
061
062        private final DaoRegistry myDaoRegistry;
063
064        private final FhirContext myFhirContext;
065
066        private final IHapiTransactionService myTransactionService;
067
068        private final JpaStorageSettings myJpaStorageSettings;
069
070        @Override
071        public boolean isAllResourceTypeSupported() {
072                return true;
073        }
074
075        public Batch2DaoSvcImpl(
076                        IResourceTableDao theResourceTableDao,
077                        MatchUrlService theMatchUrlService,
078                        DaoRegistry theDaoRegistry,
079                        FhirContext theFhirContext,
080                        IHapiTransactionService theTransactionService,
081                        JpaStorageSettings theJpaStorageSettings) {
082                myResourceTableDao = theResourceTableDao;
083                myMatchUrlService = theMatchUrlService;
084                myDaoRegistry = theDaoRegistry;
085                myFhirContext = theFhirContext;
086                myTransactionService = theTransactionService;
087                myJpaStorageSettings = theJpaStorageSettings;
088        }
089
090        @Override
091        public IResourcePidList fetchResourceIdsPage(
092                        Date theStart, Date theEnd, @Nullable RequestPartitionId theRequestPartitionId, @Nullable String theUrl) {
093                return myTransactionService
094                                .withSystemRequest()
095                                .withRequestPartitionId(theRequestPartitionId)
096                                .execute(() -> {
097                                        if (theUrl == null) {
098                                                return fetchResourceIdsPageNoUrl(theStart, theEnd, theRequestPartitionId);
099                                        } else {
100                                                return fetchResourceIdsPageWithUrl(theEnd, theUrl, theRequestPartitionId);
101                                        }
102                                });
103        }
104
105        @Nonnull
106        private HomogeneousResourcePidList fetchResourceIdsPageWithUrl(
107                        Date theEnd, @Nonnull String theUrl, @Nullable RequestPartitionId theRequestPartitionId) {
108                if (!theUrl.contains("?")) {
109                        throw new InternalErrorException(Msg.code(2422) + "this should never happen: URL is missing a '?'");
110                }
111
112                final Integer internalSynchronousSearchSize = myJpaStorageSettings.getInternalSynchronousSearchSize();
113
114                if (internalSynchronousSearchSize == null || internalSynchronousSearchSize <= 0) {
115                        throw new InternalErrorException(Msg.code(2423)
116                                        + "this should never happen: internalSynchronousSearchSize is null or less than or equal to 0");
117                }
118
119                List<IResourcePersistentId> currentIds = fetchResourceIdsPageWithUrl(0, theUrl, theRequestPartitionId);
120                ourLog.debug("FIRST currentIds: {}", currentIds.size());
121
122                final List<IResourcePersistentId> allIds = new ArrayList<>(currentIds);
123
124                while (internalSynchronousSearchSize < currentIds.size()) {
125                        // Ensure the offset is set to the last ID in the cumulative List, otherwise, we'll be stuck in an infinite
126                        // loop here:
127                        currentIds = fetchResourceIdsPageWithUrl(allIds.size(), theUrl, theRequestPartitionId);
128                        ourLog.debug("NEXT currentIds: {}", currentIds.size());
129
130                        allIds.addAll(currentIds);
131                }
132
133                final String resourceType = theUrl.substring(0, theUrl.indexOf('?'));
134
135                return new HomogeneousResourcePidList(resourceType, allIds, theEnd, theRequestPartitionId);
136        }
137
138        private List<IResourcePersistentId> fetchResourceIdsPageWithUrl(
139                        int theOffset, String theUrl, RequestPartitionId theRequestPartitionId) {
140                String resourceType = theUrl.substring(0, theUrl.indexOf('?'));
141                RuntimeResourceDefinition def = myFhirContext.getResourceDefinition(resourceType);
142
143                SearchParameterMap searchParamMap = myMatchUrlService.translateMatchUrl(theUrl, def);
144                searchParamMap.setSort(new SortSpec(Constants.PARAM_ID, SortOrderEnum.ASC));
145                searchParamMap.setOffset(theOffset);
146                searchParamMap.setLoadSynchronousUpTo(myJpaStorageSettings.getInternalSynchronousSearchSize() + 1);
147
148                IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceType);
149                SystemRequestDetails request = new SystemRequestDetails();
150                request.setRequestPartitionId(theRequestPartitionId);
151
152                return dao.searchForIds(searchParamMap, request);
153        }
154
155        @Nonnull
156        private IResourcePidList fetchResourceIdsPageNoUrl(
157                        Date theStart, Date theEnd, RequestPartitionId theRequestPartitionId) {
158                final Pageable page = Pageable.unpaged();
159                Slice<Object[]> slice;
160                if (theRequestPartitionId == null || theRequestPartitionId.isAllPartitions()) {
161                        slice = myResourceTableDao.findIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldest(
162                                        page, theStart, theEnd);
163                } else if (theRequestPartitionId.isDefaultPartition()) {
164                        slice =
165                                        myResourceTableDao
166                                                        .findIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldestForDefaultPartition(
167                                                                        page, theStart, theEnd);
168                } else {
169                        slice =
170                                        myResourceTableDao
171                                                        .findIdsTypesAndUpdateTimesOfResourcesWithinUpdatedRangeOrderedFromOldestForPartitionIds(
172                                                                        page, theStart, theEnd, theRequestPartitionId.getPartitionIds());
173                }
174
175                List<Object[]> content = slice.getContent();
176                if (content.isEmpty()) {
177                        return new EmptyResourcePidList();
178                }
179
180                List<IResourcePersistentId> ids =
181                                content.stream().map(t -> JpaPid.fromId((Long) t[0])).collect(Collectors.toList());
182
183                List<String> types = content.stream().map(t -> (String) t[1]).collect(Collectors.toList());
184
185                Date lastDate = (Date) content.get(content.size() - 1)[2];
186
187                return new MixedResourcePidList(types, ids, lastDate, theRequestPartitionId);
188        }
189}