Skip to content

Commit 0c169b5

Browse files
committed
Fixed formatting, tests
1 parent 5eef4b6 commit 0c169b5

File tree

2 files changed

+114
-39
lines changed

2 files changed

+114
-39
lines changed

hapi-fhir-jpaserver-base/pom.xml

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -520,29 +520,42 @@
520520
<groupId>ca.uhn.hapi.fhir</groupId>
521521
<artifactId>hapi-tinder-plugin</artifactId>
522522
<version>${project.version}</version>
523-
<executions><execution><id>generate-ddl-legacy</id>
524-
<phase>process-classes</phase>
525-
<goals>
526-
<goal>generate-ddl</goal>
527-
</goals>
528-
<configuration>
529-
<databasePartitionMode>false</databasePartitionMode>
530-
<outputDirectory>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/nonpartitioned</outputDirectory>
531-
</configuration>
532-
</execution>
533-
<execution>
534-
<id>generate-ddl-partitioned</id>
535-
<phase>process-classes</phase>
536-
<goals>
537-
<goal>generate-ddl</goal>
538-
</goals>
539-
<configuration>
540-
<databasePartitionMode>true</databasePartitionMode>
541-
<outputDirectory>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/partitioned</outputDirectory>
542-
</configuration>
543-
</execution>
523+
<executions>
524+
<execution>
525+
<id>generate-ddl-legacy</id>
526+
<phase>process-classes</phase>
527+
<goals>
528+
<goal>generate-ddl</goal>
529+
</goals>
530+
<configuration>
531+
<databasePartitionMode>false</databasePartitionMode>
532+
<outputDirectory>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/nonpartitioned</outputDirectory>
533+
</configuration>
534+
</execution>
535+
<execution>
536+
<id>generate-ddl-partitioned</id>
537+
<phase>process-classes</phase>
538+
<goals>
539+
<goal>generate-ddl</goal>
540+
</goals>
541+
<configuration>
542+
<databasePartitionMode>true</databasePartitionMode>
543+
<outputDirectory>${project.build.directory}/classes/ca/uhn/hapi/fhir/jpa/docs/database/partitioned</outputDirectory>
544+
</configuration>
545+
</execution>
544546
</executions>
545547
<configuration>
548+
<fileName/>
549+
<package/>
550+
<packageName/>
551+
<packageName/>
552+
<targetDirectory/>
553+
<version/>
554+
<fhirVersion/>
555+
<configPackageBase/>
556+
<packageBase/>
557+
<targetResourceSpringBeansFile/>
558+
<baseResourceNames/>
546559
<packageNames>
547560
<packageName>ca.uhn.fhir.jpa.entity</packageName>
548561
<packageName>ca.uhn.fhir.jpa.model.entity</packageName>

hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java

Lines changed: 80 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@
6767
import org.hibernate.search.engine.search.projection.dsl.CompositeProjectionOptionsStep;
6868
import org.hibernate.search.engine.search.projection.dsl.SearchProjectionFactory;
6969
import org.hibernate.search.engine.search.query.dsl.SearchQueryOptionsStep;
70-
import org.hibernate.search.engine.search.sort.dsl.SearchSortFactory;
7170
import org.hibernate.search.mapper.orm.Search;
7271
import org.hibernate.search.mapper.orm.common.EntityReference;
7372
import org.hibernate.search.mapper.orm.search.loading.dsl.SearchLoadingOptionsStep;
@@ -76,7 +75,6 @@
7675
import org.hibernate.search.util.common.SearchException;
7776
import org.hl7.fhir.instance.model.api.IBaseResource;
7877
import org.springframework.beans.factory.annotation.Autowired;
79-
import org.springframework.stereotype.Service;
8078
import org.springframework.transaction.PlatformTransactionManager;
8179
import org.springframework.transaction.annotation.Transactional;
8280
import org.springframework.transaction.support.TransactionTemplate;
@@ -93,7 +91,6 @@
9391
import static ca.uhn.fhir.rest.server.BasePagingProvider.DEFAULT_MAX_PAGE_SIZE;
9492
import static org.apache.commons.lang3.StringUtils.isNotBlank;
9593

