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}