001/* 002 * #%L 003 * HAPI FHIR JPA Server 004 * %% 005 * Copyright (C) 2014 - 2025 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; 021 022import ca.uhn.fhir.context.FhirContext; 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.interceptor.api.HookParams; 025import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; 026import ca.uhn.fhir.interceptor.api.Pointcut; 027import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 028import ca.uhn.fhir.jpa.api.svc.IIdHelperService; 029import ca.uhn.fhir.jpa.dao.search.ExtendedHSearchClauseBuilder; 030import ca.uhn.fhir.jpa.dao.search.ExtendedHSearchIndexExtractor; 031import ca.uhn.fhir.jpa.dao.search.ExtendedHSearchResourceProjection; 032import ca.uhn.fhir.jpa.dao.search.ExtendedHSearchSearchBuilder; 033import ca.uhn.fhir.jpa.dao.search.IHSearchSortHelper; 034import ca.uhn.fhir.jpa.dao.search.LastNOperation; 035import ca.uhn.fhir.jpa.dao.search.SearchScrollQueryExecutorAdaptor; 036import ca.uhn.fhir.jpa.model.dao.JpaPid; 037import ca.uhn.fhir.jpa.model.entity.ResourceTable; 038import ca.uhn.fhir.jpa.model.search.ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams; 039import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData; 040import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; 041import ca.uhn.fhir.jpa.search.autocomplete.ValueSetAutocompleteOptions; 042import ca.uhn.fhir.jpa.search.autocomplete.ValueSetAutocompleteSearch; 043import ca.uhn.fhir.jpa.search.builder.ISearchQueryExecutor; 044import ca.uhn.fhir.jpa.search.builder.SearchBuilder; 045import ca.uhn.fhir.jpa.search.builder.SearchQueryExecutors; 046import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 047import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; 048import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; 049import ca.uhn.fhir.model.api.IQueryParameterType; 050import ca.uhn.fhir.parser.IParser; 051import ca.uhn.fhir.rest.api.Constants; 052import ca.uhn.fhir.rest.api.server.RequestDetails; 053import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId; 054import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 055import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 056import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; 057import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; 058import ca.uhn.fhir.rest.server.util.ResourceSearchParams; 059import com.google.common.collect.Ordering; 060import jakarta.annotation.Nonnull; 061import jakarta.persistence.EntityManager; 062import jakarta.persistence.PersistenceContext; 063import jakarta.persistence.PersistenceContextType; 064import org.hibernate.search.backend.elasticsearch.ElasticsearchExtension; 065import org.hibernate.search.engine.search.predicate.dsl.PredicateFinalStep; 066import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory; 067import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionOptionsStep; 068import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory; 069import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep; 070import org.hibernate.search.mapper.orm.Search; 071import org.hibernate.search.mapper.orm.common.EntityReference; 072import org.hibernate.search.mapper.orm.search.loading.dsl.SearchLoadingOptionsStep; 073import org.hibernate.search.mapper.orm.session.SearchSession; 074import org.hibernate.search.mapper.orm.work.SearchIndexingPlan; 075import org.hibernate.search.util.common.SearchException; 076import org.hl7.fhir.instance.model.api.IBaseResource; 077import org.springframework.beans.factory.annotation.Autowired; 078import org.springframework.transaction.PlatformTransactionManager; 079import org.springframework.transaction.annotation.Transactional; 080import org.springframework.transaction.support.TransactionTemplate; 081 082import java.util.ArrayList; 083import java.util.Collection; 084import java.util.Collections; 085import java.util.List; 086import java.util.Spliterators; 087import java.util.stream.Collectors; 088import java.util.stream.StreamSupport; 089 090import static ca.uhn.fhir.rest.server.BasePagingProvider.DEFAULT_MAX_PAGE_SIZE; 091import static org.apache.commons.lang3.StringUtils.isNotBlank; 092 093public class FulltextSearchSvcImpl implements IFulltextSearchSvc { 094 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class); 095 private static final int DEFAULT_MAX_NON_PAGED_SIZE = 500; 096 private final ExtendedHSearchSearchBuilder myAdvancedIndexQueryBuilder = new ExtendedHSearchSearchBuilder(); 097 098 @Autowired 099 ISearchParamExtractor mySearchParamExtractor; 100 101 @Autowired 102 IIdHelperService myIdHelperService; 103 104 @PersistenceContext(type = PersistenceContextType.TRANSACTION) 105 private EntityManager myEntityManager; 106 107 @Autowired 108 private PlatformTransactionManager myTxManager; 109 110 @Autowired 111 private FhirContext myFhirContext; 112 113 @Autowired 114 private ISearchParamRegistry mySearchParamRegistry; 115 116 @Autowired 117 private JpaStorageSettings myStorageSettings; 118 119 @Autowired 120 private IHSearchSortHelper myExtendedFulltextSortHelper; 121 122 @Autowired(required = false) 123 private IHSearchEventListener myHSearchEventListener; 124 125 @Autowired 126 private IInterceptorBroadcaster myInterceptorBroadcaster; 127 128 private Boolean ourDisabled; 129 130 /** 131 * Constructor 132 */ 133 public FulltextSearchSvcImpl() { 134 super(); 135 } 136 137 @Override 138 public ExtendedHSearchIndexData extractLuceneIndexData( 139 IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) { 140 String resourceType = myFhirContext.getResourceType(theResource); 141 ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams( 142 resourceType, ISearchParamRegistry.SearchParamLookupContextEnum.SEARCH); 143 ExtendedHSearchIndexExtractor extractor = new ExtendedHSearchIndexExtractor( 144 myStorageSettings, myFhirContext, activeSearchParams, mySearchParamExtractor); 145 return extractor.extract(theResource, theEntity, theNewParams); 146 } 147 148 @Override 149 public boolean canUseHibernateSearch(String theResourceType, SearchParameterMap myParams) { 150 boolean requiresHibernateSearchAccess = myParams.containsKey(Constants.PARAM_CONTENT) 151 || myParams.containsKey(Constants.PARAM_TEXT) 152 || myParams.isLastN(); 153 // we have to use it - _text and _content searches only use hibernate 154 if (requiresHibernateSearchAccess) { 155 return true; 156 } 157 158 // if the registry has not been initialized 159 // we cannot use HibernateSearch because it 160 // will, internally, trigger a new search 161 // when it refreshes the search parameters 162 // (which will cause an infinite loop) 163 if (!mySearchParamRegistry.isInitialized()) { 164 return false; 165 } 166 167 return myStorageSettings.isAdvancedHSearchIndexing() 168 && myAdvancedIndexQueryBuilder.canUseHibernateSearch(theResourceType, myParams, mySearchParamRegistry); 169 } 170 171 @Override 172 public void reindex(ResourceTable theEntity) { 173 validateHibernateSearchIsEnabled(); 174 175 SearchIndexingPlan plan = getSearchSession().indexingPlan(); 176 plan.addOrUpdate(theEntity); 177 } 178 179 @Override 180 public ISearchQueryExecutor searchNotScrolled( 181 String theResourceName, 182 SearchParameterMap theParams, 183 Integer theMaxResultsToFetch, 184 RequestDetails theRequestDetails) { 185 validateHibernateSearchIsEnabled(); 186 187 return doSearch(theResourceName, theParams, null, theMaxResultsToFetch, theRequestDetails); 188 } 189 190 @Transactional 191 @Override 192 public ISearchQueryExecutor searchScrolled( 193 String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails) { 194 validateHibernateSearchIsEnabled(); 195 196 SearchQueryOptionsStep<?, JpaPid, SearchLoadingOptionsStep, ?, ?> searchQueryOptionsStep = 197 getSearchQueryOptionsStep(theResourceType, theParams, null); 198 logQuery(searchQueryOptionsStep, theRequestDetails); 199 200 return new SearchScrollQueryExecutorAdaptor(searchQueryOptionsStep.scroll(SearchBuilder.getMaximumPageSize())); 201 } 202 203 // keep this in sync with supportsSomeOf(); 204 @SuppressWarnings("rawtypes") 205 private ISearchQueryExecutor doSearch( 206 String theResourceType, 207 SearchParameterMap theParams, 208 IResourcePersistentId theReferencingPid, 209 Integer theMaxResultsToFetch, 210 RequestDetails theRequestDetails) { 211 212 int offset = theParams.getOffset() == null ? 0 : theParams.getOffset(); 213 int count = getMaxFetchSize(theParams, theMaxResultsToFetch); 214 215 // perform an offset search instead of a scroll one, which doesn't allow for offset 216 SearchQueryOptionsStep<?, JpaPid, SearchLoadingOptionsStep, ?, ?> searchQueryOptionsStep = 217 getSearchQueryOptionsStep(theResourceType, theParams, theReferencingPid); 218 logQuery(searchQueryOptionsStep, theRequestDetails); 219 List<JpaPid> longs = searchQueryOptionsStep.fetchHits(offset, count); 220 221 // indicate param was already processed, otherwise queries DB to process it 222 theParams.setOffset(null); 223 return SearchQueryExecutors.from(longs); 224 } 225 226 private int getMaxFetchSize(SearchParameterMap theParams, Integer theMax) { 227 if (theMax != null) { 228 return theMax; 229 } 230 231 // todo mb we should really pass this in. 232 if (theParams.getCount() != null) { 233 return theParams.getCount(); 234 } 235 236 return DEFAULT_MAX_NON_PAGED_SIZE; 237 } 238 239 @SuppressWarnings("rawtypes") 240 private SearchQueryOptionsStep<?, JpaPid, SearchLoadingOptionsStep, ?, ?> getSearchQueryOptionsStep( 241 String theResourceType, SearchParameterMap theParams, IResourcePersistentId theReferencingPid) { 242 243 dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH); 244 SearchQueryOptionsStep<?, JpaPid, SearchLoadingOptionsStep, ?, ?> query = getSearchSession() 245 .search(ResourceTable.class) 246 // The document id is the PK which is pid. We use this instead of _myId to avoid fetching the doc body. 247 .select( 248 // adapt the String docRef.id() to the Long that it really is. 249 f -> f.composite(docRef -> JpaPid.fromId(Long.valueOf(docRef.id())), f.documentReference())) 250 .where(f -> buildWhereClause(f, theResourceType, theParams, theReferencingPid)); 251 252 if (theParams.getSort() != null) { 253 query.sort(f -> myExtendedFulltextSortHelper.getSortClauses(f, theParams.getSort(), theResourceType)); 254 255 // indicate parameter was processed 256 theParams.setSort(null); 257 } 258 259 return query; 260 } 261 262 @SuppressWarnings("rawtypes") 263 private PredicateFinalStep buildWhereClause( 264 SearchPredicateFactory f, 265 String theResourceType, 266 SearchParameterMap theParams, 267 IResourcePersistentId theReferencingPid) { 268 269 if (theParams.containsKey(Constants.PARAM_TEXT) || theParams.containsKey(Constants.PARAM_CONTENT)) { 270 if (!myStorageSettings.isHibernateSearchIndexFullText()) { 271 String params = theParams.keySet().stream() 272 .filter(t -> t.equals(Constants.PARAM_TEXT) || t.equals(Constants.PARAM_CONTENT)) 273 .sorted() 274 .collect(Collectors.joining(", ")); 275 String msg = myFhirContext 276 .getLocalizer() 277 .getMessage(FulltextSearchSvcImpl.class, "fullTextSearchingNotPossible", params); 278 throw new InvalidRequestException(Msg.code(2566) + msg); 279 } 280 } 281 282 return f.bool(b -> { 283 ExtendedHSearchClauseBuilder builder = 284 new ExtendedHSearchClauseBuilder(myFhirContext, myStorageSettings, b, f); 285 286 /* 287 * Handle _content parameter (resource body content) 288 * 289 * Posterity: 290 * We do not want the HAPI-FHIR dao's to process the 291 * _content parameter, so we remove it from the map here 292 */ 293 List<List<IQueryParameterType>> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT); 294 builder.addStringTextSearch(Constants.PARAM_CONTENT, contentAndTerms); 295 296 /* 297 * Handle _text parameter (resource narrative content) 298 * 299 * Posterity: 300 * We do not want the HAPI-FHIR dao's to process the 301 * _text parameter, so we remove it from the map here 302 */ 303 List<List<IQueryParameterType>> textAndTerms = theParams.remove(Constants.PARAM_TEXT); 304 builder.addStringTextSearch(Constants.PARAM_TEXT, textAndTerms); 305 306 if (theReferencingPid != null) { 307 b.must(f.match().field("myResourceLinksField").matching(theReferencingPid.toString())); 308 } 309 310 if (isNotBlank(theResourceType)) { 311 builder.addResourceTypeClause(theResourceType); 312 } 313 314 /* 315 * Handle other supported parameters 316 */ 317 if (myStorageSettings.isAdvancedHSearchIndexing() && theParams.getEverythingMode() == null) { 318 ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams params = 319 new ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams(); 320 params.setSearchParamRegistry(mySearchParamRegistry) 321 .setResourceType(theResourceType) 322 .setSearchParameterMap(theParams); 323 myAdvancedIndexQueryBuilder.addAndConsumeAdvancedQueryClauses(builder, params); 324 } 325 // DROP EARLY HERE IF BOOL IS EMPTY? 326 }); 327 } 328 329 @Nonnull 330 private SearchSession getSearchSession() { 331 return Search.session(myEntityManager); 332 } 333 334 @SuppressWarnings("rawtypes") 335 private List<IResourcePersistentId> convertLongsToResourcePersistentIds(List<Long> theLongPids) { 336 return theLongPids.stream().map(JpaPid::fromId).collect(Collectors.toList()); 337 } 338 339 @Override 340 @SuppressWarnings({"rawtypes", "unchecked"}) 341 public List<IResourcePersistentId> everything( 342 String theResourceName, 343 SearchParameterMap theParams, 344 IResourcePersistentId theReferencingPid, 345 RequestDetails theRequestDetails) { 346 validateHibernateSearchIsEnabled(); 347 348 // todo mb what about max results here? 349 List<IResourcePersistentId> retVal = 350 toList(doSearch(null, theParams, theReferencingPid, 10_000, theRequestDetails), 10_000); 351 if (theReferencingPid != null) { 352 retVal.add(theReferencingPid); 353 } 354 return retVal; 355 } 356 357 private void validateHibernateSearchIsEnabled() { 358 if (isDisabled()) { 359 throw new UnsupportedOperationException(Msg.code(2137) + "Hibernate search is not enabled!"); 360 } 361 } 362 363 @Override 364 public boolean isDisabled() { 365 Boolean retVal = ourDisabled; 366 367 if (retVal == null) { 368 retVal = new TransactionTemplate(myTxManager).execute(t -> { 369 try { 370 SearchSession searchSession = getSearchSession(); 371 searchSession.search(ResourceTable.class); 372 return Boolean.FALSE; 373 } catch (Exception e) { 374 ourLog.trace("FullText test failed", e); 375 ourLog.debug( 376 "Hibernate Search (Lucene) appears to be disabled on this server, fulltext will be disabled"); 377 return Boolean.TRUE; 378 } 379 }); 380 ourDisabled = retVal; 381 } 382 383 assert retVal != null; 384 return retVal; 385 } 386 387 @Transactional() 388 @Override 389 @SuppressWarnings("unchecked") 390 public List<IResourcePersistentId> search( 391 String theResourceName, SearchParameterMap theParams, RequestDetails theRequestDetails) { 392 validateHibernateSearchIsEnabled(); 393 return toList( 394 doSearch(theResourceName, theParams, null, DEFAULT_MAX_NON_PAGED_SIZE, theRequestDetails), 395 DEFAULT_MAX_NON_PAGED_SIZE); 396 } 397 398 /** 399 * Adapt our async interface to the legacy concrete List 400 */ 401 @SuppressWarnings("rawtypes") 402 private List<IResourcePersistentId> toList(ISearchQueryExecutor theSearchResultStream, long theMaxSize) { 403 return StreamSupport.stream(Spliterators.spliteratorUnknownSize(theSearchResultStream, 0), false) 404 .limit(theMaxSize) 405 .collect(Collectors.toList()); 406 } 407 408 @Transactional() 409 @Override 410 public IBaseResource tokenAutocompleteValueSetSearch(ValueSetAutocompleteOptions theOptions) { 411 validateHibernateSearchIsEnabled(); 412 ensureElastic(); 413 414 ValueSetAutocompleteSearch autocomplete = 415 new ValueSetAutocompleteSearch(myFhirContext, myStorageSettings, getSearchSession()); 416 417 dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH); 418 return autocomplete.search(theOptions); 419 } 420 421 /** 422 * Throws an error if configured with Lucene. 423 * <p> 424 * Some features only work with Elasticsearch. 425 * Lastn and the autocomplete search use nested aggregations which are Elasticsearch-only 426 */ 427 private void ensureElastic() { 428 try { 429 getSearchSession().scope(ResourceTable.class).aggregation().extension(ElasticsearchExtension.get()); 430 } catch (SearchException e) { 431 // unsupported. we are probably running Lucene. 432 throw new IllegalStateException( 433 Msg.code(2070) + "This operation requires Elasticsearch. Lucene is not supported."); 434 } 435 } 436 437 @Override 438 @SuppressWarnings("rawtypes") 439 public List<IResourcePersistentId> lastN(SearchParameterMap theParams, Integer theMaximumResults) { 440 ensureElastic(); 441 dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH); 442 List<Long> pidList = new LastNOperation( 443 getSearchSession(), myFhirContext, myStorageSettings, mySearchParamRegistry) 444 .executeLastN(theParams, theMaximumResults); 445 return convertLongsToResourcePersistentIds(pidList); 446 } 447 448 @Override 449 public List<IBaseResource> getResources(Collection<Long> thePids) { 450 if (thePids.isEmpty()) { 451 return Collections.emptyList(); 452 } 453 454 SearchSession session = getSearchSession(); 455 dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH); 456 List<ExtendedHSearchResourceProjection> rawResourceDataList = session.search(ResourceTable.class) 457 .select(this::buildResourceSelectClause) 458 .where( 459 f -> f.id().matchingAny(JpaPid.fromLongList(thePids)) // matches '_id' from resource index 460 ) 461 .fetchAllHits(); 462 463 // order resource projections as per thePids 464 ArrayList<Long> pidList = new ArrayList<>(thePids); 465 List<ExtendedHSearchResourceProjection> orderedAsPidsResourceDataList = rawResourceDataList.stream() 466 .sorted(Ordering.explicit(pidList).onResultOf(ExtendedHSearchResourceProjection::getPid)) 467 .collect(Collectors.toList()); 468 469 return resourceProjectionsToResources(orderedAsPidsResourceDataList); 470 } 471 472 @Nonnull 473 private List<IBaseResource> resourceProjectionsToResources( 474 List<ExtendedHSearchResourceProjection> theResourceDataList) { 475 IParser parser = myFhirContext.newJsonParser(); 476 return theResourceDataList.stream().map(p -> p.toResource(parser)).collect(Collectors.toList()); 477 } 478 479 private CompositeProjectionOptionsStep<?, ExtendedHSearchResourceProjection> buildResourceSelectClause( 480 SearchProjectionFactory<EntityReference, ResourceTable> f) { 481 return f.composite( 482 ExtendedHSearchResourceProjection::new, 483 f.field("myId", JpaPid.class), 484 f.field("myForcedId", String.class), 485 f.field("myRawResource", String.class)); 486 } 487 488 @Override 489 public long count(String theResourceName, SearchParameterMap theParams) { 490 SearchQueryOptionsStep<?, JpaPid, SearchLoadingOptionsStep, ?, ?> queryOptionsStep = 491 getSearchQueryOptionsStep(theResourceName, theParams, null); 492 493 return queryOptionsStep.fetchTotalHitCount(); 494 } 495 496 @Override 497 @Transactional(readOnly = true) 498 public List<IBaseResource> searchForResources( 499 String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails) { 500 int offset = 0; 501 int limit = theParams.getCount() == null ? DEFAULT_MAX_PAGE_SIZE : theParams.getCount(); 502 503 if (theParams.getOffset() != null && theParams.getOffset() != 0) { 504 offset = theParams.getOffset(); 505 // indicate param was already processed, otherwise queries DB to process it 506 theParams.setOffset(null); 507 } 508 509 dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH); 510 511 var query = getSearchSession() 512 .search(ResourceTable.class) 513 .select(this::buildResourceSelectClause) 514 .where(f -> buildWhereClause(f, theResourceType, theParams, null)); 515 516 if (theParams.getSort() != null) { 517 query.sort(f -> myExtendedFulltextSortHelper.getSortClauses(f, theParams.getSort(), theResourceType)); 518 } 519 520 logQuery(query, theRequestDetails); 521 List<ExtendedHSearchResourceProjection> extendedLuceneResourceProjections = query.fetchHits(offset, limit); 522 523 return resourceProjectionsToResources(extendedLuceneResourceProjections); 524 } 525 526 /** 527 * Fire the JPA_PERFTRACE_INFO hook if it is enabled 528 * @param theQuery the query to log 529 * @param theRequestDetails the request details 530 */ 531 @SuppressWarnings("rawtypes") 532 private void logQuery(SearchQueryOptionsStep theQuery, RequestDetails theRequestDetails) { 533 IInterceptorBroadcaster compositeBroadcaster = 534 CompositeInterceptorBroadcaster.newCompositeBroadcaster(myInterceptorBroadcaster, theRequestDetails); 535 if (compositeBroadcaster.hasHooks(Pointcut.JPA_PERFTRACE_INFO)) { 536 StorageProcessingMessage storageProcessingMessage = new StorageProcessingMessage(); 537 String queryString = theQuery.toQuery().queryString(); 538 storageProcessingMessage.setMessage(queryString); 539 HookParams params = new HookParams() 540 .add(RequestDetails.class, theRequestDetails) 541 .addIfMatchesType(ServletRequestDetails.class, theRequestDetails) 542 .add(StorageProcessingMessage.class, storageProcessingMessage); 543 compositeBroadcaster.callHooks(Pointcut.JPA_PERFTRACE_INFO, params); 544 } 545 } 546 547 @Override 548 public boolean supportsAllOf(SearchParameterMap theParams) { 549 return myAdvancedIndexQueryBuilder.isSupportsAllOf(theParams); 550 } 551 552 @Override 553 public boolean supportsAllSortTerms(String theResourceType, SearchParameterMap theParams) { 554 return myExtendedFulltextSortHelper.supportsAllSortTerms(theResourceType, theParams); 555 } 556 557 private void dispatchEvent(IHSearchEventListener.HSearchEventType theEventType) { 558 if (myHSearchEventListener != null) { 559 myHSearchEventListener.hsearchEvent(theEventType); 560 } 561 } 562 563 @Override 564 public void deleteIndexedDocumentsByTypeAndId(Class theClazz, List<Object> theGivenIds) { 565 SearchSession session = Search.session(myEntityManager); 566 SearchIndexingPlan indexingPlan = session.indexingPlan(); 567 for (Object givenId : theGivenIds) { 568 indexingPlan.purge(theClazz, givenId, null); 569 } 570 indexingPlan.process(); 571 indexingPlan.execute(); 572 } 573}