96-
@Service
9794
public class FulltextSearchSvcImpl implements IFulltextSearchSvc {
9895
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class);
9996
private static final int DEFAULT_MAX_NON_PAGED_SIZE = 500;
@@ -154,19 +151,28 @@ public boolean canUseHibernateSearch(String theResourceType, SearchParameterMap
154151
boolean requiresHibernateSearchAccess = myParams.containsKey(Constants.PARAM_CONTENT)
155152
|| myParams.containsKey(Constants.PARAM_TEXT)
156153
|| myParams.isLastN();
154+
// we have to use it - _text and _content searches only use hibernate
157155
if (requiresHibernateSearchAccess) {
158156
return true;
159157
}
158+
159+
// if the registry has not been initialized
160+
// we cannot use HibernateSearch because it
161+
// will, internally, trigger a new search
162+
// when it refreshes the search parameters
163+
// (which will cause an infinite loop)
160164
if (!mySearchParamRegistry.isInitialized()) {
161165
return false;
162166
}
167+
163168
return myStorageSettings.isHibernateSearchIndexSearchParams()
164169
&& myAdvancedIndexQueryBuilder.canUseHibernateSearch(theResourceType, myParams, mySearchParamRegistry);
165170
}
166171

167172
@Override
168173
public void reindex(ResourceTable theEntity) {
169174
validateHibernateSearchIsEnabled();
175+
170176
SearchIndexingPlan plan = getSearchSession().indexingPlan();
171177
plan.addOrUpdate(theEntity);
172178
}
@@ -178,6 +184,7 @@ public ISearchQueryExecutor searchNotScrolled(
178184
Integer theMaxResultsToFetch,
179185
RequestDetails theRequestDetails) {
180186
validateHibernateSearchIsEnabled();
187+
181188
return doSearch(theResourceName, theParams, null, theMaxResultsToFetch, theRequestDetails);
182189
}
183190

@@ -187,13 +194,14 @@ public ISearchQueryExecutor searchScrolled(
187194
String theResourceType, SearchParameterMap theParams, RequestDetails theRequestDetails) {
188195
validateHibernateSearchIsEnabled();
189196

190-
SearchQueryOptionsStep<JpaPid, ?, ?, SearchLoadingOptionsStep, ?, ?> searchQueryOptionsStep =
197+
SearchQueryOptionsStep<?, ?, JpaPid, SearchLoadingOptionsStep, ?, ?> searchQueryOptionsStep =
191198
getSearchQueryOptionsStep(theResourceType, theParams, null);
192199
logQuery(searchQueryOptionsStep, theRequestDetails);
193200

194201
return new SearchScrollQueryExecutorAdaptor(searchQueryOptionsStep.scroll(SearchBuilder.getMaximumPageSize()));
195202
}
196203

