diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6024-full-index-search-with-filter-parameters.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6024-full-index-search-with-filter-parameters.yaml new file mode 100644 index 00000000000..d62a971afac --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_4_0/6024-full-index-search-with-filter-parameters.yaml @@ -0,0 +1,10 @@ +--- +type: fix +issue: 6024 +title: "Fixed a bug in search where requesting a count with HSearch indexing + and FilterParameter enabled and using the _filter parameter would result + in inaccurate results being returned. + This happened because the count query would use an incorrect set of parameters + to find the count, and the regular search when then try and ensure its results + matched the count query (which it couldn't because it had different parameters). +" diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index f7e1764e003..1a43905b822 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -34,6 +34,7 @@ import ca.uhn.fhir.jpa.dao.search.IHSearchSortHelper; import ca.uhn.fhir.jpa.dao.search.LastNOperation; import ca.uhn.fhir.jpa.model.dao.JpaPid; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.search.ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams; import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.search.autocomplete.ValueSetAutocompleteOptions; @@ -141,17 +142,17 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { } @Override - public boolean supportsSomeOf(SearchParameterMap myParams) { - - // keep this in sync with the guts of doSearch + public boolean canUseHibernateSearch(String theResourceType, SearchParameterMap myParams) { boolean requiresHibernateSearchAccess = myParams.containsKey(Constants.PARAM_CONTENT) || myParams.containsKey(Constants.PARAM_TEXT) || myParams.isLastN(); + // we have to use it - _text and _content searches only use hibernate + if (requiresHibernateSearchAccess) { + return true; + } - requiresHibernateSearchAccess |= - myStorageSettings.isAdvancedHSearchIndexing() && myAdvancedIndexQueryBuilder.isSupportsSomeOf(myParams); - - return requiresHibernateSearchAccess; + return myStorageSettings.isAdvancedHSearchIndexing() + && myAdvancedIndexQueryBuilder.canUseHibernateSearch(theResourceType, myParams, mySearchParamRegistry); } @Override @@ -174,6 +175,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { } // keep this in sync with supportsSomeOf(); + @SuppressWarnings("rawtypes") private ISearchQueryExecutor doSearch( String theResourceType, SearchParameterMap theParams, @@ -208,6 +210,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { return DEFAULT_MAX_NON_PAGED_SIZE; } + @SuppressWarnings("rawtypes") private SearchQueryOptionsStep getSearchQueryOptionsStep( String theResourceType, SearchParameterMap theParams, IResourcePersistentId theReferencingPid) { @@ -230,6 +233,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { return query; } + @SuppressWarnings("rawtypes") private PredicateFinalStep buildWhereClause( SearchPredicateFactory f, String theResourceType, @@ -271,8 +275,12 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { * Handle other supported parameters */ if (myStorageSettings.isAdvancedHSearchIndexing() && theParams.getEverythingMode() == null) { - myAdvancedIndexQueryBuilder.addAndConsumeAdvancedQueryClauses( - builder, theResourceType, theParams, mySearchParamRegistry); + ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams params = + new ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams(); + params.setSearchParamRegistry(mySearchParamRegistry) + .setResourceType(theResourceType) + .setSearchParameterMap(theParams); + myAdvancedIndexQueryBuilder.addAndConsumeAdvancedQueryClauses(builder, params); } // DROP EARLY HERE IF BOOL IS EMPTY? }); @@ -283,11 +291,13 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { return Search.session(myEntityManager); } + @SuppressWarnings("rawtypes") private List convertLongsToResourcePersistentIds(List theLongPids) { return theLongPids.stream().map(JpaPid::fromId).collect(Collectors.toList()); } @Override + @SuppressWarnings({"rawtypes", "unchecked"}) public List everything( String theResourceName, SearchParameterMap theParams, @@ -336,6 +346,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { @Transactional() @Override + @SuppressWarnings("unchecked") public List search( String theResourceName, SearchParameterMap theParams, RequestDetails theRequestDetails) { validateHibernateSearchIsEnabled(); @@ -347,6 +358,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { /** * Adapt our async interface to the legacy concrete List */ + @SuppressWarnings("rawtypes") private List toList(ISearchQueryExecutor theSearchResultStream, long theMaxSize) { return StreamSupport.stream(Spliterators.spliteratorUnknownSize(theSearchResultStream, 0), false) .map(JpaPid::fromId) @@ -384,6 +396,7 @@ public class FulltextSearchSvcImpl implements IFulltextSearchSvc { } @Override + @SuppressWarnings("rawtypes") public List lastN(SearchParameterMap theParams, Integer theMaximumResults) { ensureElastic(); dispatchEvent(IHSearchEventListener.HSearchEventType.SEARCH); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java index 6da76807b17..6890b9bc26f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFulltextSearchSvc.java @@ -32,6 +32,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.Collection; import java.util.List; +@SuppressWarnings({"rawtypes"}) public interface IFulltextSearchSvc { /** @@ -79,11 +80,18 @@ public interface IFulltextSearchSvc { ExtendedHSearchIndexData extractLuceneIndexData( IBaseResource theResource, ResourceIndexedSearchParams theNewParams); - boolean supportsSomeOf(SearchParameterMap myParams); + /** + * Returns true if the parameter map can be handled for hibernate search. + * We have to filter out any queries that might use search params + * we only know how to handle in JPA. + * - + * See {@link ca.uhn.fhir.jpa.dao.search.ExtendedHSearchSearchBuilder#addAndConsumeAdvancedQueryClauses} + */ + boolean canUseHibernateSearch(String theResourceType, SearchParameterMap theParameterMap); /** * Re-publish the resource to the full-text index. - * + * - * During update, hibernate search only republishes the entity if it has changed. * During $reindex, we want to force the re-index. * diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/ExtendedHSearchSearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/ExtendedHSearchSearchBuilder.java index b5f2d42ff7d..9f6b6c88261 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/ExtendedHSearchSearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/ExtendedHSearchSearchBuilder.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.search; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.model.search.ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -34,6 +35,7 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; +import ca.uhn.fhir.rest.server.util.ResourceSearchParams; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.commons.collections4.CollectionUtils; @@ -44,6 +46,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static ca.uhn.fhir.rest.api.Constants.PARAMQUALIFIER_MISSING; @@ -59,17 +62,55 @@ public class ExtendedHSearchSearchBuilder { public static final Set ourUnsafeSearchParmeters = Sets.newHashSet("_id", "_meta"); /** - * Are any of the queries supported by our indexing? + * Determine if ExtendedHibernateSearchBuilder can support this parameter + * @param theParamName param name + * @param theActiveParamsForResourceType active search parameters for the desired resource type + * @return whether or not this search parameter is supported in hibernate */ - public boolean isSupportsSomeOf(SearchParameterMap myParams) { - return myParams.getSort() != null - || myParams.getLastUpdated() != null - || myParams.entrySet().stream() - .filter(e -> !ourUnsafeSearchParmeters.contains(e.getKey())) - // each and clause may have a different modifier, so split down to the ORs - .flatMap(andList -> andList.getValue().stream()) - .flatMap(Collection::stream) - .anyMatch(this::isParamTypeSupported); + public boolean supportsSearchParameter(String theParamName, ResourceSearchParams theActiveParamsForResourceType) { + if (theActiveParamsForResourceType == null) { + return false; + } + if (ourUnsafeSearchParmeters.contains(theParamName)) { + return false; + } + if (!theActiveParamsForResourceType.containsParamName(theParamName)) { + return false; + } + return true; + } + + /** + * Are any of the queries supported by our indexing? + * - + * If not, do not use hibernate, because the results will + * be inaccurate and wrong. + */ + public boolean canUseHibernateSearch( + String theResourceType, SearchParameterMap myParams, ISearchParamRegistry theSearchParamRegistry) { + boolean canUseHibernate = true; + ResourceSearchParams resourceActiveSearchParams = theSearchParamRegistry.getActiveSearchParams(theResourceType); + for (String paramName : myParams.keySet()) { + // is this parameter supported? + if (!supportsSearchParameter(paramName, resourceActiveSearchParams)) { + canUseHibernate = false; + } else { + // are the parameter values supported? + canUseHibernate = + myParams.get(paramName).stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()) + .stream() + .anyMatch(this::isParamTypeSupported); + } + + // if not supported, don't use + if (!canUseHibernate) { + return false; + } + } + + return canUseHibernate; } /** @@ -166,86 +207,91 @@ public class ExtendedHSearchSearchBuilder { } public void addAndConsumeAdvancedQueryClauses( - ExtendedHSearchClauseBuilder builder, - String theResourceType, - SearchParameterMap theParams, - ISearchParamRegistry theSearchParamRegistry) { + ExtendedHSearchClauseBuilder theBuilder, + ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams theMethodParams) { + SearchParameterMap searchParameterMap = theMethodParams.getSearchParameterMap(); + String resourceType = theMethodParams.getResourceType(); + ISearchParamRegistry searchParamRegistry = theMethodParams.getSearchParamRegistry(); + // copy the keys to avoid concurrent modification error - ArrayList paramNames = compileParamNames(theParams); + ArrayList paramNames = compileParamNames(searchParameterMap); + ResourceSearchParams activeSearchParams = searchParamRegistry.getActiveSearchParams(resourceType); for (String nextParam : paramNames) { - if (ourUnsafeSearchParmeters.contains(nextParam)) { - continue; - } - RuntimeSearchParam activeParam = theSearchParamRegistry.getActiveSearchParam(theResourceType, nextParam); - if (activeParam == null) { + if (!supportsSearchParameter(nextParam, activeSearchParams)) { // ignore magic params handled in JPA continue; } + RuntimeSearchParam activeParam = activeSearchParams.get(nextParam); // NOTE - keep this in sync with isParamSupported() above. switch (activeParam.getParamType()) { case TOKEN: List> tokenTextAndOrTerms = - theParams.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_TOKEN_TEXT); - builder.addStringTextSearch(nextParam, tokenTextAndOrTerms); + searchParameterMap.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_TOKEN_TEXT); + theBuilder.addStringTextSearch(nextParam, tokenTextAndOrTerms); List> tokenUnmodifiedAndOrTerms = - theParams.removeByNameUnmodified(nextParam); - builder.addTokenUnmodifiedSearch(nextParam, tokenUnmodifiedAndOrTerms); + searchParameterMap.removeByNameUnmodified(nextParam); + theBuilder.addTokenUnmodifiedSearch(nextParam, tokenUnmodifiedAndOrTerms); break; case STRING: List> stringTextAndOrTerms = - theParams.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_TOKEN_TEXT); - builder.addStringTextSearch(nextParam, stringTextAndOrTerms); + searchParameterMap.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_TOKEN_TEXT); + theBuilder.addStringTextSearch(nextParam, stringTextAndOrTerms); - List> stringExactAndOrTerms = - theParams.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_STRING_EXACT); - builder.addStringExactSearch(nextParam, stringExactAndOrTerms); + List> stringExactAndOrTerms = searchParameterMap.removeByNameAndModifier( + nextParam, Constants.PARAMQUALIFIER_STRING_EXACT); + theBuilder.addStringExactSearch(nextParam, stringExactAndOrTerms); List> stringContainsAndOrTerms = - theParams.removeByNameAndModifier(nextParam, Constants.PARAMQUALIFIER_STRING_CONTAINS); - builder.addStringContainsSearch(nextParam, stringContainsAndOrTerms); + searchParameterMap.removeByNameAndModifier( + nextParam, Constants.PARAMQUALIFIER_STRING_CONTAINS); + theBuilder.addStringContainsSearch(nextParam, stringContainsAndOrTerms); - List> stringAndOrTerms = theParams.removeByNameUnmodified(nextParam); - builder.addStringUnmodifiedSearch(nextParam, stringAndOrTerms); + List> stringAndOrTerms = + searchParameterMap.removeByNameUnmodified(nextParam); + theBuilder.addStringUnmodifiedSearch(nextParam, stringAndOrTerms); break; case QUANTITY: - List> quantityAndOrTerms = theParams.removeByNameUnmodified(nextParam); - builder.addQuantityUnmodifiedSearch(nextParam, quantityAndOrTerms); + List> quantityAndOrTerms = + searchParameterMap.removeByNameUnmodified(nextParam); + theBuilder.addQuantityUnmodifiedSearch(nextParam, quantityAndOrTerms); break; case REFERENCE: - List> referenceAndOrTerms = theParams.removeByNameUnmodified(nextParam); - builder.addReferenceUnchainedSearch(nextParam, referenceAndOrTerms); + List> referenceAndOrTerms = + searchParameterMap.removeByNameUnmodified(nextParam); + theBuilder.addReferenceUnchainedSearch(nextParam, referenceAndOrTerms); break; case DATE: List> dateAndOrTerms = nextParam.equalsIgnoreCase("_lastupdated") - ? getLastUpdatedAndOrList(theParams) - : theParams.removeByNameUnmodified(nextParam); - builder.addDateUnmodifiedSearch(nextParam, dateAndOrTerms); + ? getLastUpdatedAndOrList(searchParameterMap) + : searchParameterMap.removeByNameUnmodified(nextParam); + theBuilder.addDateUnmodifiedSearch(nextParam, dateAndOrTerms); break; case COMPOSITE: - List> compositeAndOrTerms = theParams.removeByNameUnmodified(nextParam); + List> compositeAndOrTerms = + searchParameterMap.removeByNameUnmodified(nextParam); // RuntimeSearchParam only points to the subs by reference. Resolve here while we have // ISearchParamRegistry List subSearchParams = - JpaParamUtil.resolveCompositeComponentsDeclaredOrder(theSearchParamRegistry, activeParam); - builder.addCompositeUnmodifiedSearch(activeParam, subSearchParams, compositeAndOrTerms); + JpaParamUtil.resolveCompositeComponentsDeclaredOrder(searchParamRegistry, activeParam); + theBuilder.addCompositeUnmodifiedSearch(activeParam, subSearchParams, compositeAndOrTerms); break; case URI: List> uriUnmodifiedAndOrTerms = - theParams.removeByNameUnmodified(nextParam); - builder.addUriUnmodifiedSearch(nextParam, uriUnmodifiedAndOrTerms); + searchParameterMap.removeByNameUnmodified(nextParam); + theBuilder.addUriUnmodifiedSearch(nextParam, uriUnmodifiedAndOrTerms); break; case NUMBER: - List> numberUnmodifiedAndOrTerms = theParams.remove(nextParam); - builder.addNumberUnmodifiedSearch(nextParam, numberUnmodifiedAndOrTerms); + List> numberUnmodifiedAndOrTerms = searchParameterMap.remove(nextParam); + theBuilder.addNumberUnmodifiedSearch(nextParam, numberUnmodifiedAndOrTerms); break; default: diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/LastNOperation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/LastNOperation.java index 1263bf027f5..823a2893a5f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/LastNOperation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/search/LastNOperation.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.dao.search; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.StorageSettings; +import ca.uhn.fhir.jpa.model.search.ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; @@ -67,8 +68,12 @@ public class LastNOperation { b.must(f.match().field("myResourceType").matching(OBSERVATION_RES_TYPE)); ExtendedHSearchClauseBuilder builder = new ExtendedHSearchClauseBuilder(myFhirContext, myStorageSettings, b, f); - myExtendedHSearchSearchBuilder.addAndConsumeAdvancedQueryClauses( - builder, OBSERVATION_RES_TYPE, theParams.clone(), mySearchParamRegistry); + ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams params = + new ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams(); + params.setResourceType(OBSERVATION_RES_TYPE) + .setSearchParameterMap(theParams.clone()) + .setSearchParamRegistry(mySearchParamRegistry); + myExtendedHSearchSearchBuilder.addAndConsumeAdvancedQueryClauses(builder, params); })) .aggregation(observationsByCodeKey, f -> f.fromJson(lastNAggregation.toAggregation())) .fetch(0); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/search/ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/search/ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams.java new file mode 100644 index 00000000000..3bb80396a0f --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/model/search/ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams.java @@ -0,0 +1,73 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package ca.uhn.fhir.jpa.model.search; + +import ca.uhn.fhir.jpa.dao.search.ExtendedHSearchClauseBuilder; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; + +/** + * This is a parameter class for the + * {@link ca.uhn.fhir.jpa.dao.search.ExtendedHSearchSearchBuilder#addAndConsumeAdvancedQueryClauses(ExtendedHSearchClauseBuilder, ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams)} + * method, so that we can keep the signature manageable (small) and allow for updates without breaking + * implementers so often. + */ +public class ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams { + /** + * Resource type + */ + private String myResourceType; + /** + * The registered search + */ + private SearchParameterMap mySearchParameterMap; + /** + * Search param registry + */ + private ISearchParamRegistry mySearchParamRegistry; + + public String getResourceType() { + return myResourceType; + } + + public ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams setResourceType(String theResourceType) { + myResourceType = theResourceType; + return this; + } + + public SearchParameterMap getSearchParameterMap() { + return mySearchParameterMap; + } + + public ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams setSearchParameterMap(SearchParameterMap theParams) { + mySearchParameterMap = theParams; + return this; + } + + public ISearchParamRegistry getSearchParamRegistry() { + return mySearchParamRegistry; + } + + public ExtendedHSearchBuilderConsumeAdvancedQueryClausesParams setSearchParamRegistry( + ISearchParamRegistry theSearchParamRegistry) { + mySearchParamRegistry = theSearchParamRegistry; + return this; + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java index 9843034bee1..ec4f7fa16e5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java @@ -79,7 +79,7 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl ourLog.trace("Done fetching search resource PIDs"); int countOfPids = pids.size(); - ; + int maxSize = Math.min(theToIndex - theFromIndex, countOfPids); thePageBuilder.setTotalRequestedResourcesFetched(countOfPids); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index 064e34b5942..a2bc4a4fcf5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -19,6 +19,8 @@ */ package ca.uhn.fhir.jpa.search.builder; +import ca.uhn.fhir.context.BaseRuntimeChildDefinition; +import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.ComboSearchParamType; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; @@ -93,6 +95,7 @@ import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StringUtil; import ca.uhn.fhir.util.UrlUtil; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Streams; import com.healthmarketscience.sqlbuilder.Condition; import jakarta.annotation.Nonnull; @@ -163,7 +166,7 @@ public class SearchBuilder implements ISearchBuilder { public static boolean myUseMaxPageSize50ForTest = false; protected final IInterceptorBroadcaster myInterceptorBroadcaster; protected final IResourceTagDao myResourceTagDao; - private final String myResourceName; + private String myResourceName; private final Class myResourceType; private final HapiFhirLocalContainerEntityManagerFactoryBean myEntityManagerFactory; private final SqlObjectFactory mySqlBuilderFactory; @@ -195,12 +198,16 @@ public class SearchBuilder implements ISearchBuilder { @Autowired(required = false) private IElasticsearchSvc myIElasticsearchSvc; + @Autowired + private FhirContext myCtx; + @Autowired private IJpaStorageResourceParser myJpaStorageResourceParser; /** * Constructor */ + @SuppressWarnings({"rawtypes", "unchecked"}) public SearchBuilder( IDao theDao, String theResourceName, @@ -235,6 +242,11 @@ public class SearchBuilder implements ISearchBuilder { myIdHelperService = theIdHelperService; } + @VisibleForTesting + void setResourceName(String theName) { + myResourceName = theName; + } + @Override public void setMaxResultsToFetch(Integer theMaxResultsToFetch) { myMaxResultsToFetch = theMaxResultsToFetch; @@ -260,8 +272,6 @@ public class SearchBuilder implements ISearchBuilder { attemptComboUniqueSpProcessing(theQueryStack, theParams, theRequest); } - SearchContainedModeEnum searchContainedMode = theParams.getSearchContainedMode(); - // Handle _id and _tag last, since they can typically be tacked onto a different parameter List paramNames = myParams.keySet().stream() .filter(t -> !t.equals(IAnyResource.SP_RES_ID)) @@ -394,7 +404,8 @@ public class SearchBuilder implements ISearchBuilder { } if (fulltextExecutor == null) { - fulltextExecutor = SearchQueryExecutors.from(fulltextMatchIds); + fulltextExecutor = + SearchQueryExecutors.from(fulltextMatchIds != null ? fulltextMatchIds : new ArrayList<>()); } if (theSearchRuntimeDetails != null) { @@ -481,7 +492,7 @@ public class SearchBuilder implements ISearchBuilder { return fulltextEnabled && myParams != null && myParams.getSearchContainedMode() == SearchContainedModeEnum.FALSE - && myFulltextSearchSvc.supportsSomeOf(myParams) + && myFulltextSearchSvc.canUseHibernateSearch(myResourceName, myParams) && myFulltextSearchSvc.supportsAllSortTerms(myResourceName, myParams); } @@ -533,8 +544,7 @@ public class SearchBuilder implements ISearchBuilder { pid = myIdHelperService.resolveResourcePersistentIds(myRequestPartitionId, myResourceName, idParamValue); } - List pids = myFulltextSearchSvc.everything(myResourceName, myParams, pid, theRequestDetails); - return pids; + return myFulltextSearchSvc.everything(myResourceName, myParams, pid, theRequestDetails); } private void doCreateChunkedQueries( @@ -857,13 +867,8 @@ public class SearchBuilder implements ISearchBuilder { theQueryStack.addSortOnLastUpdated(ascending); } else { - - RuntimeSearchParam param = null; - - if (param == null) { - // do we have a composition param defined for the whole chain? - param = mySearchParamRegistry.getActiveSearchParam(myResourceName, theSort.getParamName()); - } + RuntimeSearchParam param = + mySearchParamRegistry.getActiveSearchParam(myResourceName, theSort.getParamName()); /* * If we have a sort like _sort=subject.name and we have an @@ -891,9 +896,7 @@ public class SearchBuilder implements ISearchBuilder { mySearchParamRegistry.getActiveSearchParam(myResourceName, referenceParam); if (outerParam == null) { throwInvalidRequestExceptionForUnknownSortParameter(myResourceName, referenceParam); - } - - if (outerParam.hasUpliftRefchain(targetParam)) { + } else if (outerParam.hasUpliftRefchain(targetParam)) { for (String nextTargetType : outerParam.getTargets()) { if (referenceParamTargetType != null && !referenceParamTargetType.equals(nextTargetType)) { continue; @@ -940,6 +943,9 @@ public class SearchBuilder implements ISearchBuilder { throwInvalidRequestExceptionForUnknownSortParameter(getResourceName(), paramName); } + // param will never be null here (the above line throws if it does) + // this is just to prevent the warning + assert param != null; if (isNotBlank(chainName) && param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) { throw new InvalidRequestException( Msg.code(2285) + "Invalid chain, " + paramName + " is not a reference SearchParameter"); @@ -1116,11 +1122,15 @@ public class SearchBuilder implements ISearchBuilder { resourceType, next, tagMap.get(next.getId()), theForHistoryOperation); } if (resource == null) { - ourLog.warn( - "Unable to find resource {}/{}/_history/{} in database", - next.getResourceType(), - next.getIdDt().getIdPart(), - next.getVersion()); + if (next != null) { + ourLog.warn( + "Unable to find resource {}/{}/_history/{} in database", + next.getResourceType(), + next.getIdDt().getIdPart(), + next.getVersion()); + } else { + ourLog.warn("Unable to find resource in database."); + } continue; } @@ -1191,7 +1201,6 @@ public class SearchBuilder implements ISearchBuilder { RequestDetails theDetails) { if (thePids.isEmpty()) { ourLog.debug("The include pids are empty"); - // return; } // Dupes will cause a crash later anyhow, but this is expensive so only do it @@ -1251,10 +1260,9 @@ public class SearchBuilder implements ISearchBuilder { // only impl // to handle lastN? if (myStorageSettings.isAdvancedHSearchIndexing() && myStorageSettings.isStoreResourceInHSearchIndex()) { - List pidList = thePids.stream().map(pid -> (pid).getId()).collect(Collectors.toList()); + List pidList = thePids.stream().map(JpaPid::getId).collect(Collectors.toList()); - List resources = myFulltextSearchSvc.getResources(pidList); - return resources; + return myFulltextSearchSvc.getResources(pidList); } else if (!Objects.isNull(myParams) && myParams.isLastN()) { // legacy LastN implementation return myIElasticsearchSvc.getObservationResources(thePids); @@ -1309,7 +1317,7 @@ public class SearchBuilder implements ISearchBuilder { Pointcut.JPA_PERFTRACE_RAW_SQL, myInterceptorBroadcaster, theParameters.getRequestDetails())) { CurrentThreadCaptureQueriesListener.startCapturing(); } - if (matches.size() == 0) { + if (matches.isEmpty()) { return new HashSet<>(); } if (currentIncludes == null || currentIncludes.isEmpty()) { @@ -1339,7 +1347,7 @@ public class SearchBuilder implements ISearchBuilder { for (Iterator iter = includes.iterator(); iter.hasNext(); ) { Include nextInclude = iter.next(); - if (nextInclude.isRecurse() == false) { + if (!nextInclude.isRecurse()) { iter.remove(); } @@ -1356,196 +1364,32 @@ public class SearchBuilder implements ISearchBuilder { } if (matchAll) { - StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("SELECT r.").append(findPidFieldName); - sqlBuilder.append(", r.").append(findResourceTypeFieldName); - if (findVersionFieldName != null) { - sqlBuilder.append(", r.").append(findVersionFieldName); - } - sqlBuilder.append(" FROM ResourceLink r WHERE "); - - sqlBuilder.append("r."); - sqlBuilder.append(searchPidFieldName); // (rev mode) target_resource_id | source_resource_id - sqlBuilder.append(" IN (:target_pids)"); - - /* - * We need to set the resource type in 2 cases only: - * 1) we are in $everything mode - * (where we only want to fetch specific resource types, regardless of what is - * available to fetch) - * 2) we are doing revincludes - * - * Technically if the request is a qualified star (e.g. _include=Observation:*) we - * should always be checking the source resource type on the resource link. We don't - * actually index that column though by default, so in order to try and be efficient - * we don't actually include it for includes (but we do for revincludes). This is - * because for an include, it doesn't really make sense to include a different - * resource type than the one you are searching on. - */ - if (wantResourceType != null - && (reverseMode || (myParams != null && myParams.getEverythingMode() != null))) { - // because mySourceResourceType is not part of the HFJ_RES_LINK - // index, this might not be the most optimal performance. - // but it is for an $everything operation (and maybe we should update the index) - sqlBuilder.append(" AND r.mySourceResourceType = :want_resource_type"); - } else { - wantResourceType = null; - } - - // When calling $everything on a Patient instance, we don't want to recurse into new Patient - // resources - // (e.g. via Provenance, List, or Group) when in an $everything operation - if (myParams != null - && myParams.getEverythingMode() == SearchParameterMap.EverythingModeEnum.PATIENT_INSTANCE) { - sqlBuilder.append(" AND r.myTargetResourceType != 'Patient'"); - sqlBuilder.append(UNDESIRED_RESOURCE_LINKAGES_FOR_EVERYTHING_ON_PATIENT_INSTANCE.stream() - .collect(Collectors.joining("', '", " AND r.mySourceResourceType NOT IN ('", "')"))); - } - if (hasDesiredResourceTypes) { - sqlBuilder.append(" AND r.myTargetResourceType IN (:desired_target_resource_types)"); - } - - String sql = sqlBuilder.toString(); - List> partitions = partition(nextRoundMatches, getMaximumPageSize()); - for (Collection nextPartition : partitions) { - TypedQuery q = entityManager.createQuery(sql, Object[].class); - q.setParameter("target_pids", JpaPid.toLongList(nextPartition)); - if (wantResourceType != null) { - q.setParameter("want_resource_type", wantResourceType); - } - if (maxCount != null) { - q.setMaxResults(maxCount); - } - if (hasDesiredResourceTypes) { - q.setParameter("desired_target_resource_types", desiredResourceTypes); - } - List results = q.getResultList(); - for (Object nextRow : results) { - if (nextRow == null) { - // This can happen if there are outgoing references which are canonical or point to - // other servers - continue; - } - - Long version = null; - Long resourceLink = (Long) ((Object[]) nextRow)[0]; - String resourceType = (String) ((Object[]) nextRow)[1]; - if (findVersionFieldName != null) { - version = (Long) ((Object[]) nextRow)[2]; - } - - if (resourceLink != null) { - JpaPid pid = - JpaPid.fromIdAndVersionAndResourceType(resourceLink, version, resourceType); - pidsToInclude.add(pid); - } - } - } + loadIncludesMatchAll( + findPidFieldName, + findResourceTypeFieldName, + findVersionFieldName, + searchPidFieldName, + wantResourceType, + reverseMode, + hasDesiredResourceTypes, + nextRoundMatches, + entityManager, + maxCount, + desiredResourceTypes, + pidsToInclude, + request); } else { - List paths; - - // Start replace - RuntimeSearchParam param; - String resType = nextInclude.getParamType(); - if (isBlank(resType)) { - continue; - } - RuntimeResourceDefinition def = fhirContext.getResourceDefinition(resType); - if (def == null) { - ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue()); - continue; - } - - String paramName = nextInclude.getParamName(); - if (isNotBlank(paramName)) { - param = mySearchParamRegistry.getActiveSearchParam(resType, paramName); - } else { - param = null; - } - if (param == null) { - ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue()); - continue; - } - - paths = param.getPathsSplitForResourceType(resType); - // end replace - - Set targetResourceTypes = computeTargetResourceTypes(nextInclude, param); - - for (String nextPath : paths) { - String findPidFieldSqlColumn = findPidFieldName.equals(MY_SOURCE_RESOURCE_PID) - ? "src_resource_id" - : "target_resource_id"; - String fieldsToLoad = "r." + findPidFieldSqlColumn + " AS " + RESOURCE_ID_ALIAS; - if (findVersionFieldName != null) { - fieldsToLoad += ", r.target_resource_version AS " + RESOURCE_VERSION_ALIAS; - } - - // Query for includes lookup has 2 cases - // Case 1: Where target_resource_id is available in hfj_res_link table for local references - // Case 2: Where target_resource_id is null in hfj_res_link table and referred by a canonical - // url in target_resource_url - - // Case 1: - Map localReferenceQueryParams = new HashMap<>(); - - String searchPidFieldSqlColumn = searchPidFieldName.equals(MY_TARGET_RESOURCE_PID) - ? "target_resource_id" - : "src_resource_id"; - StringBuilder localReferenceQuery = - new StringBuilder("SELECT " + fieldsToLoad + " FROM hfj_res_link r " - + " WHERE r.src_path = :src_path AND " - + " r.target_resource_id IS NOT NULL AND " - + " r." - + searchPidFieldSqlColumn + " IN (:target_pids) "); - localReferenceQueryParams.put("src_path", nextPath); - // we loop over target_pids later. - if (targetResourceTypes != null) { - if (targetResourceTypes.size() == 1) { - localReferenceQuery.append(" AND r.target_resource_type = :target_resource_type "); - localReferenceQueryParams.put( - "target_resource_type", - targetResourceTypes.iterator().next()); - } else { - localReferenceQuery.append(" AND r.target_resource_type in (:target_resource_types) "); - localReferenceQueryParams.put("target_resource_types", targetResourceTypes); - } - } - - // Case 2: - Pair> canonicalQuery = buildCanonicalUrlQuery( - findVersionFieldName, searchPidFieldSqlColumn, targetResourceTypes); - - // @formatter:on - - String sql = localReferenceQuery + " UNION " + canonicalQuery.getLeft(); - - List> partitions = partition(nextRoundMatches, getMaximumPageSize()); - for (Collection nextPartition : partitions) { - Query q = entityManager.createNativeQuery(sql, Tuple.class); - q.setParameter("target_pids", JpaPid.toLongList(nextPartition)); - localReferenceQueryParams.forEach(q::setParameter); - canonicalQuery.getRight().forEach(q::setParameter); - - if (maxCount != null) { - q.setMaxResults(maxCount); - } - @SuppressWarnings("unchecked") - List results = q.getResultList(); - for (Tuple result : results) { - if (result != null) { - Long resourceId = - NumberUtils.createLong(String.valueOf(result.get(RESOURCE_ID_ALIAS))); - Long resourceVersion = null; - if (findVersionFieldName != null && result.get(RESOURCE_VERSION_ALIAS) != null) { - resourceVersion = NumberUtils.createLong( - String.valueOf(result.get(RESOURCE_VERSION_ALIAS))); - } - pidsToInclude.add(JpaPid.fromIdAndVersion(resourceId, resourceVersion)); - } - } - } - } + loadIncludesMatchSpecific( + nextInclude, + fhirContext, + findPidFieldName, + findVersionFieldName, + searchPidFieldName, + reverseMode, + nextRoundMatches, + entityManager, + maxCount, + pidsToInclude); } } @@ -1609,9 +1453,266 @@ public class SearchBuilder implements ISearchBuilder { return allAdded; } + private void loadIncludesMatchSpecific( + Include nextInclude, + FhirContext fhirContext, + String findPidFieldName, + String findVersionFieldName, + String searchPidFieldName, + boolean reverseMode, + List nextRoundMatches, + EntityManager entityManager, + Integer maxCount, + HashSet pidsToInclude) { + List paths; + + // Start replace + RuntimeSearchParam param; + String resType = nextInclude.getParamType(); + if (isBlank(resType)) { + return; + } + RuntimeResourceDefinition def = fhirContext.getResourceDefinition(resType); + if (def == null) { + ourLog.warn("Unknown resource type in include/revinclude=" + nextInclude.getValue()); + return; + } + + String paramName = nextInclude.getParamName(); + if (isNotBlank(paramName)) { + param = mySearchParamRegistry.getActiveSearchParam(resType, paramName); + } else { + param = null; + } + if (param == null) { + ourLog.warn("Unknown param name in include/revinclude=" + nextInclude.getValue()); + return; + } + + paths = param.getPathsSplitForResourceType(resType); + // end replace + + Set targetResourceTypes = computeTargetResourceTypes(nextInclude, param); + + for (String nextPath : paths) { + String findPidFieldSqlColumn = + findPidFieldName.equals(MY_SOURCE_RESOURCE_PID) ? "src_resource_id" : "target_resource_id"; + String fieldsToLoad = "r." + findPidFieldSqlColumn + " AS " + RESOURCE_ID_ALIAS; + if (findVersionFieldName != null) { + fieldsToLoad += ", r.target_resource_version AS " + RESOURCE_VERSION_ALIAS; + } + + // Query for includes lookup has 2 cases + // Case 1: Where target_resource_id is available in hfj_res_link table for local references + // Case 2: Where target_resource_id is null in hfj_res_link table and referred by a canonical + // url in target_resource_url + + // Case 1: + Map localReferenceQueryParams = new HashMap<>(); + + String searchPidFieldSqlColumn = + searchPidFieldName.equals(MY_TARGET_RESOURCE_PID) ? "target_resource_id" : "src_resource_id"; + StringBuilder localReferenceQuery = new StringBuilder("SELECT " + fieldsToLoad + " FROM hfj_res_link r " + + " WHERE r.src_path = :src_path AND " + + " r.target_resource_id IS NOT NULL AND " + + " r." + + searchPidFieldSqlColumn + " IN (:target_pids) "); + localReferenceQueryParams.put("src_path", nextPath); + // we loop over target_pids later. + if (targetResourceTypes != null) { + if (targetResourceTypes.size() == 1) { + localReferenceQuery.append(" AND r.target_resource_type = :target_resource_type "); + localReferenceQueryParams.put( + "target_resource_type", + targetResourceTypes.iterator().next()); + } else { + localReferenceQuery.append(" AND r.target_resource_type in (:target_resource_types) "); + localReferenceQueryParams.put("target_resource_types", targetResourceTypes); + } + } + + // Case 2: + Pair> canonicalQuery = + buildCanonicalUrlQuery(findVersionFieldName, targetResourceTypes, reverseMode); + + String sql = localReferenceQuery + " UNION " + canonicalQuery.getLeft(); + + List> partitions = partition(nextRoundMatches, getMaximumPageSize()); + for (Collection nextPartition : partitions) { + Query q = entityManager.createNativeQuery(sql, Tuple.class); + q.setParameter("target_pids", JpaPid.toLongList(nextPartition)); + localReferenceQueryParams.forEach(q::setParameter); + canonicalQuery.getRight().forEach(q::setParameter); + + if (maxCount != null) { + q.setMaxResults(maxCount); + } + @SuppressWarnings("unchecked") + List results = q.getResultList(); + for (Tuple result : results) { + if (result != null) { + Long resourceId = NumberUtils.createLong(String.valueOf(result.get(RESOURCE_ID_ALIAS))); + Long resourceVersion = null; + if (findVersionFieldName != null && result.get(RESOURCE_VERSION_ALIAS) != null) { + resourceVersion = + NumberUtils.createLong(String.valueOf(result.get(RESOURCE_VERSION_ALIAS))); + } + pidsToInclude.add(JpaPid.fromIdAndVersion(resourceId, resourceVersion)); + } + } + } + } + } + + private void loadIncludesMatchAll( + String findPidFieldName, + String findResourceTypeFieldName, + String findVersionFieldName, + String searchPidFieldName, + String wantResourceType, + boolean reverseMode, + boolean hasDesiredResourceTypes, + List nextRoundMatches, + EntityManager entityManager, + Integer maxCount, + List desiredResourceTypes, + HashSet pidsToInclude, + RequestDetails request) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT r.").append(findPidFieldName); + sqlBuilder.append(", r.").append(findResourceTypeFieldName); + sqlBuilder.append(", r.myTargetResourceUrl"); + if (findVersionFieldName != null) { + sqlBuilder.append(", r.").append(findVersionFieldName); + } + sqlBuilder.append(" FROM ResourceLink r WHERE "); + + sqlBuilder.append("r."); + sqlBuilder.append(searchPidFieldName); // (rev mode) target_resource_id | source_resource_id + sqlBuilder.append(" IN (:target_pids)"); + + /* + * We need to set the resource type in 2 cases only: + * 1) we are in $everything mode + * (where we only want to fetch specific resource types, regardless of what is + * available to fetch) + * 2) we are doing revincludes + * + * Technically if the request is a qualified star (e.g. _include=Observation:*) we + * should always be checking the source resource type on the resource link. We don't + * actually index that column though by default, so in order to try and be efficient + * we don't actually include it for includes (but we do for revincludes). This is + * because for an include, it doesn't really make sense to include a different + * resource type than the one you are searching on. + */ + if (wantResourceType != null && (reverseMode || (myParams != null && myParams.getEverythingMode() != null))) { + // because mySourceResourceType is not part of the HFJ_RES_LINK + // index, this might not be the most optimal performance. + // but it is for an $everything operation (and maybe we should update the index) + sqlBuilder.append(" AND r.mySourceResourceType = :want_resource_type"); + } else { + wantResourceType = null; + } + + // When calling $everything on a Patient instance, we don't want to recurse into new Patient + // resources + // (e.g. via Provenance, List, or Group) when in an $everything operation + if (myParams != null + && myParams.getEverythingMode() == SearchParameterMap.EverythingModeEnum.PATIENT_INSTANCE) { + sqlBuilder.append(" AND r.myTargetResourceType != 'Patient'"); + sqlBuilder.append(UNDESIRED_RESOURCE_LINKAGES_FOR_EVERYTHING_ON_PATIENT_INSTANCE.stream() + .collect(Collectors.joining("', '", " AND r.mySourceResourceType NOT IN ('", "')"))); + } + if (hasDesiredResourceTypes) { + sqlBuilder.append(" AND r.myTargetResourceType IN (:desired_target_resource_types)"); + } + + String sql = sqlBuilder.toString(); + List> partitions = partition(nextRoundMatches, getMaximumPageSize()); + for (Collection nextPartition : partitions) { + TypedQuery q = entityManager.createQuery(sql, Object[].class); + q.setParameter("target_pids", JpaPid.toLongList(nextPartition)); + if (wantResourceType != null) { + q.setParameter("want_resource_type", wantResourceType); + } + if (maxCount != null) { + q.setMaxResults(maxCount); + } + if (hasDesiredResourceTypes) { + q.setParameter("desired_target_resource_types", desiredResourceTypes); + } + List results = q.getResultList(); + Set canonicalUrls = null; + for (Object nextRow : results) { + if (nextRow == null) { + // This can happen if there are outgoing references which are canonical or point to + // other servers + continue; + } + + Long version = null; + Long resourceId = (Long) ((Object[]) nextRow)[0]; + String resourceType = (String) ((Object[]) nextRow)[1]; + String resourceCanonicalUrl = (String) ((Object[]) nextRow)[2]; + if (findVersionFieldName != null) { + version = (Long) ((Object[]) nextRow)[3]; + } + + if (resourceId != null) { + JpaPid pid = JpaPid.fromIdAndVersionAndResourceType(resourceId, version, resourceType); + pidsToInclude.add(pid); + } else if (resourceCanonicalUrl != null) { + if (canonicalUrls == null) { + canonicalUrls = new HashSet<>(); + } + canonicalUrls.add(resourceCanonicalUrl); + } + } + + if (canonicalUrls != null) { + String message = + "Search with _include=* can be inefficient when references using canonical URLs are detected. Use more specific _include values instead."; + firePerformanceWarning(request, message); + loadCanonicalUrls(canonicalUrls, entityManager, pidsToInclude, reverseMode); + } + } + } + + private void loadCanonicalUrls( + Set theCanonicalUrls, + EntityManager theEntityManager, + HashSet thePidsToInclude, + boolean theReverse) { + StringBuilder sqlBuilder; + Set identityHashesForTypes = calculateIndexUriIdentityHashesForResourceTypes(null, theReverse); + List> canonicalUrlPartitions = + partition(theCanonicalUrls, getMaximumPageSize() - identityHashesForTypes.size()); + + sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT i.myResourcePid "); + sqlBuilder.append("FROM ResourceIndexedSearchParamUri i "); + sqlBuilder.append("WHERE i.myHashIdentity IN (:hash_identity) "); + sqlBuilder.append("AND i.myUri IN (:uris)"); + + String canonicalResSql = sqlBuilder.toString(); + + for (Collection nextCanonicalUrlList : canonicalUrlPartitions) { + TypedQuery canonicalResIdQuery = theEntityManager.createQuery(canonicalResSql, Long.class); + canonicalResIdQuery.setParameter("hash_identity", identityHashesForTypes); + canonicalResIdQuery.setParameter("uris", nextCanonicalUrlList); + List resIds = canonicalResIdQuery.getResultList(); + for (var next : resIds) { + if (next != null) { + thePidsToInclude.add(JpaPid.fromId(next)); + } + } + } + } + /** - * Given a - * @param request + * Calls Performance Trace Hook + * @param request the request deatils + * Sends a raw SQL query to the Pointcut for raw SQL queries. */ private void callRawSqlHookWithCurrentThreadQueries(RequestDetails request) { SqlQueryList capturedQueries = CurrentThreadCaptureQueriesListener.getCurrentQueueAndStopCapturing(); @@ -1641,30 +1742,22 @@ public class SearchBuilder implements ISearchBuilder { @Nonnull private Pair> buildCanonicalUrlQuery( - String theVersionFieldName, String thePidFieldSqlColumn, Set theTargetResourceTypes) { - String fieldsToLoadFromSpidxUriTable = "rUri.res_id"; + String theVersionFieldName, Set theTargetResourceTypes, boolean theReverse) { + String fieldsToLoadFromSpidxUriTable = theReverse ? "r.src_resource_id" : "rUri.res_id"; if (theVersionFieldName != null) { // canonical-uri references aren't versioned, but we need to match the column count for the UNION fieldsToLoadFromSpidxUriTable += ", NULL"; } // The logical join will be by hfj_spidx_uri on sp_name='uri' and sp_uri=target_resource_url. // But sp_name isn't indexed, so we use hash_identity instead. - if (theTargetResourceTypes == null) { - // hash_identity includes the resource type. So a null wildcard must be replaced with a list of all types. - theTargetResourceTypes = myDaoRegistry.getRegisteredDaoTypes(); - } - assert !theTargetResourceTypes.isEmpty(); - - Set identityHashesForTypes = theTargetResourceTypes.stream() - .map(type -> BaseResourceIndexedSearchParam.calculateHashIdentity( - myPartitionSettings, myRequestPartitionId, type, "url")) - .collect(Collectors.toSet()); + Set identityHashesForTypes = + calculateIndexUriIdentityHashesForResourceTypes(theTargetResourceTypes, theReverse); Map canonicalUriQueryParams = new HashMap<>(); StringBuilder canonicalUrlQuery = new StringBuilder( "SELECT " + fieldsToLoadFromSpidxUriTable + " FROM hfj_res_link r " + " JOIN hfj_spidx_uri rUri ON ( "); // join on hash_identity and sp_uri - indexed in IDX_SP_URI_HASH_IDENTITY_V2 - if (theTargetResourceTypes.size() == 1) { + if (theTargetResourceTypes != null && theTargetResourceTypes.size() == 1) { canonicalUrlQuery.append(" rUri.hash_identity = :uri_identity_hash "); canonicalUriQueryParams.put( "uri_identity_hash", identityHashesForTypes.iterator().next()); @@ -1673,21 +1766,102 @@ public class SearchBuilder implements ISearchBuilder { canonicalUriQueryParams.put("uri_identity_hashes", identityHashesForTypes); } - canonicalUrlQuery.append(" AND r.target_resource_url = rUri.sp_uri )" + " WHERE r.src_path = :src_path AND " - + " r.target_resource_id IS NULL AND " - + " r." - + thePidFieldSqlColumn + " IN (:target_pids) "); + canonicalUrlQuery.append(" AND r.target_resource_url = rUri.sp_uri )"); + canonicalUrlQuery.append(" WHERE r.src_path = :src_path AND "); + canonicalUrlQuery.append(" r.target_resource_id IS NULL "); + canonicalUrlQuery.append(" AND "); + if (theReverse) { + canonicalUrlQuery.append("rUri.res_id"); + } else { + canonicalUrlQuery.append("r.src_resource_id"); + } + canonicalUrlQuery.append(" IN (:target_pids) "); + return Pair.of(canonicalUrlQuery.toString(), canonicalUriQueryParams); } - private List> partition(Collection theNextRoundMatches, int theMaxLoad) { + @Nonnull + Set calculateIndexUriIdentityHashesForResourceTypes(Set theTargetResourceTypes, boolean theReverse) { + Set targetResourceTypes = theTargetResourceTypes; + if (targetResourceTypes == null) { + /* + * If we don't have a list of valid target types, we need to figure out a list of all + * possible target types in order to perform the search of the URI index table. This is + * because the hash_identity column encodes the resource type, so we'll need a hash + * value for each possible target type. + */ + targetResourceTypes = new HashSet<>(); + Set possibleTypes = myDaoRegistry.getRegisteredDaoTypes(); + if (theReverse) { + // For reverse includes, it is really hard to figure out what types + // are actually potentially pointing to the type we're searching for + // in this context, so let's just assume it could be anything. + targetResourceTypes = possibleTypes; + } else { + for (var next : mySearchParamRegistry.getActiveSearchParams(myResourceName).values().stream() + .filter(t -> t.getParamType().equals(RestSearchParameterTypeEnum.REFERENCE)) + .collect(Collectors.toList())) { + + // If the reference points to a Reference (ie not a canonical or CanonicalReference) + // then it doesn't matter here anyhow. The logic here only works for elements at the + // root level of the document (e.g. QuestionnaireResponse.subject or + // QuestionnaireResponse.subject.where(...)) but this is just an optimization + // anyhow. + if (next.getPath().startsWith(myResourceName + ".")) { + String elementName = + next.getPath().substring(next.getPath().indexOf('.') + 1); + int secondDotIndex = elementName.indexOf('.'); + if (secondDotIndex != -1) { + elementName = elementName.substring(0, secondDotIndex); + } + BaseRuntimeChildDefinition child = + myContext.getResourceDefinition(myResourceName).getChildByName(elementName); + if (child != null) { + BaseRuntimeElementDefinition childDef = child.getChildByName(elementName); + if (childDef != null) { + if (childDef.getName().equals("Reference")) { + continue; + } + } + } + } + + if (!next.getTargets().isEmpty()) { + // For each reference parameter on the resource type we're searching for, + // add all the potential target types to the list of possible target + // resource types we can look up. + for (var nextTarget : next.getTargets()) { + if (possibleTypes.contains(nextTarget)) { + targetResourceTypes.add(nextTarget); + } + } + } else { + // If we have any references that don't define any target types, then + // we need to assume that all enabled resource types are possible target + // types + targetResourceTypes.addAll(possibleTypes); + break; + } + } + } + } + assert !targetResourceTypes.isEmpty(); + + Set identityHashesForTypes = targetResourceTypes.stream() + .map(type -> BaseResourceIndexedSearchParam.calculateHashIdentity( + myPartitionSettings, myRequestPartitionId, type, "url")) + .collect(Collectors.toSet()); + return identityHashesForTypes; + } + + private List> partition(Collection theNextRoundMatches, int theMaxLoad) { if (theNextRoundMatches.size() <= theMaxLoad) { return Collections.singletonList(theNextRoundMatches); } else { - List> retVal = new ArrayList<>(); - Collection current = null; - for (JpaPid next : theNextRoundMatches) { + List> retVal = new ArrayList<>(); + Collection current = null; + for (T next : theNextRoundMatches) { if (current == null) { current = new ArrayList<>(theMaxLoad); retVal.add(current); @@ -1721,7 +1895,7 @@ public class SearchBuilder implements ISearchBuilder { for (RuntimeSearchParam nextCandidate : candidateComboParams) { List nextCandidateParamNames = JpaParamUtil.resolveComponentParameters(mySearchParamRegistry, nextCandidate).stream() - .map(t -> t.getName()) + .map(RuntimeSearchParam::getName) .collect(Collectors.toList()); if (theParams.keySet().containsAll(nextCandidateParamNames)) { comboParam = nextCandidate; @@ -1733,7 +1907,7 @@ public class SearchBuilder implements ISearchBuilder { if (comboParam != null) { // Since we're going to remove elements below - theParams.values().forEach(nextAndList -> ensureSubListsAreWritable(nextAndList)); + theParams.values().forEach(this::ensureSubListsAreWritable); StringBuilder sb = new StringBuilder(); sb.append(myResourceName); @@ -2121,19 +2295,11 @@ public class SearchBuilder implements ISearchBuilder { } private void sendProcessingMsgAndFirePerformanceHook() { - StorageProcessingMessage message = new StorageProcessingMessage(); String msg = "Pass completed with no matching results seeking rows " + myPidSet.size() + "-" + mySkipCount + ". This indicates an inefficient query! Retrying with new max count of " + myMaxResultsToFetch; - ourLog.warn(msg); - message.setMessage(msg); - HookParams params = new HookParams() - .add(RequestDetails.class, myRequest) - .addIfMatchesType(ServletRequestDetails.class, myRequest) - .add(StorageProcessingMessage.class, message); - CompositeInterceptorBroadcaster.doCallHooks( - myInterceptorBroadcaster, myRequest, Pointcut.JPA_PERFTRACE_WARNING, params); + firePerformanceWarning(myRequest, msg); } private void initializeIteratorQuery(Integer theOffset, Integer theMaxResultsToFetch) { @@ -2208,6 +2374,18 @@ public class SearchBuilder implements ISearchBuilder { } } + private void firePerformanceWarning(RequestDetails theRequest, String theMessage) { + ourLog.warn(theMessage); + StorageProcessingMessage message = new StorageProcessingMessage(); + message.setMessage(theMessage); + HookParams params = new HookParams() + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest) + .add(StorageProcessingMessage.class, message); + CompositeInterceptorBroadcaster.doCallHooks( + myInterceptorBroadcaster, theRequest, Pointcut.JPA_PERFTRACE_WARNING, params); + } + public static int getMaximumPageSize() { if (myUseMaxPageSize50ForTest) { return MAXIMUM_PAGE_SIZE_FOR_TESTING; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/SearchBuilderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/SearchBuilderTest.java new file mode 100644 index 00000000000..f6564c8c53e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/builder/SearchBuilderTest.java @@ -0,0 +1,70 @@ +package ca.uhn.fhir.jpa.search.builder; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.api.dao.DaoRegistry; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.rest.server.util.FhirContextSearchParamRegistry; +import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class SearchBuilderTest { + + public static final FhirContext ourCtx = FhirContext.forR4Cached(); + @Spy + private FhirContext myFhirContext = ourCtx; + + @Spy + private ISearchParamRegistry mySearchParamRegistry = new FhirContextSearchParamRegistry(myFhirContext); + + @Spy + private PartitionSettings myPartitionSettings = new PartitionSettings(); + + @Mock(strictness = Mock.Strictness.LENIENT) + private DaoRegistry myDaoRegistry; + + @InjectMocks + private SearchBuilder mySearchBuilder; + + @BeforeEach + public void beforeEach() { + mySearchBuilder.setResourceName("QuestionnaireResponse"); + when(myDaoRegistry.getRegisteredDaoTypes()).thenReturn(ourCtx.getResourceTypes()); + } + + @Test + void testCalculateIndexUriIdentityHashesForResourceTypes_Include_Null() { + Set types = mySearchBuilder.calculateIndexUriIdentityHashesForResourceTypes(null, false); + // There are only 12 resource types that actually can be linked to by the QuestionnaireResponse + // resource via canonical references in any parameters + assertThat(types).hasSize(1); + } + + @Test + void testCalculateIndexUriIdentityHashesForResourceTypes_Include_Nonnull() { + Set inputTypes = Set.of("Questionnaire"); + Set types = mySearchBuilder.calculateIndexUriIdentityHashesForResourceTypes(inputTypes, false); + // Just the one that we actually specified + assertThat(types).hasSize(1); + } + + @Test + void testCalculateIndexUriIdentityHashesForResourceTypes_RevInclude_Null() { + Set types = mySearchBuilder.calculateIndexUriIdentityHashesForResourceTypes(null, true); + // Revincludes are really hard to figure out the potential resource types for, so we just need to + // use all active resource types + assertThat(types).hasSize(146); + } + +} diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java b/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java index d14ef81686a..a2d90780355 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java +++ b/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java @@ -26,7 +26,7 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.search.BaseSourceSearchParameterTestCases; import ca.uhn.fhir.jpa.search.CompositeSearchParameterTestCases; import ca.uhn.fhir.jpa.search.QuantitySearchParameterTestCases; -import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl; +import ca.uhn.fhir.jpa.search.builder.SearchBuilder; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; @@ -56,6 +56,7 @@ import ca.uhn.fhir.test.utilities.LogbackLevelOverrideExtension; import ca.uhn.fhir.test.utilities.docker.RequiresDocker; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.ValidationResult; +import ch.qos.logback.classic.Level; import jakarta.annotation.Nonnull; import jakarta.persistence.EntityManager; import org.apache.commons.lang3.RandomStringUtils; @@ -106,7 +107,6 @@ import org.springframework.test.context.support.DirtiesContextTestExecutionListe import org.springframework.transaction.PlatformTransactionManager; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import org.testcontainers.elasticsearch.ElasticsearchContainer; import java.io.IOException; import java.net.URLEncoder; @@ -207,13 +207,9 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl private IFhirResourceDao myQuestionnaireResponseDao; @Autowired private TestHSearchEventDispatcher myHSearchEventDispatcher; - @Autowired - ElasticsearchContainer myElasticsearchContainer; @Mock private IHSearchEventListener mySearchEventListener; - @Autowired - private ElasticsearchSvcImpl myElasticsearchSvc; @BeforeEach @@ -917,15 +913,14 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest impl */ @Test public void testDirectPathWholeResourceNotIndexedWorks() { + // setup + myLogbackLevelOverrideExtension.setLogLevel(SearchBuilder.class, Level.WARN); IIdType id1 = myTestDataBuilder.createObservation(List.of(myTestDataBuilder.withObservationCode("http://example.com/", "theCode"))); // set it after creating resource, so search doesn't find it in the index myStorageSettings.setStoreResourceInHSearchIndex(true); - myCaptureQueriesListener.clear(); - - List result = searchForFastResources("Observation?code=theCode"); - myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + List result = searchForFastResources("Observation?code=theCode&_count=10&_total=accurate"); assertThat(result).hasSize(1); assertEquals(((Observation) result.get(0)).getIdElement().getIdPart(), id1.getIdPart()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index fe8fab14308..7c2f5199ae8 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -3,6 +3,8 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.interceptor.api.Hook; +import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao; @@ -44,12 +46,16 @@ import ca.uhn.fhir.rest.gclient.NumberClientParam; import ca.uhn.fhir.rest.gclient.StringClientParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParamPrefixEnum; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.server.util.ICachedSearchDetails; import ca.uhn.fhir.util.ClasspathUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.TestUtil; @@ -77,7 +83,7 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; -import org.hamcrest.Matchers; +import org.assertj.core.api.AssertionsForInterfaceTypes; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -158,7 +164,6 @@ import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Nested; @@ -195,32 +200,12 @@ import java.util.stream.Stream; import static ca.uhn.fhir.jpa.util.TestUtil.sleepOneClick; import static ca.uhn.fhir.rest.param.BaseParamWithPrefix.MSG_PREFIX_INVALID_FORMAT; -import static ca.uhn.fhir.test.utilities.CustomMatchersUtil.assertDoesNotContainAnyOf; import static ca.uhn.fhir.util.TestUtil.sleepAtLeast; import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsInRelativeOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.endsWith; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.hasProperty; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.lessThan; -import static org.hamcrest.Matchers.matchesPattern; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.startsWith; -import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -292,7 +277,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { HttpGet get = new HttpGet(myServerBase + "/Procedure?focalAccess.a%20ne%20e"); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); - assertThat(output, containsString("Invalid parameter chain: focalAccess.a ne e")); + assertThat(output).contains("Invalid parameter chain: focalAccess.a ne e"); assertEquals(400, resp.getStatusLine().getStatusCode()); } @@ -346,7 +331,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { HttpGet get = new HttpGet(myServerBase + "/Procedure?a"); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); - assertThat(output, containsString("Unknown search parameter "a"")); + assertThat(output).contains("Unknown search parameter "a""); assertEquals(400, resp.getStatusLine().getStatusCode()); } } @@ -388,11 +373,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .filter(theRow -> theRow.getParamName().equals("_profile")) .collect(Collectors.toList()); - assertThat(matched, hasSize(2)); - assertThat(matched, hasItems( - hasProperty("uri", is(nullValue())), - hasProperty("uri", is("http://foo")) - )); + assertThat(matched).hasSize(2); + assertNull(matched.get(0).getUri()); + assertEquals("http://foo", matched.get(1).getUri()); }); // when @@ -404,10 +387,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .collect(Collectors.toList()); // then - assertThat(matched, hasSize(1)); - assertThat(matched, hasItem( - hasProperty("uri", is(nullValue())) - )); + assertThat(matched).hasSize(1); + assertNull(matched.get(0).getUri()); }); } @@ -449,7 +430,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); List ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(pt1id)); + assertThat(ids).containsExactlyInAnyOrder(pt1id); output = myClient .search() @@ -458,7 +439,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(pt1id)); + assertThat(ids).containsExactlyInAnyOrder(pt1id); } @@ -477,7 +458,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); List ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(pt1id)); + assertThat(ids).containsExactlyInAnyOrder(pt1id); Patient pt2 = new Patient(); pt2.addName().setFamily("Sm%ith"); @@ -490,7 +471,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(pt2id)); + assertThat(ids).containsExactlyInAnyOrder(pt2id); } @Test @@ -498,21 +479,21 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { HttpGet get = new HttpGet(myServerBase + "/Condition?onset-date=junk"); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); - assertThat(output, containsString(MSG_PREFIX_INVALID_FORMAT + ""junk"")); + assertThat(output).contains(MSG_PREFIX_INVALID_FORMAT + ""junk""); assertEquals(400, resp.getStatusLine().getStatusCode()); } get = new HttpGet(myServerBase + "/Condition?onset-date=ge"); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); - assertThat(output, containsString(MSG_PREFIX_INVALID_FORMAT + ""ge"")); + assertThat(output).contains(MSG_PREFIX_INVALID_FORMAT + ""ge""); assertEquals(400, resp.getStatusLine().getStatusCode()); } get = new HttpGet(myServerBase + "/Condition?onset-date=" + UrlUtil.escapeUrlParam(">")); try (CloseableHttpResponse resp = ourHttpClient.execute(get)) { String output = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); - assertThat(output, containsString(MSG_PREFIX_INVALID_FORMAT + "">"")); + assertThat(output).contains(MSG_PREFIX_INVALID_FORMAT + "">""); assertEquals(400, resp.getStatusLine().getStatusCode()); } } @@ -598,11 +579,11 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String linkNext = output.getLink("next").getUrl(); linkNext = linkNext.replaceAll("_getpagesoffset=[0-9]+", "_getpagesoffset=3300"); - assertThat(linkNext, containsString("_getpagesoffset=3300")); + assertThat(linkNext).contains("_getpagesoffset=3300"); Bundle nextPageBundle = myClient.loadPage().byUrl(linkNext).andReturnBundle(Bundle.class).execute(); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(nextPageBundle)); - assertEquals(null, nextPageBundle.getLink("next")); + assertNull(nextPageBundle.getLink("next")); } @Test @@ -629,7 +610,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { List ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); ourLog.info("Ids: {}", ids); - assertEquals(6, output.getEntry().size()); + assertThat(output.getEntry()).hasSize(6); assertNotNull(output.getLink("next")); // Page 2 @@ -640,7 +621,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); ourLog.info("Ids: {}", ids); - assertEquals(4, output.getEntry().size()); + assertThat(output.getEntry()).hasSize(4); assertNull(output.getLink("next")); } @@ -696,31 +677,31 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // Regular search param idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?organization=" + orgId.getValue()); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?organization.name=ORGANIZATION"); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?organization.partof.name=PARENT"); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?organization.partof.partof.name=GRANDPARENT"); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); // Search param on extension idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?extpatorg=" + orgId.getValue()); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?extpatorg.name=ORGANIZATION"); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); myCaptureQueriesListener.clear(); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?extpatorg.extorgorg.name=PARENT"); myCaptureQueriesListener.logSelectQueries(); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient?extpatorg.extorgorg.extorgorg.name=GRANDPARENT"); - assertThat(idValues, contains(pid)); + assertThat(idValues).containsExactly(pid); } @@ -749,7 +730,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .byUrl(url) .andReturnBundle(Bundle.class) .execute(); - assertEquals(0, output.getEntry().size()); + assertThat(output.getEntry()).isEmpty(); } @@ -799,8 +780,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } List locationHeader = captureInterceptor.getLastResponse().getHeaders(Constants.HEADER_LOCATION); - assertEquals(1, locationHeader.size()); - assertThat(locationHeader.get(0), containsString(id.getValue() + "/_history/2")); + assertThat(locationHeader).hasSize(1); + assertThat(locationHeader.get(0)).contains(id.getValue() + "/_history/2"); } @Test @@ -813,7 +794,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(responseContent); assertEquals(400, status.getStatusLine().getStatusCode()); - assertThat(responseContent, containsString("No body was supplied in request")); + assertThat(responseContent).contains("No body was supplied in request"); } } @@ -848,7 +829,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(responseContent); assertEquals(400, status.getStatusLine().getStatusCode()); - assertThat(responseContent, containsString("No body was supplied in request")); + assertThat(responseContent).contains("No body was supplied in request"); } } @@ -869,7 +850,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(responseContent); assertEquals(201, status.getStatusLine().getStatusCode()); - assertThat(responseContent, containsString("true")); + assertThat(responseContent).contains("true"); } // Delete @@ -891,7 +872,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(responseContent); assertEquals(201, status.getStatusLine().getStatusCode()); - assertThat(responseContent, containsString("true")); + assertThat(responseContent).contains("true"); } @@ -936,7 +917,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Basic basic = myClient.read().resource(Basic.class).withId(id).execute(); List exts = basic.getExtensionsByUrl("http://localhost:1080/hapi-fhir-jpaserver-example/baseDstu2/StructureDefinition/DateID"); - assertEquals(1, exts.size()); + assertThat(exts).hasSize(1); } private List searchAndReturnUnqualifiedIdValues(String theUri) throws IOException { @@ -969,7 +950,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { public void testBundleCreate() throws Exception { IGenericClient client = myClient; - String resBody = IOUtils.toString(ResourceProviderR4Test.class.getResource("/r4/document-father.json"), StandardCharsets.UTF_8); + String resBody = IOUtils.toString( ResourceProviderR4Test.class.getResource("/r4/document-father.json"), StandardCharsets.UTF_8); IIdType id = client.create().resource(resBody).execute().getId(); ourLog.info("Created: {}", id); @@ -989,7 +970,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { client.create().resource(resBody).execute(); fail(); } catch (UnprocessableEntityException e) { - assertThat(e.getMessage(), containsString("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint.")); + assertThat(e.getMessage()).contains("Unable to store a Bundle resource on this server with a Bundle.type value of: transaction. Note that if you are trying to perform a FHIR transaction or batch operation you should POST the Bundle resource to the Base URL of the server, not to the /Bundle endpoint."); } } @@ -1011,7 +992,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:off - assertThat(toUnqualifiedVersionlessIds(resp), contains(id)); + assertThat(toUnqualifiedVersionlessIds(resp)).containsExactly(id); //@formatter:off resp = myClient @@ -1023,7 +1004,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:off - assertThat(toUnqualifiedVersionlessIds(resp), contains(id)); + assertThat(toUnqualifiedVersionlessIds(resp)).containsExactly(id); //@formatter:off resp = myClient @@ -1035,7 +1016,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:off - assertThat(toUnqualifiedVersionlessIds(resp), empty()); + assertThat(toUnqualifiedVersionlessIds(resp)).isEmpty(); } @@ -1124,10 +1105,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .count(10) .returnBundle(Bundle.class) .execute(); - assertEquals(10, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(10); found = myClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpr4_testCountParam_01")).count(999).returnBundle(Bundle.class).execute(); - assertEquals(50, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(50); } @@ -1280,15 +1261,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String encodedOo = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); ourLog.info(encodedOo); - assertThat(encodedOo, containsString("cvc-complex-type.2.4.b")); - assertThat(encodedOo, containsString("Successfully created resource \\\"Observation/")); + assertThat(encodedOo).contains("cvc-complex-type.2.4.b"); + assertThat(encodedOo).contains("Successfully created resource \\\"Observation/"); interceptor.setAddValidationResultsToResponseOperationOutcome(false); outcome = myClient.create().resource(obs).execute().getOperationOutcome(); encodedOo = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome); ourLog.info(encodedOo); - assertThat(encodedOo, not(containsString("cvc-complex-type.2.4.b"))); - assertThat(encodedOo, containsString("Successfully created resource \\\"Observation/")); + assertThat(encodedOo).doesNotContain("cvc-complex-type.2.4.b"); + assertThat(encodedOo).contains("Successfully created resource \\\"Observation/"); } finally { myServer.getRestfulServer().unregisterInterceptor(interceptor); @@ -1329,7 +1310,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { OperationOutcome oo = (OperationOutcome) result.getOperationOutcome(); - assertThat(oo.getIssue(), hasSize(1)); + assertThat(oo.getIssue()).hasSize(1); OperationOutcome.OperationOutcomeIssueComponent firstIssue = oo.getIssue().get(0); assertEquals(OperationOutcome.IssueSeverity.INFORMATION, firstIssue.getSeverity()); assertEquals("No issues detected during validation", firstIssue.getDiagnostics()); @@ -1348,11 +1329,11 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.validate().resource(qa).execute(); } catch (PreconditionFailedException e) { oo = (OperationOutcome) e.getOperationOutcome(); - assertThat(oo.getIssue(), hasSize(2)); + assertThat(oo.getIssue()).hasSize(2); for (OperationOutcome.OperationOutcomeIssueComponent next : oo.getIssue()) { assertEquals(OperationOutcome.IssueSeverity.ERROR, next.getSeverity()); - assertThat(next.getDiagnostics(), containsString("code1")); + assertThat(next.getDiagnostics()).contains("code1"); } } } @@ -1381,7 +1362,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .where(Observation.ENCOUNTER.hasId(patientReference.getReference())) .returnBundle(Bundle.class) .execute(); - assertEquals(0, returnedBundle.getEntry().size()); + assertThat(returnedBundle.getEntry()).isEmpty(); // Search for right type returnedBundle = myClient.search() @@ -1409,7 +1390,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(procedurePost)) { assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Procedure/")); + assertThat(newIdString).startsWith(myServerBase + "/Procedure/"); id = new IdType(newIdString); } @@ -1449,7 +1430,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(parameterPost)) { assertEquals(400, response.getStatusLine().getStatusCode()); String responseString = EntityUtils.toString(response.getEntity(), "UTF-8"); - assertTrue(responseString.contains("Extension contains both a value and nested extensions")); + assertThat(responseString).contains("Extension contains both a value and nested extensions"); } // Get procedures @@ -1475,7 +1456,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); @@ -1510,7 +1491,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); @@ -1521,7 +1502,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { assertEquals(200, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id2 = new IdType(newIdString); } finally { response.close(); @@ -1551,8 +1532,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(response.toString()); ourLog.debug(respString); - assertThat(respString, startsWith("")); - assertThat(respString, endsWith("")); + assertThat(respString).startsWith(""); + assertThat(respString).endsWith(""); } finally { response.getEntity().getContent().close(); response.close(); @@ -1573,7 +1554,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(response.toString()); ourLog.debug(respString); - assertThat(respString, containsString("")); + assertThat(respString).contains(""); } finally { response.getEntity().getContent().close(); response.close(); @@ -1593,8 +1574,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(responseString); assertEquals(400, response.getStatusLine().getStatusCode()); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, responseString); - assertEquals(Msg.code(365) + "Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", - oo.getIssue().get(0).getDiagnostics()); + assertEquals(Msg.code(365) + "Can not create resource with ID \"2\", ID must not be supplied on a create (POST) operation (use an HTTP PUT / update operation if you wish to supply an ID)", oo.getIssue().get(0).getDiagnostics()); } finally { response.getEntity().getContent().close(); @@ -1644,8 +1624,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.debug(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(res)); - assertEquals(3, res.getEntry().size()); - assertEquals(1, genResourcesOfType(res, Encounter.class).size()); + assertThat(res.getEntry()).hasSize(3); + assertThat(genResourcesOfType(res, Encounter.class)).hasSize(1); assertEquals(e1id.toUnqualifiedVersionless(), genResourcesOfType(res, Encounter.class).get(0).getIdElement().toUnqualifiedVersionless()); } @@ -1670,8 +1650,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.debug(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(res)); - assertEquals(1, res.getEntry().size()); - assertEquals(1, genResourcesOfType(res, Encounter.class).size()); + assertThat(res.getEntry()).hasSize(1); + assertThat(genResourcesOfType(res, Encounter.class)).hasSize(1); assertEquals(e1id.toUnqualifiedVersionless(), genResourcesOfType(res, Encounter.class).get(0).getIdElement().toUnqualifiedVersionless()); // Right type @@ -1683,9 +1663,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.debug(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(res)); - assertEquals(2, res.getEntry().size()); - assertEquals(1, genResourcesOfType(res, Encounter.class).size()); - assertEquals(1, genResourcesOfType(res, Group.class).size()); + assertThat(res.getEntry()).hasSize(2); + assertThat(genResourcesOfType(res, Encounter.class)).hasSize(1); + assertThat(genResourcesOfType(res, Group.class)).hasSize(1); } @@ -1715,8 +1695,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { //@formatter:on fail(); } catch (PreconditionFailedException e) { - assertEquals("HTTP 412 Precondition Failed: " + Msg.code(962) + "Failed to DELETE resource with match URL \"Patient?identifier=testDeleteConditionalMultiple\" because this search matched 2 resources", - e.getMessage()); + assertEquals("HTTP 412 Precondition Failed: " + Msg.code(962) + "Failed to DELETE resource with match URL \"Patient?identifier=testDeleteConditionalMultiple\" because this search matched 2 resources", e.getMessage()); } // Not deleted yet.. @@ -1733,8 +1712,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String encoded = myFhirContext.newXmlParser().encodeResourceToString(response.getOperationOutcome()); ourLog.info(encoded); - assertThat(encoded, containsString( - "Successfully deleted 2 resource(s). Took ")); + assertThat(encoded).contains("Successfully deleted 2 resource(s). Took "); try { myClient.read().resource("Patient").withId(id1).execute(); fail(); @@ -1759,9 +1737,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String response = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(response); assertEquals(200, resp.getStatusLine().getStatusCode()); - assertThat(response, containsString( - "" - )); + assertThat(response).contains(""); } } @@ -1773,7 +1749,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(responseString); assertEquals(400, response.getStatusLine().getStatusCode()); - assertThat(responseString, containsString("Can not perform delete, no ID provided")); + assertThat(responseString).contains("Can not perform delete, no ID provided"); } } @@ -1825,7 +1801,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); @@ -1838,7 +1814,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s). Took")); + assertThat(oo.getIssueFirstRep().getDiagnostics()).startsWith("Successfully deleted 1 resource(s). Took"); } finally { response.close(); } @@ -1851,7 +1827,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Resource was deleted at")); + assertThat(oo.getIssueFirstRep().getDiagnostics()).startsWith("Resource was deleted at"); } finally { response.close(); } @@ -1865,7 +1841,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL")); + assertThat(oo.getIssueFirstRep().getDiagnostics()).startsWith("Unable to find resource matching URL"); } finally { response.close(); } @@ -1891,7 +1867,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); @@ -1946,7 +1922,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { MethodOutcome resp = myClient.delete().resourceById(resourceType, logicalID).execute(); OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome(); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Not deleted, resource " + resourceType + "/" + logicalID + " does not exist.")); + assertThat(oo.getIssueFirstRep().getDiagnostics()).startsWith("Not deleted, resource " + resourceType + "/" + logicalID + " does not exist."); } @Test @@ -1956,13 +1932,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { MethodOutcome resp = myClient.delete().resourceById(id).execute(); OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome(); - assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("Successfully deleted 1 resource(s).")); - assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("Took ")); + assertThat(oo.getIssueFirstRep().getDiagnostics()).contains("Successfully deleted 1 resource(s)."); + assertThat(oo.getIssueFirstRep().getDiagnostics()).contains("Took "); resp = myClient.delete().resourceById(id).execute(); oo = (OperationOutcome) resp.getOperationOutcome(); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Not deleted, resource ")); - assertThat(oo.getIssueFirstRep().getDiagnostics(), endsWith("was already deleted.")); + assertThat(oo.getIssueFirstRep().getDiagnostics()).startsWith("Not deleted, resource "); + assertThat(oo.getIssueFirstRep().getDiagnostics()).endsWith("was already deleted."); } /** @@ -2014,7 +1990,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(get)) { assertEquals(200, response.getStatusLine().getStatusCode()); String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - assertThat(output, not(containsString(" test = List.of("a", "b", "c"); - String testString = "testAString"; - - //examined iterable must be of the same length as the specified collection of matchers - assertThat(test, not(contains("b"))); //replace with not(hasItem()) - - //examined Iterable yield at least one item that is matched - //it can contain "a", but it doesn't contain "d" so this passes - //really does "do not have one of these" - assertThat(test, not(hasItems("a", "d"))); //replace with individual calls to not(hasItem()) - //MatchersUtil.assertDoesNotContainAnyOf(test, List.of("a", "d")); - - //examined iterable must be of the same length as the specified collection of matchers - assertThat(test, not(containsInAnyOrder("a", "b"))); //replace with indiv calls to not(hasItem()) - } - - /** * See #872 */ @@ -2110,7 +2067,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse resp = ourHttpClient.execute(post)) { String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); ourLog.debug(respString); - assertThat(respString, containsString("Unknown extension http://hl7.org/fhir/ValueSet/v3-ActInvoiceGroupCode")); + assertThat(respString).contains("Unknown extension http://hl7.org/fhir/ValueSet/v3-ActInvoiceGroupCode"); assertEquals(200, resp.getStatusLine().getStatusCode()); } } finally { @@ -2156,13 +2113,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8); ourLog.debug(respString); assertEquals(200, resp.getStatusLine().getStatusCode()); - assertThat(respString, containsString("Profile reference 'http://foo/structuredefinition/myprofile' has not been checked because it is unknown")); + assertThat(respString).contains("Profile reference 'http://foo/structuredefinition/myprofile' has not been checked because it is unknown"); } } @SuppressWarnings("unused") @Test public void testFullTextSearch() throws Exception { + IParser parser = myFhirContext.newJsonParser(); + Observation obs1 = new Observation(); obs1.getCode().setText("Systolic Blood Pressure"); obs1.setStatus(ObservationStatus.FINAL); @@ -2174,13 +2133,21 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { obs2.setStatus(ObservationStatus.FINAL); obs2.setValue(new Quantity(81)); IIdType id2 = myObservationDao.create(obs2, mySrd).getId().toUnqualifiedVersionless(); + obs2.setId(id2); + + myStorageSettings.setAdvancedHSearchIndexing(true); HttpGet get = new HttpGet(myServerBase + "/Observation?_content=systolic&_pretty=true"); + get.addHeader("Content-Type", "application/json"); try (CloseableHttpResponse response = ourHttpClient.execute(get)) { assertEquals(200, response.getStatusLine().getStatusCode()); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(responseString); - assertThat(responseString, containsString(id1.getIdPart())); + Bundle bundle = parser.parseResource(Bundle.class, responseString); + assertEquals(1, bundle.getTotal()); + Resource resource = bundle.getEntry().get(0).getResource(); + assertEquals("Observation", resource.fhirType()); + assertEquals(id1.getIdPart(), resource.getIdPart()); } } @@ -2203,10 +2170,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(o).execute(); List ids = searchAndReturnUnqualifiedVersionlessIdValues(myServerBase + "/Patient?_id=FOO&_content=family"); - assertThat(ids, contains("Patient/FOO")); + assertThat(ids).containsExactly("Patient/FOO"); ids = searchAndReturnUnqualifiedVersionlessIdValues(myServerBase + "/Patient?_id=FOO&_content=HELLO"); - assertThat(ids, empty()); + assertThat(ids).isEmpty(); } @Test @@ -2226,7 +2193,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); response.getEntity().getContent().close(); ourLog.info(output); - assertThat(output, containsString(" ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertThat(ids, contains(pid0.getValue())); + assertThat(ids).containsExactly(pid0.getValue()); } @Test @@ -2274,7 +2241,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(get)) { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); - assertThat(resp, containsString("Invalid _has parameter syntax: _has")); + assertThat(resp).contains("Invalid _has parameter syntax: _has"); } } @@ -2322,7 +2289,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myCaptureQueriesListener.clear(); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); myCaptureQueriesListener.logAllQueries(); - assertThat(ids, contains(obsId.getValue())); + assertThat(ids).containsExactly(obsId.getValue()); } @ParameterizedTest @@ -2354,7 +2321,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myCaptureQueriesListener.clear(); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); myCaptureQueriesListener.logAllQueries(); - assertThat(ids, contains(pid0.getValue())); + assertThat(ids).containsExactly(pid0.getValue()); } @Test @@ -2384,25 +2351,25 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { List idValues; idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + id.getIdPart() + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); - assertThat(idValues.toString(), idValues, contains(ids.get(3), ids.get(2), ids.get(1), ids.get(0))); + assertThat(idValues).as(idValues.toString()).containsExactly(ids.get(3), ids.get(2), ids.get(1), ids.get(0)); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); - assertThat(idValues.toString(), idValues, contains(ids.get(3), ids.get(2), ids.get(1))); + assertThat(idValues).as(idValues.toString()).containsExactly(ids.get(3), ids.get(2), ids.get(1)); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/_history?_at=gt" + toStr(preDates.get(0)) + "&_at=lt" + toStr(preDates.get(3))); - assertThat(idValues.toString(), idValues, contains(ids.get(3), ids.get(2), ids.get(1))); + assertThat(idValues).as(idValues.toString()).containsExactly(ids.get(3), ids.get(2), ids.get(1)); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/_history?_at=gt2060"); - assertThat(idValues.toString(), idValues, empty()); + assertThat(idValues).as(idValues.toString()).isEmpty(); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/_history?_at=" + InstantDt.withCurrentTime().getYear()); - assertThat(idValues.toString(), idValues, hasSize(10)); // 10 is the page size + assertThat(idValues).as(idValues.toString()).hasSize(10); // 10 is the page size idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/_history?_at=ge" + InstantDt.withCurrentTime().getYear()); - assertThat(idValues.toString(), idValues, hasSize(10)); + assertThat(idValues).as(idValues.toString()).hasSize(10); idValues = searchAndReturnUnqualifiedIdValues(myServerBase + "/_history?_at=gt" + InstantDt.withCurrentTime().getYear()); - assertThat(idValues, hasSize(0)); + assertThat(idValues).hasSize(0); } @@ -2419,14 +2386,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { IIdType id = myClient.create().resource(patient).execute().getId().toVersionless(); ourLog.info("Res ID: {}", id); - final String expectedFullUrl = myServerBase + "/Patient/" + id.getIdPart(); + final String expectedFullUrl = myServerBase + "/Patient/" + id.getIdPart(); if (theInvalidateCacheBeforeHistory) { // the reason for this test parameterization to invalidate the cache is that // when a resource is created/updated, its id mapping is cached for 1 minute so // retrieving the history right after creating the resource will use the cached value. // By invalidating the cache here and getting the history bundle again, - // we test the scenario where the id mapping needs to be read from the db, + // we test the scenario where the id mapping needs to be read from the db, // hence testing a different code path. myMemoryCacheService.invalidateCaches(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID); } @@ -2468,7 +2435,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info("Res ID: {}", id); assertEquals(patientForcedId, id.getIdPart()); - final String expectedFullUrl = myServerBase + "/Patient/" + id.getIdPart(); + final String expectedFullUrl = myServerBase + "/Patient/" + id.getIdPart(); if (theInvalidateCacheBeforeHistory) { // the reason for this test parameterization to invalidate the cache is that @@ -2513,16 +2480,16 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info("Res ID: {}", id); Bundle history = myClient.history().onInstance(id.getValue()).andReturnBundle(Bundle.class).prettyPrint().summaryMode(SummaryEnum.DATA).execute(); - assertEquals(3, history.getEntry().size()); + assertThat(history.getEntry()).hasSize(3); assertEquals(id.withVersion("3").getValue(), history.getEntry().get(0).getResource().getId()); - assertEquals(1, ((Patient) history.getEntry().get(0).getResource()).getName().size()); + assertThat(((Patient) history.getEntry().get(0).getResource()).getName()).hasSize(1); assertEquals(HTTPVerb.DELETE, history.getEntry().get(1).getRequest().getMethodElement().getValue()); assertEquals("Patient/" + id.getIdPart() + "/_history/2", history.getEntry().get(1).getRequest().getUrl()); - assertEquals(null, history.getEntry().get(1).getResource()); + assertNull(history.getEntry().get(1).getResource()); assertEquals(id.withVersion("1").getValue(), history.getEntry().get(2).getResource().getId()); - assertEquals(1, ((Patient) history.getEntry().get(2).getResource()).getName().size()); + assertThat(((Patient) history.getEntry().get(2).getResource()).getName()).hasSize(1); ourLog.debug(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(history)); @@ -2565,30 +2532,21 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .count(2) .execute(); - assertThat(toUnqualifiedIdValues(history).toString(), toUnqualifiedIdValues(history), contains( - "Patient/A/_history/6", - "Patient/A/_history/5" - )); + assertThat(toUnqualifiedIdValues(history)).as(toUnqualifiedIdValues(history).toString()).containsExactly("Patient/A/_history/6", "Patient/A/_history/5"); history = myClient .loadPage() .next(history) .execute(); - assertThat(toUnqualifiedIdValues(history).toString(), toUnqualifiedIdValues(history), contains( - "Patient/A/_history/4", - "Patient/A/_history/3" - )); + assertThat(toUnqualifiedIdValues(history)).as(toUnqualifiedIdValues(history).toString()).containsExactly("Patient/A/_history/4", "Patient/A/_history/3"); history = myClient .loadPage() .next(history) .execute(); - assertThat(toUnqualifiedIdValues(history).toString(), toUnqualifiedIdValues(history), contains( - "Patient/A/_history/2", - "Patient/A/_history/1" - )); + assertThat(toUnqualifiedIdValues(history)).as(toUnqualifiedIdValues(history).toString()).containsExactly("Patient/A/_history/2", "Patient/A/_history/1"); // we got them all assertNull(history.getLink("next")); @@ -2606,27 +2564,21 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .count(2) .execute(); - assertThat(toUnqualifiedIdValues(history).toString(), toUnqualifiedIdValues(history), contains( - "Patient/A/_history/6", - "Patient/A/_history/5" - )); + assertThat(toUnqualifiedIdValues(history)).as(toUnqualifiedIdValues(history).toString()).containsExactly("Patient/A/_history/6", "Patient/A/_history/5"); history = myClient .loadPage() .next(history) .execute(); - assertThat(toUnqualifiedIdValues(history).toString(), toUnqualifiedIdValues(history), contains( - "Patient/A/_history/4", - "Patient/A/_history/3" - )); + assertThat(toUnqualifiedIdValues(history)).as(toUnqualifiedIdValues(history).toString()).containsExactly("Patient/A/_history/4", "Patient/A/_history/3"); history = myClient .loadPage() .next(history) .execute(); - assertEquals(0, history.getEntry().size()); + assertThat(history.getEntry()).isEmpty(); } @@ -2650,14 +2602,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info("Response: {}", respString); assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); } assertEquals("1", id.getVersionIdPart()); - assertNotEquals("AAA", id.getIdPart()); + assertThat(id.getIdPart()).isNotEqualTo("AAA"); HttpGet get = new HttpGet(myServerBase + "/Patient/" + id.getIdPart()); response = ourHttpClient.execute(get); @@ -2665,8 +2617,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(200, response.getStatusLine().getStatusCode()); String respString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info("Response: {}", respString); - assertThat(respString, containsString("")); - assertThat(respString, containsString("")); + assertThat(respString).contains(""); + assertThat(respString).contains(""); } finally { response.close(); } @@ -2717,14 +2669,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info("Response: {}", respString); assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); } assertEquals("1", id.getVersionIdPart()); - assertNotEquals("AAA", id.getIdPart()); + assertThat(id.getIdPart()).isNotEqualTo("AAA"); HttpPut put = new HttpPut(myServerBase + "/Patient/" + id.getIdPart() + "/_history/1"); put.setEntity(new StringEntity(resource, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); @@ -2734,10 +2686,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info("Response: {}", respString); assertEquals(400, response.getStatusLine().getStatusCode()); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, respString); - assertEquals( - Msg.code(420) + "Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"AAA\" does not match URL ID of \"" - + id.getIdPart() + "\"", - oo.getIssue().get(0).getDiagnostics()); + assertThat(oo.getIssue().get(0).getDiagnostics()).isEqualTo(Msg.code(420) + "Can not update resource, resource body must contain an ID element which matches the request URL for update (PUT) operation - Resource body ID of \"AAA\" does not match URL ID of \"" + + id.getIdPart() + "\""); } finally { response.close(); } @@ -2789,8 +2739,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .byUrl("Task?_count=10&_tag=test&status=requested&_include=Task%3Aowner&_sort=status") .returnBundle(Bundle.class) .execute(); - assertFalse(bundle.getEntry().isEmpty()); - assertEquals(11, bundle.getEntry().size()); + assertThat(bundle.getEntry()).isNotEmpty(); + assertThat(bundle.getEntry()).hasSize(11); for (BundleEntryComponent resource : bundle.getEntry()) { ids.add(resource.getResource().getId()); } @@ -2802,7 +2752,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { nextUrl = nextLink.getUrl(); // make sure we're always requesting 10 - assertTrue(nextUrl.contains(String.format("_count=%d", requestedAmount))); + assertThat(nextUrl).contains(String.format("_count=%d", requestedAmount)); // get next batch bundle = myClient.fetchResourceFromUrl(Bundle.class, nextUrl); @@ -2824,7 +2774,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // verify // we should receive all resources and the single organization (repeatedly) - assertEquals(total + 1, ids.size()); + assertThat(ids).hasSize(total + 1); } @@ -2882,7 +2832,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); int count = bundle.getEntry().size(); - assertFalse(bundle.getEntry().isEmpty()); + assertThat(bundle.getEntry()).isNotEmpty(); String nextUrl = null; do { @@ -2891,14 +2841,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { nextUrl = nextLink.getUrl(); // make sure we're always requesting 10 - assertTrue(nextUrl.contains(String.format("_count=%d", requestedAmount))); + assertThat(nextUrl).contains(String.format("_count=%d", requestedAmount)); // get next batch bundle = myClient.fetchResourceFromUrl(Bundle.class, nextUrl); int received = bundle.getEntry().size(); // every next result should produce results - assertFalse(bundle.getEntry().isEmpty()); + assertThat(bundle.getEntry()).isNotEmpty(); count += received; } else { nextUrl = null; @@ -2946,7 +2896,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); int count = bundle.getEntry().size(); - assertFalse(bundle.getEntry().isEmpty()); + assertThat(bundle.getEntry()).isNotEmpty(); String nextUrl = null; do { @@ -2955,14 +2905,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { nextUrl = nextLink.getUrl(); // make sure we're always requesting 10 - assertTrue(nextUrl.contains(String.format("_count=%d", requestedAmount))); + assertThat(nextUrl).contains(String.format("_count=%d", requestedAmount)); // get next batch bundle = myClient.fetchResourceFromUrl(Bundle.class, nextUrl); int received = bundle.getEntry().size(); // every next result should produce results - assertFalse(bundle.getEntry().isEmpty()); + assertThat(bundle.getEntry()).isNotEmpty(); count += received; } else { nextUrl = null; @@ -3001,7 +2951,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.debug(myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle)); - assertEquals(3, bundle.getEntry().size()); + assertThat(bundle.getEntry()).hasSize(3); assertEquals("Patient", bundle.getEntry().get(0).getResource().getIdElement().getResourceType()); assertEquals("Patient", bundle.getEntry().get(1).getResource().getIdElement().getResourceType()); assertEquals("Organization", bundle.getEntry().get(2).getResource().getIdElement().getResourceType()); @@ -3017,7 +2967,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.create().resource(p).execute(); Bundle b = myClient.search().forResource("Patient").include(Patient.INCLUDE_ORGANIZATION).returnBundle(Bundle.class).execute(); - assertEquals(1, b.getEntry().size()); + assertThat(b.getEntry()).hasSize(1); } @Test @@ -3045,7 +2995,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(output); assertEquals(400, response.getStatusLine().getStatusCode()); - assertThat(output, containsString("Input contains no parameter with name 'meta'")); + assertThat(output).contains("Input contains no parameter with name 'meta'"); } finally { response.close(); } @@ -3057,7 +3007,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(output); assertEquals(400, response.getStatusLine().getStatusCode()); - assertThat(output, containsString("Input contains no parameter with name 'meta'")); + assertThat(output).contains("Input contains no parameter with name 'meta'"); } finally { response.close(); } @@ -3073,17 +3023,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { IIdType id = myClient.create().resource(pt).execute().getId().toUnqualifiedVersionless(); Meta meta = myClient.meta().get(Meta.class).fromResource(id).execute(); - assertEquals(0, meta.getTag().size()); + assertThat(meta.getTag()).isEmpty(); Meta inMeta = new Meta(); inMeta.addTag().setSystem("urn:system1").setCode("urn:code1"); meta = myClient.meta().add().onResource(id).meta(inMeta).execute(); - assertEquals(1, meta.getTag().size()); + assertThat(meta.getTag()).hasSize(1); inMeta = new Meta(); inMeta.addTag().setSystem("urn:system1").setCode("urn:code1"); meta = myClient.meta().delete().onResource(id).meta(inMeta).execute(); - assertEquals(0, meta.getTag().size()); + assertThat(meta.getTag()).isEmpty(); } @@ -3094,7 +3044,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); assertEquals(200, response.getStatusLine().getStatusCode()); - assertThat(resp, stringContainsInOrder("THIS IS THE DESC")); + assertThat(resp).contains("THIS IS THE DESC"); } } @@ -3144,7 +3094,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { assertEquals(201, response.getStatusLine().getStatusCode()); String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(myServerBase + "/Patient/")); + assertThat(newIdString).startsWith(myServerBase + "/Patient/"); id = new IdType(newIdString); } finally { response.close(); @@ -3156,7 +3106,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); assertEquals(200, response.getStatusLine().getStatusCode()); - assertThat(resp, containsString("Underweight")); + assertThat(resp).contains("Underweight"); } finally { response.getEntity().getContent().close(); response.close(); @@ -3183,8 +3133,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(patch)) { assertEquals(200, response.getStatusLine().getStatusCode()); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - assertThat(responseString, containsString("")); + assertThat(responseString).contains(""); } Patient newPt = myClient.read().resource(Patient.class).withId(pid1.getIdPart()).execute(); @@ -3241,8 +3191,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(200, response.getStatusLine().getStatusCode()); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info("Response: {}", responseString); - assertThat(responseString, containsString("IdentifiertestSearchByResourceChain01")); + assertThat(actual.getText().getDiv().getValueAsString()).contains("IdentifiertestSearchByResourceChain01"); } @Test @@ -3523,10 +3473,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(text); assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode()); - assertThat(text, containsString("\"A\"")); - assertThat(text, containsString("\"A1\"")); - assertThat(text, not(containsString("\"B\""))); - assertThat(text, not(containsString("\"B1\""))); + assertThat(text).contains("\"A\""); + assertThat(text).contains("\"A1\""); + assertThat(text).doesNotContain("\"B\""); + assertThat(text).doesNotContain("\"B1\""); } @@ -3535,7 +3485,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); // ourLog.info(text); // assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode()); -// assertThat(text, not(containsString("\"text\",\"type\""))); +// assertThat(text).doesNotContain("\"text\",\"type\""); // } } @@ -3595,8 +3545,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(text); assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode()); - assertThat(text, containsString("\"OBS1\"")); - assertThat(text, not(containsString("\"OBS2\""))); + assertThat(text).contains("\"OBS1\""); + assertThat(text).doesNotContain("\"OBS2\""); } } @@ -3634,7 +3584,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } private void assertOneResult(Bundle theResponse) { - assertThat(theResponse.getEntry().size(), is(equalTo(1))); + assertThat(theResponse.getEntry()).hasSize(1); } private void printResourceToConsole(IBaseResource theResource) { @@ -3648,7 +3598,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(text); assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode()); - assertThat(text, not(containsString("\"text\",\"type\""))); + assertThat(text).doesNotContain("\"text\",\"type\""); } } @@ -3707,7 +3657,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + assertThat(toUnqualifiedVersionlessIds(found)).containsExactlyInAnyOrder(id1, id2); found = myClient .search() @@ -3716,7 +3666,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + assertThat(toUnqualifiedVersionlessIds(found)).containsExactlyInAnyOrder(id1, id2); found = myClient .search() @@ -3725,7 +3675,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + assertThat(toUnqualifiedVersionlessIds(found)).containsExactlyInAnyOrder(id1); found = myClient .search() @@ -3735,7 +3685,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + assertThat(toUnqualifiedVersionlessIds(found)).containsExactlyInAnyOrder(id1); found = myClient .search() @@ -3745,7 +3695,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + assertThat(toUnqualifiedVersionlessIds(found)).containsExactlyInAnyOrder(id1); found = myClient .search() @@ -3754,7 +3704,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + assertThat(toUnqualifiedVersionlessIds(found)).containsExactlyInAnyOrder(id1, id2); found = myClient .search() @@ -3763,7 +3713,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertThat(toUnqualifiedVersionlessIds(found), empty()); + assertThat(toUnqualifiedVersionlessIds(found)).isEmpty(); } @@ -3858,7 +3808,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:on - assertEquals(1, actual.getEntry().size()); + assertThat(actual.getEntry()).hasSize(1); assertEquals(myServerBase + "/Patient/" + p1Id.getIdPart(), actual.getEntry().get(0).getFullUrl()); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); assertEquals(SearchEntryMode.MATCH, actual.getEntry().get(0).getSearch().getModeElement().getValue()); @@ -3881,7 +3831,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); //@formatter:on - assertEquals(1, actual.getEntry().size()); + assertThat(actual.getEntry()).hasSize(1); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); } @@ -3913,7 +3863,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(output); List ids = toUnqualifiedVersionlessIds(myFhirContext.newXmlParser().parseResource(Bundle.class, output)); ourLog.info(ids.toString()); - assertThat(ids, containsInAnyOrder(pid1)); + assertThat(ids).containsExactlyInAnyOrder(pid1); } finally { response.close(); } @@ -3929,7 +3879,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(output); List ids = toUnqualifiedVersionlessIds(myFhirContext.newXmlParser().parseResource(Bundle.class, output)); ourLog.info(ids.toString()); - assertThat(ids, containsInAnyOrder(pid2)); + assertThat(ids).containsExactlyInAnyOrder(pid2); } finally { response.close(); } @@ -3968,7 +3918,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(output); List ids = toUnqualifiedVersionlessIdValues(myFhirContext.newXmlParser().parseResource(Bundle.class, output)); ourLog.info(ids.toString()); - assertThat(ids, containsInAnyOrder("Practitioner/PRAC", "Encounter/E2")); + assertThat(ids).containsExactlyInAnyOrder("Practitioner/PRAC", "Encounter/E2"); } finally { response.close(); } @@ -3982,7 +3932,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(output); List ids = toUnqualifiedVersionlessIdValues(myFhirContext.newXmlParser().parseResource(Bundle.class, output)); ourLog.info(ids.toString()); - assertThat(ids, empty()); + assertThat(ids).isEmpty(); } finally { response.close(); } @@ -4023,7 +3973,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { for (BundleEntryComponent ele : actual.getEntry()) { actualIds.add(ele.getResource().getIdElement().getIdPart()); } - assertEquals(expectedIds, actualIds, "Expects to retrieve the 2 patients which reference the two different organizations"); + assertThat(actualIds).as("Expects to retrieve the 2 patients which reference the two different organizations").isEqualTo(expectedIds); } @Test @@ -4045,7 +3995,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .where(Patient.ORGANIZATION.hasId(o1id.getIdPart())) .encodedJson().prettyPrint().returnBundle(Bundle.class).execute(); //@formatter:on - assertEquals(1, actual.getEntry().size()); + assertThat(actual.getEntry()).hasSize(1); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); //@formatter:off @@ -4054,7 +4004,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .where(Patient.ORGANIZATION.hasId(o1id.getValue())) .encodedJson().prettyPrint().returnBundle(Bundle.class).execute(); //@formatter:on - assertEquals(1, actual.getEntry().size()); + assertThat(actual.getEntry()).hasSize(1); assertEquals(p1Id.getIdPart(), actual.getEntry().get(0).getResource().getIdElement().getIdPart()); } @@ -4073,7 +4023,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try { String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.debug(respString); - assertThat(respString, containsString("Invalid parameter chain: subject.id")); + assertThat(respString).contains("Invalid parameter chain: subject.id"); assertEquals(400, resp.getStatusLine().getStatusCode()); } finally { resp.getEntity().getContent().close(); @@ -4125,7 +4075,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:on List patients = toUnqualifiedVersionlessIds(found); - assertThat(patients, hasItems(id1a, id1b, id2)); + assertThat(patients).contains(id1a, id1b, id2); } { //@formatter:off @@ -4137,7 +4087,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:on List patients = toUnqualifiedVersionlessIds(found); - assertThat(patients, hasItems(id1a, id1b, id2)); + assertThat(patients).contains(id1a, id1b, id2); } { //@formatter:off @@ -4149,8 +4099,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:on List patients = toUnqualifiedVersionlessIds(found); - assertThat(patients, hasItems(id2)); - assertDoesNotContainAnyOf(patients, List.of(id1a, id1b)); + assertThat(patients).contains(id2); + AssertionsForInterfaceTypes.assertThat(patients).doesNotContainAnyElementsOf(List.of(id1a, id1b)); } { //@formatter:off @@ -4162,8 +4112,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:on List patients = toUnqualifiedVersionlessIds(found); - assertThat(patients.toString(), patients, not(hasItem(id2))); - assertThat(patients.toString(), patients, (hasItems(id1a, id1b))); + assertThat(patients).doesNotContain(id2); + assertThat(patients).contains(id1a, id1b); } { //@formatter:off @@ -4175,8 +4125,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); //@formatter:on List patients = toUnqualifiedVersionlessIds(found); - assertThat(patients, (hasItems(id1a, id1b))); - assertThat(patients, not(hasItem(id2))); + assertThat(patients).doesNotContain(id2); + assertThat(patients).contains(id1a, id1b); } } @@ -4198,7 +4148,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(200, response.getStatusLine().getStatusCode()); String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(responseString); - assertThat(responseString, containsString(moId.getIdPart())); + assertThat(responseString).contains(moId.getIdPart()); } } @@ -4219,8 +4169,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); List ids = toUnqualifiedVersionlessIdValues(bundle); - assertThat(ids, contains(oid1)); - assertThat(ids, not(hasItem(oid2))); + assertThat(ids).containsExactly(oid1); + assertThat(ids).doesNotContain(oid2); } } @@ -4248,7 +4198,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { for (int i = 0; i < 100; i++) { Bundle bundle = myClient.search().forResource(Patient.class).where(Patient.NAME.matches().value("testSearchPagingKeepsOldSearches")).count(5).returnBundle(Bundle.class).execute(); assertTrue(isNotBlank(bundle.getLink("next").getUrl())); - assertEquals(5, bundle.getEntry().size()); + assertThat(bundle.getEntry()).hasSize(5); linkNext.add(bundle.getLink("next").getUrl()); } @@ -4256,7 +4206,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { for (String nextLink : linkNext) { ourLog.info("Fetching index {}", index++); Bundle b = myClient.fetchResourceFromUrl(Bundle.class, nextLink); - assertEquals(5, b.getEntry().size()); + assertThat(b.getEntry()).hasSize(5); } } @@ -4270,7 +4220,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, resp); matches = bundle.getEntry().size(); - assertThat(matches, greaterThan(0)); + assertThat(matches).isGreaterThan(0); } @Test @@ -4297,7 +4247,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(value.getTime() + ""); ourLog.info(before.getTime() + ""); assertTrue(value.after(before)); - assertTrue(value.before(after), new InstantDt(value) + " should be before " + new InstantDt(after)); + assertThat(value.before(after)).as(new InstantDt(value) + " should be before " + new InstantDt(after)).isTrue(); } @Test @@ -4367,19 +4317,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt1|http://unitsofmeasure.org|m"); ourLog.info("uri = " + uri); List ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(3, ids.size()); + assertThat(ids).hasSize(3); //>= 100cm uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt100|http://unitsofmeasure.org|cm"); ourLog.info("uri = " + uri); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(3, ids.size()); + assertThat(ids).hasSize(3); //>= 10dm uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt10|http://unitsofmeasure.org|dm"); ourLog.info("uri = " + uri); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(3, ids.size()); + assertThat(ids).hasSize(3); } @Test @@ -4444,12 +4394,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // With non-normalized uri = myServerBase + "/Observation?value-quantity=" + UrlUtil.escapeUrlParam("100|http://unitsofmeasure.org|cm,100|http://foo|cm"); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(1, ids.size()); + assertThat(ids).hasSize(1); // With normalized uri = myServerBase + "/Observation?value-quantity=" + UrlUtil.escapeUrlParam("1|http://unitsofmeasure.org|m,100|http://foo|cm"); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(2, ids.size()); + assertThat(ids).hasSize(2); } @Test @@ -4507,12 +4457,12 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(1, returnedBundle.getEntry().size()); + assertThat(returnedBundle.getEntry()).hasSize(1); //-- check use normalized quantity table to search String searchSql = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, true); - assertThat(searchSql, not(containsString("HFJ_SPIDX_QUANTITY t0"))); - assertThat(searchSql, (containsString("HFJ_SPIDX_QUANTITY_NRML"))); + assertThat(searchSql).doesNotContain("HFJ_SPIDX_QUANTITY t0"); + assertThat(searchSql).contains("HFJ_SPIDX_QUANTITY_NRML"); } @Test @@ -4572,7 +4522,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); final String uuid2 = toSearchUuidFromLinkNext(result2); - assertNotEquals(uuid1, uuid2); + assertThat(uuid2).isNotEqualTo(uuid1); } { @@ -4643,8 +4593,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String uuid3 = toSearchUuidFromLinkNext(result3); - assertNotEquals(uuid1, uuid2); - assertNotEquals(uuid1, uuid3); + assertThat(uuid2).isNotEqualTo(uuid1); + assertThat(uuid3).isNotEqualTo(uuid1); } @Test @@ -4698,7 +4648,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String uuid3 = toSearchUuidFromLinkNext(result3); assertEquals(uuid1, uuid2); - assertNotEquals(uuid1, uuid3); + assertThat(uuid3).isNotEqualTo(uuid1); } @Test @@ -4772,7 +4722,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); patient = (Patient) response.getEntry().get(0).getResource(); - assertEquals(1, patient.getMeta().getTag().size()); + assertThat(patient.getMeta().getTag()).hasSize(1); } @Test @@ -4801,7 +4751,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(2, response.getEntry().size()); + assertThat(response.getEntry()).hasSize(2); } @Test @@ -4830,17 +4780,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info("** Done searching in {}ms with count of 1", sw.getMillis()); ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString()); - assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); - assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()), Matchers.empty()); + assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE)).isEmpty(); + assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase())).isEmpty(); // When we've only got one DB connection available, we are forced to wait for the // search to finish before returning if (TestR4Config.getMaxThreads() > 1) { assertNull(found.getTotalElement().getValue()); - assertEquals(1, found.getEntry().size()); - assertThat(sw.getMillis(), lessThan(1000L)); + assertThat(found.getEntry()).hasSize(1); + assertThat(sw.getMillis()).isLessThan(1000L); } else { - assertThat(sw.getMillis(), greaterThan(1000L)); + assertThat(sw.getMillis()).isGreaterThan(1000L); } } @@ -4866,10 +4816,10 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .count(1) .execute(); - assertThat(sw.getMillis(), not(lessThan(1000L))); + assertThat(sw.getMillis()).isGreaterThanOrEqualTo(1000L); assertEquals(10, found.getTotalElement().getValue().intValue()); - assertEquals(1, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(1); } @@ -4895,17 +4845,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .count(1) .execute(); - assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); - assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()), Matchers.empty()); + assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE)).isEmpty(); + assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase())).isEmpty(); // WHen we've only got one DB connection available, we are forced to wait for the // search to finish before returning if (TestR4Config.getMaxThreads() > 1) { assertNull(found.getTotalElement().getValue()); - assertEquals(1, found.getEntry().size()); - assertThat(sw.getMillis(), lessThan(1500L)); + assertThat(found.getEntry()).hasSize(1); + assertThat(sw.getMillis()).isLessThan(1500L); } else { - assertThat(sw.getMillis(), greaterThan(1500L)); + assertThat(sw.getMillis()).isGreaterThan(1500L); } } @@ -4932,7 +4882,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(200, resp.getStatusLine().getStatusCode()); String respString = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8); Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, respString); - assertEquals(1, bundle.getEntry().size()); + assertThat(bundle.getEntry()).hasSize(1); } } @@ -4956,7 +4906,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(2, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(2); assertEquals(Patient.class, found.getEntry().get(0).getResource().getClass()); assertEquals(SearchEntryMode.MATCH, found.getEntry().get(0).getSearch().getMode()); assertEquals(Organization.class, found.getEntry().get(1).getResource().getClass()); @@ -4985,7 +4935,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(2, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(2); assertEquals(Patient.class, found.getEntry().get(0).getResource().getClass()); assertEquals(SearchEntryMode.MATCH, found.getEntry().get(0).getSearch().getMode()); assertEquals(Organization.class, found.getEntry().get(1).getResource().getClass()); @@ -5004,7 +4954,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Unable to handle number prefix \"eb\" for value: eb100")); + assertThat(e.getMessage()).contains("Unable to handle number prefix \"eb\" for value: eb100"); } try { @@ -5017,7 +4967,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Unable to handle number prefix \"sa\" for value: sa100")); + assertThat(e.getMessage()).contains("Unable to handle number prefix \"sa\" for value: sa100"); } } @@ -5038,7 +4988,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { //@formatter:on fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Unable to handle quantity prefix \"eb\" for value: eb100||")); + assertThat(e.getMessage()).contains("Unable to handle quantity prefix \"eb\" for value: eb100||"); } } @@ -5116,7 +5066,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.debug("Bundle: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(found)); List list = toUnqualifiedVersionlessIds(found); - assertEquals(4, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(4); assertEquals(oid3, list.get(0)); assertEquals(oid1, list.get(1)); assertEquals(oid4, list.get(2)); @@ -5212,7 +5162,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.debug("Bundle: \n" + myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(found)); List list = toUnqualifiedVersionlessIds(found); - assertEquals(4, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(4); assertEquals(oid3, list.get(0)); assertEquals(oid1, list.get(1)); assertEquals(oid4, list.get(2)); @@ -5267,9 +5217,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { List list = toUnqualifiedVersionlessIds(found); ourLog.info(methodName + ": " + list.toString()); ourLog.info("Wanted " + orgNotMissing + " and not " + deletedIdMissingFalse + " but got " + list.size() + ": " + list); - assertThat("Wanted " + orgNotMissing + " but got " + list.size() + ": " + list, list, containsInRelativeOrder(orgNotMissing)); - assertThat(list, not(containsInRelativeOrder(deletedIdMissingFalse))); - assertThat(list, not(containsInRelativeOrder(orgMissing))); + assertThat(list).contains(orgNotMissing); + assertThat(list).doesNotContain(deletedIdMissingFalse); + assertThat(list).doesNotContain(orgMissing); } //@formatter:off @@ -5285,9 +5235,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { List list = toUnqualifiedVersionlessIds(found); ourLog.info(methodName + " found: " + list.toString() + " - Wanted " + orgMissing + " but not " + orgNotMissing); - assertThat(list, not(containsInRelativeOrder(orgNotMissing))); - assertThat(list, not(containsInRelativeOrder(deletedIdMissingTrue))); - assertThat("Wanted " + orgMissing + " but found: " + list, list, containsInRelativeOrder(orgMissing)); + assertThat(list).doesNotContain(orgNotMissing); + assertThat(list).doesNotContain(deletedIdMissingTrue); + assertThat(list).contains(orgMissing); } @Test @@ -5319,8 +5269,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Bundle bundle = myFhirContext.newXmlParser().parseResource(Bundle.class, IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8)); List ids = toUnqualifiedVersionlessIdValues(bundle); - assertThat(ids, contains(id1.getValue())); - assertThat(ids, not(hasItem(id2.getValue()))); + assertThat(ids).containsExactly(id1.getValue()); + assertThat(ids).doesNotContain(id2.getValue()); } } @@ -5387,6 +5337,40 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } + @Test + public void testSearchWithParameterAddedInInterceptor() { + Object interceptor = new Object() { + @Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED) + public void storagePreSearchRegistered( + ICachedSearchDetails theCachedSearchDetails, + RequestDetails theRequestDetails, + ServletRequestDetails theServletRequestDetails, + SearchParameterMap theSearchParameterMap) { + theSearchParameterMap.add("_security", new TokenParam("http://system", "security1").setModifier(TokenParamModifier.NOT)); + } + }; + myInterceptorRegistry.registerInterceptor(interceptor); + + try { + final Patient patient1 = new Patient().setActive(true); + patient1.getMeta().addSecurity("http://system", "security1", "Tag 1"); + MethodOutcome outcome1 = myPatientDao.create(patient1, mySrd); + assertTrue(outcome1.getCreated()); + + final Patient patient2 = new Patient().setActive(true); + patient2.getMeta().addSecurity("http://system", "security2", "Tag 2"); + MethodOutcome outcome2 = myPatientDao.create(patient2, mySrd); + assertTrue(outcome2.getCreated()); + String idForPatient2 = outcome2.getId().toUnqualifiedVersionless().getValue(); + + IBaseBundle bundle = myClient.search().forResource("Patient").execute(); + List ids = toUnqualifiedVersionlessIdValues(bundle); + assertThat(ids).containsExactly(idForPatient2); + } finally { + myInterceptorRegistry.unregisterInterceptor(interceptor); + } + } + @Test public void testSelfReferentialInclude() { Location loc1 = new Location(); @@ -5415,7 +5399,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .where(Location.NAME.matches().value("loc1")) .include(Location.INCLUDE_PARTOF.asRecursive()) .execute(); - assertThat(toUnqualifiedVersionlessIdValues(result), contains(loc1id.getValue(), loc2id.getValue())); + assertThat(toUnqualifiedVersionlessIdValues(result)).containsExactly(loc1id.getValue(), loc2id.getValue()); } @Test @@ -5446,7 +5430,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .where(Location.NAME.matches().value("loc1")) .revInclude(Location.INCLUDE_PARTOF.asRecursive()) .execute(); - assertThat(toUnqualifiedVersionlessIdValues(result), contains(loc1id.getValue(), loc2id.getValue())); + assertThat(toUnqualifiedVersionlessIdValues(result)).containsExactly(loc1id.getValue(), loc2id.getValue()); } @Test @@ -5473,7 +5457,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(2, b.getEntry().size()); + assertThat(b.getEntry()).hasSize(2); } @@ -5583,23 +5567,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(StringUtils.join(names, '\n')); - assertThat(names, contains( // this matches in order only - "Daniel Adams", - "Aaron Alexis", - "Carol Allen", - "Ruth Black", - "Brian Brooks", - "Amy Clark", - "Susan Clark", - "Anthony Coleman", - "Lisa Coleman", - "Steven Coleman", - "Ruth Cook", - "Betty Davis", - "Joshua Diaz", - "Brian Gracia", - "Sarah Graham", - "Stephan Graham")); + assertThat(names).containsExactly("Daniel Adams", "Aaron Alexis", "Carol Allen", "Ruth Black", "Brian Brooks", "Amy Clark", "Susan Clark", "Anthony Coleman", "Lisa Coleman", "Steven Coleman", "Ruth Cook", "Betty Davis", "Joshua Diaz", "Brian Gracia", "Sarah Graham", "Stephan Graham"); } @@ -5618,14 +5586,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Organization returned = myOrganizationDao.read(orgId, mySrd); String val = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned); ourLog.info(val); - assertThat(val, containsString("")); + assertThat(val).contains(""); } // Read back through the HTTP API { Organization returned = myClient.read().resource(Organization.class).withId(orgId.getIdPart()).execute(); String val = myFhirContext.newXmlParser().setPrettyPrint(true).encodeResourceToString(returned); ourLog.info(val); - assertThat(val, containsString("")); + assertThat(val).contains(""); } } @@ -5680,7 +5648,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.create().resource(p1).execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Organization/99999999999")); + assertThat(e.getMessage()).contains("Organization/99999999999"); } } @@ -5700,8 +5668,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(responseString); assertEquals(400, response.getStatusLine().getStatusCode()); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, responseString); - assertThat(oo.getIssue().get(0).getDiagnostics(), - containsString("Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])")); + assertThat(oo.getIssue().get(0).getDiagnostics()).contains("Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])"); } } @@ -5719,8 +5686,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { try (CloseableHttpResponse response = ourHttpClient.execute(post)) { String responseString = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(responseString); - assertThat(responseString, containsString("Can not update resource, request URL must contain an ID element for update (PUT) operation (it must be of the form [base]/[resource type]/[id])")); - assertThat(responseString, containsString("No issues detected during validation")); - assertThat(resp, - stringContainsInOrder("", "", "", "", - "")); + assertThat(resp).doesNotContain("Resource has no id"); + assertThat(resp).contains("No issues detected during validation"); + assertThat(resp).contains( + "", + "", "", + "", + ""); } finally { response.getEntity().getContent().close(); response.close(); @@ -6390,19 +6355,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); assertEquals(200, response.getStatusLine().getStatusCode()); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); - assertThat(resp, containsString("")); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); + assertThat(resp).contains(""); } finally { response.getEntity().getContent().close(); response.close(); @@ -6419,9 +6384,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(resp); assertEquals(200, response.getStatusLine().getStatusCode()); //@formatter:off - assertThat(resp, stringContainsInOrder( + assertThat(resp).contains( "", - "")); + ""); //@formatter:on } finally { response.getEntity().getContent().close(); @@ -6518,20 +6483,20 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt1|http://unitsofmeasure.org|m"); ourLog.info("uri = " + uri); List ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(2, ids.size()); + assertThat(ids).hasSize(2); //>= 100cm uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt100|http://unitsofmeasure.org|cm"); ourLog.info("uri = " + uri); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(2, ids.size()); + assertThat(ids).hasSize(2); //>= 10dm uri = myServerBase + "/Observation?code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$gt10|http://unitsofmeasure.org|dm"); ourLog.info("uri = " + uri); ids = searchAndReturnUnqualifiedVersionlessIdValues(uri); - assertEquals(2, ids.size()); + assertThat(ids).hasSize(2); } @Test @@ -6592,14 +6557,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(p).historyRewrite().withId(id).execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("ID must contain a history version")); + assertThat(e.getMessage()).contains("ID must contain a history version"); } try { myClient.update().resource(p).historyRewrite().withId("1234").execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("ID must contain a history version")); + assertThat(e.getMessage()).contains("ID must contain a history version"); } p.setId(id); @@ -6607,7 +6572,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(p).historyRewrite().execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("ID must contain a history version")); + assertThat(e.getMessage()).contains("ID must contain a history version"); } } @@ -6628,21 +6593,21 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(p).historyRewrite().withId((IIdType) null).execute(); fail(); } catch (NullPointerException e) { - assertThat(e.getMessage(), containsString("can not be null")); + assertThat(e.getMessage()).contains("can not be null"); } try { myClient.update().resource(p).historyRewrite().withId((String) null).execute(); fail(); } catch (NullPointerException e) { - assertThat(e.getMessage(), containsString("can not be null")); + assertThat(e.getMessage()).contains("can not be null"); } try { myClient.update().resource(p).historyRewrite().execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("No ID supplied for resource to update")); + assertThat(e.getMessage()).contains("No ID supplied for resource to update"); } } @@ -6664,14 +6629,14 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(p).historyRewrite().withId(noIdPartId).execute(); fail(); } catch (NullPointerException e) { - assertThat(e.getMessage(), containsString("must not be blank and must contain an ID")); + assertThat(e.getMessage()).contains("must not be blank and must contain an ID"); } try { myClient.update().resource(p).historyRewrite().withId("").execute(); fail(); } catch (NullPointerException e) { - assertThat(e.getMessage(), containsString("must not be blank and must contain an ID")); + assertThat(e.getMessage()).contains("must not be blank and must contain an ID"); } p.setId(noIdPartId); @@ -6679,7 +6644,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(p).historyRewrite().execute(); fail(); } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("No ID supplied for resource to update")); + assertThat(e.getMessage()).contains("No ID supplied for resource to update"); } } @@ -6749,7 +6714,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String returnedPatientMetaSource = returnedPatient.getMeta().getSource(); - assertTrue(returnedPatientMetaSource.startsWith(sourceURL)); + assertThat(returnedPatientMetaSource).startsWith(sourceURL); assertFalse(returnedPatientMetaSource.endsWith(requestId)); } @@ -6778,7 +6743,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Patient returnedPatient = (Patient) results.getEntry().get(0).getResource(); String returnedPatientMetaSource = returnedPatient.getMeta().getSource(); - assertEquals(1, results.getEntry().size()); + assertThat(results.getEntry()).hasSize(1); assertEquals(expectedSourceUrl, returnedPatientMetaSource); } @@ -6801,7 +6766,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(0, results.getEntry().size()); + assertThat(results.getEntry()).isEmpty(); } @Test @@ -6819,7 +6784,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { myClient.update().resource(patient).execute(); Patient unrelatedPatient = (Patient) myClient.create().resource(new Patient()).execute().getResource(); - assertNotEquals(unrelatedPatient.getIdElement().getIdPartAsLong(), patientId); + assertThat(patientId).isNotEqualTo(unrelatedPatient.getIdElement().getIdPartAsLong()); // ensure the patient has the expected overall history Bundle result = myClient.history() @@ -6827,7 +6792,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(2, result.getEntry().size()); + assertThat(result.getEntry()).hasSize(2); Patient patientV1 = (Patient) result.getEntry().get(1).getResource(); assertEquals(patientId, patientV1.getIdElement().getIdPartAsLong()); @@ -6855,9 +6820,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.after(dateV1)); assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_at=gt" + toStr(timeBetweenUpdates)); - assertEquals(2, resultIds.size()); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/1")); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); + assertThat(resultIds).hasSize(2); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/1"); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/2"); } private void verifyAtBehaviourWhenQueriedDateAfterTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -6865,8 +6830,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.after(dateV1)); assertTrue(timeBetweenUpdates.after(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_at=gt" + toStr(timeBetweenUpdates)); - assertEquals(1, resultIds.size()); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); + assertThat(resultIds).hasSize(1); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/2"); } private void verifyAtBehaviourWhenQueriedDateBeforeTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -6874,9 +6839,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.before(dateV1)); assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_at=gt" + toStr(timeBetweenUpdates)); - assertEquals(2, resultIds.size()); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/1")); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); + assertThat(resultIds).hasSize(2); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/1"); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/2"); } private void verifySinceBehaviourWhenQueriedDateDuringTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -6884,8 +6849,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.after(dateV1)); assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_since=" + toStr(timeBetweenUpdates)); - assertEquals(1, resultIds.size()); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); + assertThat(resultIds).hasSize(1); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/2"); } private void verifySinceBehaviourWhenQueriedDateAfterTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -6893,7 +6858,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.after(dateV1)); assertTrue(timeBetweenUpdates.after(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_since=" + toStr(timeBetweenUpdates)); - assertEquals(0, resultIds.size()); + assertThat(resultIds).isEmpty(); } private void verifySinceBehaviourWhenQueriedDateBeforeTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -6901,9 +6866,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.before(dateV1)); assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_since=" + toStr(timeBetweenUpdates)); - assertEquals(2, resultIds.size()); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/1")); - assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); + assertThat(resultIds).hasSize(2); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/1"); + assertThat(resultIds).contains("Patient/" + patientId + "/_history/2"); } @@ -6969,7 +6934,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .search(new SearchParameterMap(), mySrd) .getAllResources(); - assertTrue(orgs.isEmpty()); + assertThat(orgs).isEmpty(); } boolean isEnforceRefOnWrite = myStorageSettings.isEnforceReferentialIntegrityOnWrite(); @@ -6993,19 +6958,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .search(new SearchParameterMap(), mySrd) .getAllResources(); - assertTrue(orgs.isEmpty()); + assertThat(orgs).isEmpty(); } // only if all 3 are true do we expect this to fail - assertFalse( - theInput.IsAutoCreatePlaceholderReferences - && theInput.IsEnforceRefOnType - && theInput.IsEnforceRefOnWrite - ); + assertThat(theInput.IsAutoCreatePlaceholderReferences + && theInput.IsEnforceRefOnType + && theInput.IsEnforceRefOnWrite).isFalse(); } catch (InvalidRequestException ex) { - assertTrue(ex.getMessage().contains( + assertThat(ex.getMessage().contains( "Invalid resource reference" - ), ex.getMessage()); + )).as(ex.getMessage()).isTrue(); } finally { myStorageSettings.setEnforceReferentialIntegrityOnWrite(isEnforceRefOnWrite); myStorageSettings.setEnforceReferenceTargetTypes(isEnforceRefTargetTypes); @@ -7035,8 +6998,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Patient returnedPatient = (Patient) results.getEntry().get(0).getResource(); String returnedPatientMetaSource = returnedPatient.getMeta().getSource(); - assertEquals(1, results.getEntry().size()); - assertTrue(returnedPatientMetaSource.startsWith(sourceUri)); + assertThat(results.getEntry()).hasSize(1); + assertThat(returnedPatientMetaSource).startsWith(sourceUri); assertFalse(returnedPatientMetaSource.endsWith(requestId)); } @@ -7064,7 +7027,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Patient returnedPatient = (Patient) results.getEntry().get(0).getResource(); String returnedPatientMetaSource = returnedPatient.getMeta().getSource(); - assertEquals(1, results.getEntry().size()); + assertThat(results.getEntry()).hasSize(1); assertEquals(expectedSourceUrl, returnedPatientMetaSource); } @@ -7089,7 +7052,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(1, results.getEntry().size()); + assertThat(results.getEntry()).hasSize(1); Patient returnedPatient = (Patient) results.getEntry().get(0).getResource(); String returnedPatientMetaSource = returnedPatient.getMeta().getSource(); @@ -7114,7 +7077,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .returnBundle(Bundle.class) .execute(); - assertEquals(0, results.getEntry().size()); + assertThat(results.getEntry()).isEmpty(); } @Test @@ -7145,22 +7108,22 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { OperationOutcome oo = (OperationOutcome) responseBundle.getEntry().get(0).getResponse().getOutcome(); assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CREATE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); - assertEquals(1, responseBundle.getEntry().size()); + assertThat(responseBundle.getEntry()).hasSize(1); IdType id = new IdType(responseBundle.getEntry().get(0).getResponse().getLocationElement()); ConceptMap savedConceptMap = (ConceptMap) myClient.read().resource("ConceptMap").withId(id).execute(); assertEquals(conceptMap.getUrl(), savedConceptMap.getUrl()); assertEquals(conceptMap.getStatus(), savedConceptMap.getStatus()); - assertEquals(1, savedConceptMap.getGroup().size()); + assertThat(savedConceptMap.getGroup()).hasSize(1); ConceptMap.ConceptMapGroupComponent savedGroup = savedConceptMap.getGroup().get(0); assertEquals(group.getSource(), savedGroup.getSource()); assertEquals(group.getTarget(), savedGroup.getTarget()); - assertEquals(1, savedGroup.getElement().size()); + assertThat(savedGroup.getElement()).hasSize(1); ConceptMap.SourceElementComponent savedSource = savedGroup.getElement().get(0); assertEquals(source.getCode(), savedSource.getCode()); - assertEquals(1, source.getTarget().size()); + assertThat(source.getTarget()).hasSize(1); ConceptMap.TargetElementComponent savedTarget = savedSource.getTarget().get(0); assertEquals(target.getCode(), savedTarget.getCode()); @@ -7513,6 +7476,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ); } } + @Nested class SearchWithIdentifiers { private static final String SYSTEM = "http://acme.org/fhir/identifier/mrn"; @@ -7547,15 +7511,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } private void testAndAssertFailureFor(String theUrl) { - try { + assertThatThrownBy(() -> myClient.search() .byUrl(theUrl) .returnBundle(Bundle.class) - .execute(); - fail(); - } catch (InvalidRequestException exception) { - assertEquals("HTTP 400 Bad Request: HAPI-2498: Unsupported search modifier(s): \"[:identifier]\" for resource type \"Observation\". Valid search modifiers are: [:contains, :exact, :in, :iterate, :missing, :not-in, :of-type, :recurse, :text]", exception.getMessage()); - } + .execute()) + .isInstanceOf(InvalidRequestException.class) + .hasMessage("HTTP 400 Bad Request: HAPI-2498: Unsupported search modifier(s): \"[:identifier]\" for resource type \"Observation\". Valid search modifiers are: [:contains, :exact, :in, :iterate, :missing, :not-in, :of-type, :recurse, :text]"); } } } diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index d6c38965bbb..81e03a692b3 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -398,7 +398,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor; @Autowired - private DaoRegistry myDaoRegistry; + protected DaoRegistry myDaoRegistry; @Autowired private IBulkDataExportJobSchedulingHelper myBulkDataSchedulerHelper; diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/UpliftedRefchainsAndChainedSortingR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/UpliftedRefchainsAndChainedSortingR5Test.java index 50e2c2787c0..a3649b0279d 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/UpliftedRefchainsAndChainedSortingR5Test.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/UpliftedRefchainsAndChainedSortingR5Test.java @@ -13,16 +13,17 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.HapiExtensions; +import jakarta.annotation.Nonnull; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r5.model.Composition; -import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.CodeType; +import org.hl7.fhir.r5.model.Composition; import org.hl7.fhir.r5.model.DateType; import org.hl7.fhir.r5.model.Encounter; import org.hl7.fhir.r5.model.Enumerations; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.IdType; import org.hl7.fhir.r5.model.Identifier; import org.hl7.fhir.r5.model.Organization; import org.hl7.fhir.r5.model.Patient; @@ -35,7 +36,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; -import jakarta.annotation.Nonnull; import java.util.List; import static org.apache.commons.lang3.StringUtils.countMatches; diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java index 961621e7a6b..3870487c81c 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5Test.java @@ -1,11 +1,26 @@ package ca.uhn.fhir.jpa.provider.r5; +import ca.uhn.fhir.batch2.jobs.reindex.ReindexAppCtx; +import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters; +import ca.uhn.fhir.batch2.model.JobInstanceStartRequest; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; +import ca.uhn.fhir.rest.api.SortOrderEnum; +import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor; +import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.util.BundleBuilder; @@ -17,7 +32,6 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; -import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; @@ -25,13 +39,18 @@ import org.hl7.fhir.r5.model.CarePlan; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Condition; import org.hl7.fhir.r5.model.DateTimeType; +import org.hl7.fhir.r5.model.Extension; import org.hl7.fhir.r5.model.MedicationRequest; +import org.hl7.fhir.r5.model.MedicinalProductDefinition; import org.hl7.fhir.r5.model.Observation; import org.hl7.fhir.r5.model.Observation.ObservationComponentComponent; import org.hl7.fhir.r5.model.Organization; import org.hl7.fhir.r5.model.Parameters; import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Quantity; +import org.hl7.fhir.r5.model.SearchParameter; +import org.hl7.fhir.r5.model.StringType; +import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -44,15 +63,13 @@ import org.springframework.util.comparator.Comparators; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.leftPad; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasItem; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; @SuppressWarnings("Duplicates") public class ResourceProviderR5Test extends BaseResourceProviderR5Test { @@ -110,7 +127,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .returnBundle(Bundle.class) .execute(); List ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(pt1id)); + assertThat(ids).containsExactlyInAnyOrder(pt1id); output = myClient .search() @@ -119,7 +136,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .returnBundle(Bundle.class) .execute(); ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(pt1id)); + assertThat(ids).containsExactlyInAnyOrder(pt1id); } @@ -135,7 +152,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .where(org.hl7.fhir.r4.model.Patient.NAME.matches().value("Hello")) .returnBundle(Bundle.class) .execute(); - assertEquals(1, response0.getEntry().size()); + assertThat(response0.getEntry()).hasSize(1); // Perform the search again (should return the same) Bundle response1 = myClient.search() @@ -143,7 +160,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .where(org.hl7.fhir.r4.model.Patient.NAME.matches().value("Hello")) .returnBundle(Bundle.class) .execute(); - assertEquals(1, response1.getEntry().size()); + assertThat(response1.getEntry()).hasSize(1); assertEquals(response0.getId(), response1.getId()); // Pretend the search was errored out @@ -155,8 +172,8 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .where(org.hl7.fhir.r4.model.Patient.NAME.matches().value("Hello")) .returnBundle(Bundle.class) .execute(); - assertEquals(1, response3.getEntry().size()); - assertNotEquals(response0.getId(), response3.getId()); + assertThat(response3.getEntry()).hasSize(1); + assertThat(response3.getId()).isNotEqualTo(response0.getId()); } @@ -196,7 +213,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .returnBundle(Bundle.class) .count(1) .execute(); - assertEquals(1, response0.getEntry().size()); + assertThat(response0.getEntry()).hasSize(1); // Make sure it works for now myClient.loadPage().next(response0).execute(); @@ -209,9 +226,183 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { myClient.loadPage().next(response0).execute(); } catch (NotImplementedOperationException e) { assertEquals(501, e.getStatusCode()); - assertThat(e.getMessage(), containsString("Some Failure Message")); + assertThat(e.getMessage()).contains("Some Failure Message"); } + } + @Test + public void searchForNewerResources_fullTextSearchWithFilterAndCount_shouldReturnAccurateResults() { + IParser parser = myFhirContext.newJsonParser(); + int count = 10; + + boolean presetFilterParameterEnabled = myStorageSettings.isFilterParameterEnabled(); + boolean presetAdvancedHSearchIndexing = myStorageSettings.isAdvancedHSearchIndexing(); + + try { + // fullTextSearch means Advanced Hibernate Search + myStorageSettings.setFilterParameterEnabled(true); + myStorageSettings.setAdvancedHSearchIndexing(true); + + // create custom search parameters - the _filter and _include are needed + { + @SuppressWarnings("unchecked") + IFhirResourceDao spDao = myDaoRegistry.getResourceDao("SearchParameter"); + SearchParameter sp; + + @Language("JSON") + String includeParam = """ + { + "resourceType": "SearchParameter", + "id": "9905463e-e817-4db0-9a3e-ff6aa3427848", + "meta": { + "versionId": "2", + "lastUpdated": "2024-03-28T12:53:57.874+00:00", + "source": "#7b34a4bfa42fe3ae" + }, + "title": "Medicinal Product Manfacturer", + "status": "active", + "publisher": "MOH-IDMS", + "code": "productmanufacturer", + "base": [ + "MedicinalProductDefinition" + ], + "type": "reference", + "expression": "MedicinalProductDefinition.operation.organization" + } + """; + sp = parser.parseResource(SearchParameter.class, includeParam); + spDao.create(sp, new SystemRequestDetails()); + sp = null; + @Language("JSON") + String filterParam = """ + { + "resourceType": "SearchParameter", + "id": "SEARCH-PARAMETER-MedicinalProductDefinition-SearchableString", + "meta": { + "versionId": "2", + "lastUpdated": "2024-03-27T19:20:25.200+00:00", + "source": "#384dd6bccaeafa6c" + }, + "url": "https://health.gov.on.ca/idms/fhir/SearchParameter/MedicinalProductDefinition-SearchableString", + "version": "1.0.0", + "name": "MedicinalProductDefinitionSearchableString", + "status": "active", + "publisher": "MOH-IDMS", + "description": "Search Parameter for the MedicinalProductDefinition Searchable String Extension", + "code": "MedicinalProductDefinitionSearchableString", + "base": [ + "MedicinalProductDefinition" + ], + "type": "string", + "expression": "MedicinalProductDefinition.extension('https://health.gov.on.ca/idms/fhir/StructureDefinition/SearchableExtraString')", + "target": [ + "MedicinalProductDefinition" + ] + } + """; + sp = parser.parseResource(SearchParameter.class, filterParam); + spDao.create(sp, new SystemRequestDetails()); + } + // create MedicinalProductDefinitions + MedicinalProductDefinition mdr; + { + @Language("JSON") + String mpdstr = """ + { + "resourceType": "MedicinalProductDefinition", + "id": "36fb418b-4b1f-414c-bbb1-731bc8744b93", + "meta": { + "versionId": "17", + "lastUpdated": "2024-06-10T16:52:23.907+00:00", + "source": "#3a309416d5f52c5b", + "profile": [ + "https://health.gov.on.ca/idms/fhir/StructureDefinition/IDMS_MedicinalProductDefinition" + ] + }, + "extension": [ + { + "url": "https://health.gov.on.ca/idms/fhir/StructureDefinition/SearchableExtraString", + "valueString": "zahidbrand0610-2up|genupuu|qwewqe2 111|11111115|DF other des|Biologic|Oncology|Private Label" + } + ], + "status": { + "coding": [ + { + "system": "http://hl7.org/fhir/ValueSet/publication-status", + "code": "active", + "display": "Active" + } + ] + }, + "name": [ + { + "productName": "zahidbrand0610-2up" + } + ] + } + """; + mdr = parser.parseResource(MedicinalProductDefinition.class, mpdstr); + } + IFhirResourceDao mdrdao = myDaoRegistry.getResourceDao(MedicinalProductDefinition.class); + + /* + * We actually want a bunch of non-matching resources in the db + * that won't match the filter before we get to the one that will. + * + * To this end, we're going to insert more than we plan + * on retrieving to ensure the _filter is being used in both the + * count query and the actual db hit + */ + List productNames = mdr.getName(); + mdr.setName(null); + List extensions = mdr.getExtension(); + mdr.setExtension(null); + // we need at least 10 of these; 20 should be good + for (int i = 0; i < 2 * count; i++) { + mdr.addName(new MedicinalProductDefinition.MedicinalProductDefinitionNameComponent("Product " + i)); + mdr.addExtension() + .setUrl("https://health.gov.on.ca/idms/fhir/StructureDefinition/SearchableExtraString") + .setValue(new StringType("Non-matching string " + i)); + mdrdao.create(mdr, new SystemRequestDetails()); + } + mdr.setName(productNames); + mdr.setExtension(extensions); + mdrdao.create(mdr, new SystemRequestDetails()); + + // do a reindex + ReindexJobParameters jobParameters = new ReindexJobParameters(); + jobParameters.setRequestPartitionId(RequestPartitionId.allPartitions()); + JobInstanceStartRequest request = new JobInstanceStartRequest(); + request.setJobDefinitionId(ReindexAppCtx.JOB_REINDEX); + request.setParameters(jobParameters); + Batch2JobStartResponse response = myJobCoordinator.startInstance(new SystemRequestDetails(), request); + + myBatch2JobHelper.awaitJobCompletion(response); + + // query like: + // MedicinalProductDefinition?_getpagesoffset=0&_count=10&_total=accurate&_sort:asc=name&status=active&_include=MedicinalProductDefinition:productmanufacturer&_filter=MedicinalProductDefinitionSearchableString%20co%20%22zah%22 + SearchParameterMap map = new SearchParameterMap(); + map.setCount(10); + map.setSearchTotalMode(SearchTotalModeEnum.ACCURATE); + map.setSort(new SortSpec().setOrder(SortOrderEnum.ASC).setParamName("name")); + map.setIncludes(Set.of( + new Include("MedicinalProductDefinition:productmanufacturer") + )); + map.add("_filter", new StringParam("MedicinalProductDefinitionSearchableString co \"zah\"")); + + // test + IBundleProvider result = mdrdao.search(map, new SystemRequestDetails()); + + // validate + // we expect to find our 1 matching resource + assertEquals(1, result.getAllResources().size()); + assertNotNull(result.size()); + assertEquals(1, result.size()); + } finally { + // reset values + myStorageSettings.setFilterParameterEnabled(presetFilterParameterEnabled); + myStorageSettings.setAdvancedHSearchIndexing(presetAdvancedHSearchIndexing); + } } @Test @@ -254,7 +445,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { .returnBundle(Bundle.class) .execute(); List ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualified()).collect(Collectors.toList()); - assertThat(ids, containsInAnyOrder(oid)); + assertThat(ids).containsExactlyInAnyOrder(oid); } @@ -278,7 +469,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { myCaptureQueriesListener.logSelectQueries(); assertEquals(2, output.getTotal()); - assertEquals(0, output.getEntry().size()); + assertThat(output.getEntry()).isEmpty(); } @Test @@ -370,7 +561,7 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { ourLog.debug("Bundle: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(found)); List list = toUnqualifiedVersionlessIds(found); - assertEquals(4, found.getEntry().size()); + assertThat(found.getEntry()).hasSize(4); assertEquals(oid3, list.get(0)); assertEquals(oid1, list.get(1)); assertEquals(oid4, list.get(2)); @@ -403,9 +594,9 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { assertEquals(Bundle.BundleType.SEARCHSET, b.getType()); List ids = toUnqualifiedVersionlessIds(b); - assertThat(ids, containsInAnyOrder(p1Id, c1Id, obs1Id)); - assertThat(ids, Matchers.not(hasItem(o1Id))); - assertThat(ids, Matchers.not(hasItem(m1Id))); + assertThat(ids).containsExactlyInAnyOrder(p1Id, c1Id, obs1Id); + assertThat(ids).doesNotContain(o1Id); + assertThat(ids).doesNotContain(m1Id); } @Test @@ -434,9 +625,9 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { assertEquals(Bundle.BundleType.SEARCHSET, b.getType()); List ids = toUnqualifiedVersionlessIds(b); - assertThat(ids, containsInAnyOrder(p1Id, c1Id, obs1Id)); - assertThat(ids, Matchers.not(hasItem(o1Id))); - assertThat(ids, Matchers.not(hasItem(m1Id))); + assertThat(ids).containsExactlyInAnyOrder(p1Id, c1Id, obs1Id); + assertThat(ids).doesNotContain(o1Id); + assertThat(ids).doesNotContain(m1Id); } @Test @@ -473,11 +664,11 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { assertEquals(Bundle.BundleType.SEARCHSET, b.getType()); List ids = toUnqualifiedVersionlessIds(b); - assertThat(ids, containsInAnyOrder(p1Id, c1Id, obs1Id)); - assertThat(ids, Matchers.not(hasItem(o1Id))); - assertThat(ids, Matchers.not(hasItem(m1Id))); - assertThat(ids, Matchers.not(hasItem(p2Id))); - assertThat(ids, Matchers.not(hasItem(o2Id))); + assertThat(ids).containsExactlyInAnyOrder(p1Id, c1Id, obs1Id); + assertThat(ids).doesNotContain(o1Id); + assertThat(ids).doesNotContain(m1Id); + assertThat(ids).doesNotContain(p2Id); + assertThat(ids).doesNotContain(o2Id); } @@ -614,4 +805,5 @@ public class ResourceProviderR5Test extends BaseResourceProviderR5Test { } return retVal; } + }