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}