204+
// keep this in sync with supportsSomeOf();
197205
@SuppressWarnings("rawtypes")
198206
private ISearchQueryExecutor doSearch(
199207
String theResourceType,
@@ -205,11 +213,13 @@ private ISearchQueryExecutor doSearch(
205213
int offset = theParams.getOffset() == null ? 0 : theParams.getOffset();
206214
int count = getMaxFetchSize(theParams, theMaxResultsToFetch);
207215

208-
SearchQueryOptionsStep<JpaPid, ?, ?, SearchLoadingOptionsStep, SearchSortFactory, ?> searchQueryOptionsStep =
216+
// perform an offset search instead of a scroll one, which doesn't allow for offset
217+
SearchQueryOptionsStep<?, ?, JpaPid, SearchLoadingOptionsStep, ?, ?> searchQueryOptionsStep =
209218
getSearchQueryOptionsStep(theResourceType, theParams, theReferencingPid);
210219
logQuery(searchQueryOptionsStep, theRequestDetails);
211220
List<JpaPid> longs = searchQueryOptionsStep.fetchHits(offset, count);
212221

222+
// indicate param was already processed, otherwise queries DB to process it
213223
theParams.setOffset(null);
214224
return SearchQueryExecutors.from(longs);
215225
}
@@ -218,26 +228,32 @@ private int getMaxFetchSize(SearchParameterMap theParams, Integer theMax) {
218228
if (theMax != null) {
219229
return theMax;
220230
}
231+
232+
// todo mb we should really pass this in.
221233
if (theParams.getCount() != null) {
222234
return theParams.getCount();
223235
}
236+
224237
return DEFAULT_MAX_NON_PAGED_SIZE;
225238
}
226239

227240
@SuppressWarnings("rawtypes")
228-
private SearchQueryOptionsStep<JpaPid, ?, ?, SearchLoadingOptionsStep, SearchSortFactory, ?>
229-
getSearchQueryOptionsStep(
230-
String theResourceType, SearchParameterMap theParams, IResourcePersistentId theReferencingPid) {
241+
private SearchQueryOptionsStep<?, ?, JpaPid, SearchLoadingOptionsStep, ?, ?> getSearchQueryOptionsStep(
242+
String theResourceType, SearchParameterMap theParams, IResourcePersistentId theReferencingPid) {
231243

232244
dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH);
233-
SearchQueryOptionsStep<JpaPid, ?, ?, SearchLoadingOptionsStep, SearchSortFactory, ?> query = getSearchSession()
245+
SearchQueryOptionsStep<?, ?, JpaPid, SearchLoadingOptionsStep, ?, ?> query = getSearchSession()
234246
.search(ResourceTable.class)
235-
// The document id is the PK which is pid. We use this instead of _myId to avoid fetching the doc body.
236-
.select(f -> f.composite(docRef -> JpaPid.fromId(Long.valueOf(docRef.id())), f.documentReference()))
247+
// The document id is the PK which is pid. We use this instead of _myId to avoid fetching the doc body.
248+
.select(
249+
// adapt the String docRef.id() to the Long that it really is.
250+
f -> f.composite(docRef -> JpaPid.fromId(Long.valueOf(docRef.id())), f.documentReference()))
237251
.where(f -> buildWhereClause(f, theResourceType, theParams, theReferencingPid));
238252

239253
if (theParams.getSort() != null) {
240254
query.sort(f -> myExtendedFulltextSortHelper.getSortClauses(f, theParams.getSort(), theResourceType));
255+
256+
// indicate parameter was processed
241257
theParams.setSort(null);
242258
}
243259

@@ -257,9 +273,23 @@ private PredicateFinalStep buildWhereClause(
257273
ExtendedHSearchClauseBuilder builder =
258274
new ExtendedHSearchClauseBuilder(myFhirContext, myStorageSettings, b, f);
259275

276+
/*
277+
* Handle _content parameter (resource body content)
278+
*
279+
* Posterity:
280+
* We do not want the HAPI-FHIR dao's to process the
281+
* _content parameter, so we remove it from the map here
282+
*/
260283
List<List<IQueryParameterType>> contentAndTerms = theParams.remove(Constants.PARAM_CONTENT);
261284
builder.addStringTextSearch(Constants.PARAM_CONTENT, contentAndTerms);
262285

286+
/*
287+
* Handle _text parameter (resource narrative content)
288+
*
289+
* Posterity:
290+
* We do not want the HAPI-FHIR dao's to process the
291+
* _text parameter, so we remove it from the map here
292+
*/
263293
List<List<IQueryParameterType>> textAndTerms = theParams.remove(Constants.PARAM_TEXT);
264294
builder.addStringTextSearch(Constants.PARAM_TEXT, textAndTerms);
265295

@@ -271,6 +301,9 @@ private PredicateFinalStep buildWhereClause(
271301
builder.addResourceTypeClause(theResourceType);
272302
}
273303

304+
/*
305+
* Handle other supported parameters
306+
*/
274307
if (myStorageSettings.isHibernateSearchIndexSearchParams() && theParams.getEverythingMode() == null) {
275308
ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams params =
276309
new ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams();
@@ -279,15 +312,23 @@ private PredicateFinalStep buildWhereClause(
279312
.setSearchParameterMap(theParams);
280313
myAdvancedIndexQueryBuilder.addAndConsumeAdvancedQueryClauses(builder, params);
281314
}
315+
// DROP EARLY HERE IF BOOL IS EMPTY?
282316
});
283317
}
284318

319+
/**
320+
* If either (or both) of the <code>_content</code> or <code>_text</code> Search Parameters
321+
* are present in the request, verify the given parameter(s) are actually supported by the
322+
* current configuration and throw a {@link InvalidRequestException} if not.
323+
*/
285324
private void verifyContentAndTextParamsAreSupportedIfUsed(String theResourceType, SearchParameterMap theParams) {
286325
boolean haveParamText = theParams.containsKey(Constants.PARAM_TEXT);
287326
boolean haveParamContent = theParams.containsKey(Constants.PARAM_CONTENT);
288327
if (!haveParamText && !haveParamContent) {
289328
return;
290329
}
330+
331+
// Can't use _content or _text if FullText indexing is disabled
291332
if (!myStorageSettings.isHibernateSearchIndexFullText()) {
292333
String failingParams = theParams.keySet().stream()
293334
.filter(t -> t.equals(Constants.PARAM_TEXT) || t.equals(Constants.PARAM_CONTENT))
@@ -300,6 +341,8 @@ private void verifyContentAndTextParamsAreSupportedIfUsed(String theResourceType
300341
}
301342

302343
List<String> failingParams = null;
344+
345+
// theResourceType is null for $everything queries
303346
if (theResourceType != null) {
304347
if (haveParamContent
305348
&& !mySearchParamRegistry.hasActiveSearchParam(
@@ -351,6 +394,7 @@ public List<IResourcePersistentId> everything(
351394
RequestDetails theRequestDetails) {
352395
validateHibernateSearchIsEnabled();
353396

397+
// todo mb what about max results here?
354398
List<IResourcePersistentId> retVal =
355399
toList(doSearch(null, theParams, theReferencingPid, 10_000, theRequestDetails), 10_000);
356400
if (theReferencingPid != null) {
@@ -400,6 +444,9 @@ public List<IResourcePersistentId> search(
400444
DEFAULT_MAX_NON_PAGED_SIZE);
401445
}
402446

447+
/**
448+
* Adapt our async interface to the legacy concrete List
449+
*/
403450
@SuppressWarnings("rawtypes")
404451
private List<IResourcePersistentId> toList(ISearchQueryExecutor theSearchResultStream, long theMaxSize) {
405452
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(theSearchResultStream, 0), false)
@@ -420,10 +467,17 @@ public IBaseResource tokenAutocompleteValueSetSearch(ValueSetAutocompleteOptions
420467
return autocomplete.search(theOptions);
421468
}
422469

470+
/**
471+
* Throws an error if configured with Lucene.
472+
* <p>
473+
* Some features only work with Elasticsearch.
474+
* Lastn and the autocomplete search use nested aggregations which are Elasticsearch-only
475+
*/
423476
private void ensureElastic() {
424477
try {
425478
getSearchSession().scope(ResourceTable.class).aggregation().extension(ElasticsearchExtension.get());
426479
} catch (SearchException e) {
480+
// unsupported. we are probably running Lucene.
427481
throw new IllegalStateException(
428482
Msg.code(2070) + "This operation requires Elasticsearch. Lucene is not supported.");
429483
}
@@ -450,9 +504,12 @@ public List<IBaseResource> getResources(Collection<Long> thePids) {
450504
dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH);
451505
List<ExtendedHSearchResourceProjection> rawResourceDataList = session.search(ResourceTable.class)
452506
.select(this::buildResourceSelectClause)
453-
.where(f -> f.id().matchingAny(JpaPid.fromLongList(thePids)))
507+
.where(
508+
f -> f.id().matchingAny(JpaPid.fromLongList(thePids)) // matches '_id' from resource index
509+
)
454510
.fetchAllHits();
455511

512+
// order resource projections as per thePids
456513
ArrayList<Long> pidList = new ArrayList<>(thePids);
457514
List<ExtendedHSearchResourceProjection> orderedAsPidsResourceDataList = rawResourceDataList.stream()
458515
.sorted(Ordering.explicit(pidList).onResultOf(ExtendedHSearchResourceProjection::getPid))
@@ -479,7 +536,7 @@ private CompositeProjectionOptionsStep<?, ExtendedHSearchResourceProjection> bui
479536

480537
@Override
481538
public long count(String theResourceName, SearchParameterMap theParams) {
482-
SearchQueryOptionsStep<JpaPid, ?, ?, SearchLoadingOptionsStep, SearchSortFactory, ?> queryOptionsStep =
539+
SearchQueryOptionsStep<?, ?, JpaPid, SearchLoadingOptionsStep, ?, ?> queryOptionsStep =
483540
getSearchQueryOptionsStep(theResourceName, theParams, null);
484541

485542
return queryOptionsStep.fetchTotalHitCount();
@@ -494,16 +551,16 @@ public List<IBaseResource> searchForResources(
494551

495552
if (theParams.getOffset() != null && theParams.getOffset() != 0) {
496553
offset = theParams.getOffset();
554+
// indicate param was already processed, otherwise queries DB to process it
497555
theParams.setOffset(null);
498556
}
499557

500558
dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH);
501559

502-
SearchQueryOptionsStep<ExtendedHSearchResourceProjection, ?, ?, SearchLoadingOptionsStep, SearchSortFactory, ?>
503-
query = getSearchSession()
504-
.search(ResourceTable.class)
505-
.select(this::buildResourceSelectClause)
506-
.where(f -> buildWhereClause(f, theResourceType, theParams, null));
560+
var query = getSearchSession()
561+
.search(ResourceTable.class)
562+
.select(this::buildResourceSelectClause)
563+
.where(f -> buildWhereClause(f, theResourceType, theParams, null));
507564

508565
if (theParams.getSort() != null) {
509566
query.sort(f -> myExtendedFulltextSortHelper.getSortClauses(f, theParams.getSort(), theResourceType));
@@ -515,6 +572,11 @@ public List<IBaseResource> searchForResources(
515572
return resourceProjectionsToResources(extendedLuceneResourceProjections);
516573
}
517574

575+
/**
576+
* Fire the JPA_PERFTRACE_INFO hook if it is enabled
577+
* @param theQuery the query to log
578+
* @param theRequestDetails the request details
579+
*/
518580
@SuppressWarnings("rawtypes")
519581
private void logQuery(SearchQueryOptionsStep theQuery, RequestDetails theRequestDetails) {
520582
IInterceptorBroadcaster compositeBroadcaster =

0 commit comments

Comments
 (0)