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.dao.index;
021
022import ca.uhn.fhir.context.RuntimeSearchParam;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.model.RequestPartitionId;
025import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
026import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
027import ca.uhn.fhir.jpa.model.config.PartitionSettings;
028import ca.uhn.fhir.jpa.model.dao.JpaPid;
029import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
030import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
031import ca.uhn.fhir.jpa.model.entity.ResourceLink;
032import ca.uhn.fhir.jpa.model.entity.ResourceTable;
033import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamWithInlineReferencesExtractor;
034import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
035import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamWithInlineReferencesExtractor;
036import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
037import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
038import ca.uhn.fhir.rest.api.server.RequestDetails;
039import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
040import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
041import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
042import com.google.common.annotations.VisibleForTesting;
043import org.hl7.fhir.instance.model.api.IBaseResource;
044import org.springframework.beans.factory.annotation.Autowired;
045import org.springframework.context.annotation.Lazy;
046import org.springframework.stereotype.Service;
047
048import java.util.Collection;
049import java.util.Iterator;
050import java.util.stream.Collectors;
051import javax.annotation.Nullable;
052import javax.persistence.EntityManager;
053import javax.persistence.PersistenceContext;
054import javax.persistence.PersistenceContextType;
055
056@Service
057@Lazy
058public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWithInlineReferencesExtractor<JpaPid>
059                implements ISearchParamWithInlineReferencesExtractor {
060        private static final org.slf4j.Logger ourLog =
061                        org.slf4j.LoggerFactory.getLogger(SearchParamWithInlineReferencesExtractor.class);
062
063        @PersistenceContext(type = PersistenceContextType.TRANSACTION)
064        protected EntityManager myEntityManager;
065
066        @Autowired
067        private ISearchParamRegistry mySearchParamRegistry;
068
069        @Autowired
070        private SearchParamExtractorService mySearchParamExtractorService;
071
072        @Autowired
073        private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
074
075        @Autowired
076        private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
077
078        @Autowired
079        private PartitionSettings myPartitionSettings;
080
081        @VisibleForTesting
082        public void setPartitionSettings(PartitionSettings thePartitionSettings) {
083                myPartitionSettings = thePartitionSettings;
084        }
085
086        @VisibleForTesting
087        public void setSearchParamExtractorService(SearchParamExtractorService theSearchParamExtractorService) {
088                mySearchParamExtractorService = theSearchParamExtractorService;
089        }
090
091        @VisibleForTesting
092        public void setSearchParamRegistry(ISearchParamRegistry theSearchParamRegistry) {
093                mySearchParamRegistry = theSearchParamRegistry;
094        }
095
096        public void populateFromResource(
097                        RequestPartitionId theRequestPartitionId,
098                        ResourceIndexedSearchParams theParams,
099                        TransactionDetails theTransactionDetails,
100                        ResourceTable theEntity,
101                        IBaseResource theResource,
102                        ResourceIndexedSearchParams theExistingParams,
103                        RequestDetails theRequest,
104                        boolean thePerformIndexing) {
105                if (thePerformIndexing) {
106                        // Perform inline match URL substitution
107                        extractInlineReferences(theRequest, theResource, theTransactionDetails);
108                }
109
110                mySearchParamExtractorService.extractFromResource(
111                                theRequestPartitionId,
112                                theRequest,
113                                theParams,
114                                theExistingParams,
115                                theEntity,
116                                theResource,
117                                theTransactionDetails,
118                                thePerformIndexing,
119                                ISearchParamExtractor.ALL_PARAMS);
120
121                /*
122                 * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
123                 */
124                for (Iterator<ResourceLink> existingLinkIter =
125                                                theExistingParams.getResourceLinks().iterator();
126                                existingLinkIter.hasNext(); ) {
127                        ResourceLink nextExisting = existingLinkIter.next();
128                        if (theParams.myLinks.remove(nextExisting)) {
129                                existingLinkIter.remove();
130                                theParams.myLinks.add(nextExisting);
131                        }
132                }
133        }
134
135        @Nullable
136        private Collection<? extends BaseResourceIndexedSearchParam> findParameterIndexes(
137                        ResourceIndexedSearchParams theParams, RuntimeSearchParam nextCompositeOf) {
138                Collection<? extends BaseResourceIndexedSearchParam> paramsListForCompositePart = null;
139                switch (nextCompositeOf.getParamType()) {
140                        case NUMBER:
141                                paramsListForCompositePart = theParams.myNumberParams;
142                                break;
143                        case DATE:
144                                paramsListForCompositePart = theParams.myDateParams;
145                                break;
146                        case STRING:
147                                paramsListForCompositePart = theParams.myStringParams;
148                                break;
149                        case TOKEN:
150                                paramsListForCompositePart = theParams.myTokenParams;
151                                break;
152                        case QUANTITY:
153                                paramsListForCompositePart = theParams.myQuantityParams;
154                                break;
155                        case URI:
156                                paramsListForCompositePart = theParams.myUriParams;
157                                break;
158                        case REFERENCE:
159                        case SPECIAL:
160                        case COMPOSITE:
161                        case HAS:
162                                break;
163                }
164                if (paramsListForCompositePart != null) {
165                        paramsListForCompositePart = paramsListForCompositePart.stream()
166                                        .filter(t -> t.getParamName().equals(nextCompositeOf.getName()))
167                                        .collect(Collectors.toList());
168                }
169                return paramsListForCompositePart;
170        }
171
172        @VisibleForTesting
173        public void setDaoSearchParamSynchronizer(DaoSearchParamSynchronizer theDaoSearchParamSynchronizer) {
174                myDaoSearchParamSynchronizer = theDaoSearchParamSynchronizer;
175        }
176
177        public void storeUniqueComboParameters(
178                        ResourceIndexedSearchParams theParams,
179                        ResourceTable theEntity,
180                        ResourceIndexedSearchParams theExistingParams) {
181
182                /*
183                 * String Uniques
184                 */
185                if (myStorageSettings.isUniqueIndexesEnabled()) {
186                        for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract(
187                                        theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) {
188                                ourLog.debug("Removing unique index: {}", next);
189                                myEntityManager.remove(next);
190                                theEntity.getParamsComboStringUnique().remove(next);
191                        }
192                        boolean haveNewStringUniqueParams = false;
193                        for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract(
194                                        theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) {
195                                if (myStorageSettings.isUniqueIndexesCheckedBeforeSave()) {
196                                        ResourceIndexedComboStringUnique existing =
197                                                        myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
198                                        if (existing != null) {
199
200                                                String searchParameterId = "(unknown)";
201                                                if (next.getSearchParameterId() != null) {
202                                                        searchParameterId = next.getSearchParameterId()
203                                                                        .toUnqualifiedVersionless()
204                                                                        .getValue();
205                                                }
206
207                                                String msg = myFhirContext
208                                                                .getLocalizer()
209                                                                .getMessage(
210                                                                                BaseHapiFhirDao.class,
211                                                                                "uniqueIndexConflictFailure",
212                                                                                theEntity.getResourceType(),
213                                                                                next.getIndexString(),
214                                                                                existing.getResource()
215                                                                                                .getIdDt()
216                                                                                                .toUnqualifiedVersionless()
217                                                                                                .getValue(),
218                                                                                searchParameterId);
219
220                                                // Use ResourceVersionConflictException here because the HapiTransactionService
221                                                // catches this and can retry it if needed
222                                                throw new ResourceVersionConflictException(Msg.code(1093) + msg);
223                                        }
224                                }
225                                ourLog.debug("Persisting unique index: {}", next);
226                                myEntityManager.persist(next);
227                                haveNewStringUniqueParams = true;
228                        }
229                        theEntity.setParamsComboStringUniquePresent(
230                                        theParams.myComboStringUniques.size() > 0 || haveNewStringUniqueParams);
231                }
232        }
233}