Instance level reindex operations (#4699)

* Reindex improvements

* Work

* Work on reindex

* Test fixes

* Generator now working

* Add instance reindex

* Work

* Build fixes

* Build fix

* Fixes

* Add changelog

* Test fixes

* Resolve checkstyle fix

* Version bump

* Address review comments

* Resolve imports

* Address review comments

* Test fix
This commit is contained in:
James Agnew 2023-04-06 17:00:41 -04:00 committed by GitHub
parent 182ac3b36c
commit e500b23fe6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 2634 additions and 597 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
@ -12,7 +12,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -0,0 +1,6 @@
---
type: add
issue: 4699
title: "Two new operations have been added to the JPA server:
* Instance level $reindex performs a synchronous reindex of a single resource and returns a Parameters object containing all previous and new indexes
* Instance level $reindex-dryrun simulates a reindex and shows the previous and new indexes generated"

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 4699
title: "The BundleBuilder utility class did not work with DSTU2 bundles. This has been
corrected."

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 4699
title: "When updating resource fields targeted by a Combo Non-Unique SearchParameter, previous
indexes were not deleted meaning that old search values could still find the resource. This
has been corrected."

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -96,6 +96,7 @@ import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.ProcessMessageProvider;
import ca.uhn.fhir.jpa.provider.InstanceReindexProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
@ -138,7 +139,9 @@ import ca.uhn.fhir.jpa.search.cache.DatabaseSearchResultCacheSvcImpl;
import ca.uhn.fhir.jpa.search.cache.ISearchCacheSvc;
import ca.uhn.fhir.jpa.search.cache.ISearchResultCacheSvc;
import ca.uhn.fhir.jpa.search.elastic.IndexNamePrefixLayoutStrategy;
import ca.uhn.fhir.jpa.search.reindex.IInstanceReindexService;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.InstanceReindexServiceImpl;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexer;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
@ -376,6 +379,18 @@ public class JpaConfig {
return new ResourceReindexingSvcImpl();
}
@Bean
@Lazy
public IInstanceReindexService instanceReindexService() {
return new InstanceReindexServiceImpl();
}
@Bean
@Lazy
public InstanceReindexProvider instanceReindexProvider(IInstanceReindexService theInstanceReindexService) {
return new InstanceReindexProvider(theInstanceReindexService);
}
@Bean
public ResourceReindexer resourceReindexer(FhirContext theFhirContext) {
return new ResourceReindexer(theFhirContext);

View File

@ -1013,8 +1013,10 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
failIfPartitionMismatch(theRequest, entity);
// Extract search params for resource
mySearchParamWithInlineReferencesExtractor.populateFromResource(requestPartitionId, newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
// Actually persist the ResourceTable and ResourceHistoryTable entities
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
if (theForceUpdate) {
@ -1044,11 +1046,11 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
} else {
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, false);
entity.setUpdated(theTransactionDetails.getTransactionDate());
entity.setIndexStatus(null);
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, false);
}
}
@ -1105,9 +1107,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
* those by path and not by parameter name.
*/
if (thePerformIndexing && newParams != null) {
Map<String, Boolean> searchParamPresenceMap = getSearchParamPresenceMap(entity, newParams);
AddRemoveCount presenceCount = mySearchParamPresenceSvc.updatePresence(entity, searchParamPresenceMap);
AddRemoveCount presenceCount = mySearchParamPresenceSvc.updatePresence(entity, newParams.mySearchParamPresentEntities);
// Interceptor broadcast: JPA_PERFTRACE_INFO
if (!presenceCount.isEmpty()) {
@ -1248,22 +1248,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
encodedResource.setEncoding(theEncoding);
}
@Nonnull
private Map<String, Boolean> getSearchParamPresenceMap(ResourceTable entity, ResourceIndexedSearchParams newParams) {
Map<String, Boolean> retval = new HashMap<>();
for (String nextKey : newParams.getPopulatedResourceLinkParameters()) {
retval.put(nextKey, Boolean.TRUE);
}
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(entity.getResourceType());
activeSearchParams.getReferenceSearchParamNames().forEach(key -> {
if (!retval.containsKey(key)) {
retval.put(key, Boolean.FALSE);
}
});
return retval;
}
/**
* TODO eventually consider refactoring this to be part of an interceptor.

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
@ -1232,7 +1233,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
//If the resolved fhir model is null, we don't need to run pre-access over or pre-show over it.
if (retVal != null) {
invokeStoragePreaccessResources(theId, theRequest, retVal);
invokeStoragePreAccessResources(theId, theRequest, retVal);
retVal = invokeStoragePreShowResources(theRequest, retVal);
}
@ -1241,29 +1242,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
private T invokeStoragePreShowResources(RequestDetails theRequest, T retVal) {
// Interceptor broadcast: STORAGE_PRESHOW_RESOURCES
SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(retVal);
HookParams params = new HookParams()
.add(IPreResourceShowDetails.class, showDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
//noinspection unchecked
retVal = (T) showDetails.getResource(0);//TODO GGG/JA : getting resource 0 is interesting. We apparently allow null values in the list. Should we?
retVal = invokeStoragePreShowResources(myInterceptorBroadcaster, theRequest, retVal);
return retVal;
}
private void invokeStoragePreaccessResources(IIdType theId, RequestDetails theRequest, T theResource) {
// Interceptor broadcast: STORAGE_PREACCESS_RESOURCES
SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(theResource);
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
if (accessDetails.isDontReturnResourceAtIndex(0)) {
throw new ResourceNotFoundException(Msg.code(1995) + "Resource " + theId + " is not known");
}
private void invokeStoragePreAccessResources(IIdType theId, RequestDetails theRequest, T theResource) {
invokeStoragePreAccessResources(myInterceptorBroadcaster, theRequest, theId, theResource);
}
@Override
@ -1954,6 +1938,37 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
myIdHelperService = theIdHelperService;
}
public static <T extends IBaseResource> T invokeStoragePreShowResources(IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, T retVal) {
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRESHOW_RESOURCES, theInterceptorBroadcaster, theRequest)) {
SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(retVal);
HookParams params = new HookParams()
.add(IPreResourceShowDetails.class, showDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params);
//noinspection unchecked
retVal = (T) showDetails.getResource(0);//TODO GGG/JA : getting resource 0 is interesting. We apparently allow null values in the list. Should we?
return retVal;
} else {
return retVal;
}
}
public static void invokeStoragePreAccessResources(IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, IIdType theId, IBaseResource theResource) {
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PREACCESS_RESOURCES, theInterceptorBroadcaster, theRequest)) {
SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(theResource);
HookParams params = new HookParams()
.add(IPreResourceAccessDetails.class, accessDetails)
.add(RequestDetails.class, theRequest)
.addIfMatchesType(ServletRequestDetails.class, theRequest);
CompositeInterceptorBroadcaster.doCallHooks(theInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params);
if (accessDetails.isDontReturnResourceAtIndex(0)) {
throw new ResourceNotFoundException(Msg.code(1995) + "Resource " + theId + " is not known");
}
}
}
private static class IdChecker implements IValidatorModule {
private final ValidationModeEnum myMode;

View File

@ -126,8 +126,8 @@ public class DaoSearchParamSynchronizer {
}
<T> List<T> subtract(Collection<T> theSubtractFrom, Collection<T> theToSubtract) {
assert theSubtractFrom != theToSubtract;
public static <T> List<T> subtract(Collection<T> theSubtractFrom, Collection<T> theToSubtract) {
assert theSubtractFrom != theToSubtract || (theSubtractFrom.isEmpty());
if (theSubtractFrom.isEmpty()) {
return new ArrayList<>();

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
@ -95,12 +96,7 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit
extractInlineReferences(theRequest, theResource, theTransactionDetails);
}
mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequest, theParams, theExistingParams, theEntity, theResource, theTransactionDetails, thePerformIndexing);
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType());
if (myStorageSettings.getIndexMissingFields() == JpaStorageSettings.IndexEnabledEnum.ENABLED) {
theParams.findMissingSearchParams(myPartitionSettings, myStorageSettings, theEntity, activeSearchParams);
}
mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequest, theParams, theExistingParams, theEntity, theResource, theTransactionDetails, thePerformIndexing, ISearchParamExtractor.ALL_PARAMS);
/*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
@ -113,12 +109,6 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit
}
}
extractComboParameters(theEntity, theParams);
}
private void extractComboParameters(ResourceTable theEntity, ResourceIndexedSearchParams theParams) {
mySearchParamExtractorService.extractSearchParamComboUnique(theEntity, theParams);
mySearchParamExtractorService.extractSearchParamComboNonUnique(theEntity, theParams);
}
@Nullable
@ -169,13 +159,13 @@ public class SearchParamWithInlineReferencesExtractor extends BaseSearchParamWit
* String Uniques
*/
if (myStorageSettings.isUniqueIndexesEnabled()) {
for (ResourceIndexedComboStringUnique next : myDaoSearchParamSynchronizer.subtract(theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) {
for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract(theExistingParams.myComboStringUniques, theParams.myComboStringUniques)) {
ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next);
theEntity.getParamsComboStringUnique().remove(next);
}
boolean haveNewStringUniqueParams = false;
for (ResourceIndexedComboStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) {
for (ResourceIndexedComboStringUnique next : DaoSearchParamSynchronizer.subtract(theParams.myComboStringUniques, theExistingParams.myComboStringUniques)) {
if (myStorageSettings.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedComboStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) {

View File

@ -0,0 +1,75 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2023 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.provider;
import ca.uhn.fhir.jpa.search.reindex.IInstanceReindexService;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class InstanceReindexProvider {
private final IInstanceReindexService myInstanceReindexService;
/**
* Constructor
*/
public InstanceReindexProvider(@Nonnull IInstanceReindexService theInstanceReindexService) {
Validate.notNull(theInstanceReindexService);
myInstanceReindexService = theInstanceReindexService;
}
@Operation(name = ProviderConstants.OPERATION_REINDEX_DRYRUN, idempotent = true, global = true)
public IBaseParameters reindexInstanceDryRun(
@IdParam IIdType theId,
@OperationParam(name="code", typeName = "code", min = 0, max = OperationParam.MAX_UNLIMITED) List<IPrimitiveType<String>> theCodes,
RequestDetails theRequestDetails
) {
Set<String> codes = null;
if (theCodes != null && theCodes.size() > 0) {
codes = theCodes
.stream()
.map(IPrimitiveType::getValueAsString)
.collect(Collectors.toSet());
}
return myInstanceReindexService.reindexDryRun(theRequestDetails, theId, codes);
}
@Operation(name = ProviderConstants.OPERATION_REINDEX, idempotent = false, global = true)
public IBaseParameters reindexInstance(
@IdParam IIdType theId,
RequestDetails theRequestDetails
) {
return myInstanceReindexService.reindex(theRequestDetails, theId);
}
}

View File

@ -0,0 +1,40 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2023 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.search.reindex;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nullable;
import java.util.Set;
public interface IInstanceReindexService {
/**
* Simulate a reindex and return the details about what would change
*/
IBaseParameters reindexDryRun(RequestDetails theRequestDetails, IIdType theResourceId, @Nullable Set<String> theParameters);
/**
* Perform a reindex on a single resource and return details about what changed
*/
IBaseParameters reindex(RequestDetails theRequestDetails, IIdType theResourceId);
}

View File

@ -0,0 +1,616 @@
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2023 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.search.reindex;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser;
import ca.uhn.fhir.jpa.dao.tx.IHapiTransactionService;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.partition.BaseRequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.hapi.converters.canonical.VersionCanonicalizer;
import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.DecimalType;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.UrlType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer.subtract;
import static java.util.Comparator.comparing;
import static org.apache.commons.collections4.CollectionUtils.intersection;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class InstanceReindexServiceImpl implements IInstanceReindexService {
private final FhirContext myContextR4 = FhirContext.forR4Cached();
@Autowired
protected IJpaStorageResourceParser myJpaStorageResourceParser;
@Autowired
private SearchParamExtractorService mySearchParamExtractorService;
@Autowired
private BaseRequestPartitionHelperSvc myPartitionHelperSvc;
@Autowired
private IHapiTransactionService myTransactionService;
@Autowired
private IInterceptorService myInterceptorService;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private VersionCanonicalizer myVersionCanonicalizer;
@Autowired
private PartitionSettings myPartitionSettings;
private final CustomThymeleafNarrativeGenerator myNarrativeGenerator;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
/**
* Constructor
*/
public InstanceReindexServiceImpl() {
myNarrativeGenerator = new CustomThymeleafNarrativeGenerator("classpath:ca/uhn/fhir/jpa/search/reindex/reindex-outcome-narrative.properties");
}
@Override
public IBaseParameters reindexDryRun(RequestDetails theRequestDetails, IIdType theResourceId, @Nullable Set<String> theParameters) {
RequestPartitionId partitionId = determinePartition(theRequestDetails, theResourceId);
TransactionDetails transactionDetails = new TransactionDetails();
Parameters retValCanonical = myTransactionService
.withRequest(theRequestDetails)
.withTransactionDetails(transactionDetails)
.withRequestPartitionId(partitionId)
.execute(() -> reindexDryRunInTransaction(theRequestDetails, theResourceId, partitionId, transactionDetails, theParameters));
return myVersionCanonicalizer.parametersFromCanonical(retValCanonical);
}
@Override
public IBaseParameters reindex(RequestDetails theRequestDetails, IIdType theResourceId) {
RequestPartitionId partitionId = determinePartition(theRequestDetails, theResourceId);
TransactionDetails transactionDetails = new TransactionDetails();
Parameters retValCanonical = myTransactionService
.withRequest(theRequestDetails)
.withTransactionDetails(transactionDetails)
.withRequestPartitionId(partitionId)
.execute(() -> reindexInTransaction(theRequestDetails, theResourceId));
return myVersionCanonicalizer.parametersFromCanonical(retValCanonical);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Nonnull
private Parameters reindexInTransaction(RequestDetails theRequestDetails, IIdType theResourceId) {
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType());
ResourceTable entity = (ResourceTable) dao.readEntity(theResourceId, theRequestDetails);
IBaseResource resource = myJpaStorageResourceParser.toResource(entity, false);
// Invoke the pre-access and pre-show interceptors in case there are any security
// restrictions or audit requirements around the user accessing this resource
BaseHapiFhirResourceDao.invokeStoragePreAccessResources(myInterceptorService, theRequestDetails, theResourceId, resource);
BaseHapiFhirResourceDao.invokeStoragePreShowResources(myInterceptorService, theRequestDetails, resource);
ResourceIndexedSearchParams existingParamsToPopulate = new ResourceIndexedSearchParams(entity);
existingParamsToPopulate.mySearchParamPresentEntities.addAll(entity.getSearchParamPresents());
dao.reindex(resource, entity);
ResourceIndexedSearchParams newParamsToPopulate = new ResourceIndexedSearchParams(entity);
newParamsToPopulate.mySearchParamPresentEntities.addAll(entity.getSearchParamPresents());
return buildIndexResponse(existingParamsToPopulate, newParamsToPopulate, true);
}
@Nonnull
private Parameters reindexDryRunInTransaction(RequestDetails theRequestDetails, IIdType theResourceId, RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails, Set<String> theParameters) {
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(theResourceId.getResourceType());
ResourceTable entity = (ResourceTable) dao.readEntity(theResourceId, theRequestDetails);
IBaseResource resource = myJpaStorageResourceParser.toResource(entity, false);
// Invoke the pre-access and pre-show interceptors in case there are any security
// restrictions or audit requirements around the user accessing this resource
BaseHapiFhirResourceDao.invokeStoragePreAccessResources(myInterceptorService, theRequestDetails, theResourceId, resource);
BaseHapiFhirResourceDao.invokeStoragePreShowResources(myInterceptorService, theRequestDetails, resource);
ISearchParamExtractor.ISearchParamFilter searchParamFilter = ISearchParamExtractor.ALL_PARAMS;
if (theParameters != null) {
searchParamFilter = params -> params
.stream()
.filter(t -> theParameters.contains(t.getName()))
.collect(Collectors.toSet());
}
ResourceIndexedSearchParams newParamsToPopulate = new ResourceIndexedSearchParams();
mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequestDetails, newParamsToPopulate, new ResourceIndexedSearchParams(), entity, resource, theTransactionDetails, false, searchParamFilter);
ResourceIndexedSearchParams existingParamsToPopulate;
boolean showAction;
if (theParameters == null) {
existingParamsToPopulate = new ResourceIndexedSearchParams(entity);
existingParamsToPopulate.mySearchParamPresentEntities.addAll(entity.getSearchParamPresents());
fillInParamNames(entity, existingParamsToPopulate.mySearchParamPresentEntities, theResourceId.getResourceType());
showAction = true;
} else {
existingParamsToPopulate = new ResourceIndexedSearchParams();
showAction = false;
}
return buildIndexResponse(existingParamsToPopulate, newParamsToPopulate, showAction);
}
@Nonnull
private RequestPartitionId determinePartition(RequestDetails theRequestDetails, IIdType theResourceId) {
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forRead(theResourceId);
return myPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
}
@Nonnull
@VisibleForTesting
Parameters buildIndexResponse(ResourceIndexedSearchParams theExistingParams, ResourceIndexedSearchParams theNewParams, boolean theShowAction) {
Parameters parameters = new Parameters();
Parameters.ParametersParameterComponent narrativeParameter = parameters.addParameter();
narrativeParameter.setName("Narrative");
// Normal indexes
addParamsNonMissing(parameters, "CoordinateIndexes", "Coords", theExistingParams.myCoordsParams, theNewParams.myCoordsParams, new CoordsParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "DateIndexes", "Date", theExistingParams.myDateParams, theNewParams.myDateParams, new DateParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "NumberIndexes", "Number", theExistingParams.myNumberParams, theNewParams.myNumberParams, new NumberParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "QuantityIndexes", "Quantity", theExistingParams.myQuantityParams, theNewParams.myQuantityParams, new QuantityParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "QuantityIndexes", "QuantityNormalized", theExistingParams.myQuantityNormalizedParams, theNewParams.myQuantityNormalizedParams, new QuantityNormalizedParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "UriIndexes", "Uri", theExistingParams.myUriParams, theNewParams.myUriParams, new UriParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "StringIndexes", "String", theExistingParams.myStringParams, theNewParams.myStringParams, new StringParamPopulator(), theShowAction);
addParamsNonMissing(parameters, "TokenIndexes", "Token", theExistingParams.myTokenParams, theNewParams.myTokenParams, new TokenParamPopulator(), theShowAction);
// Resource links
addParams(parameters, "ResourceLinks", "Reference", normalizeLinks(theExistingParams.myLinks), normalizeLinks(theNewParams.myLinks), new ResourceLinkPopulator(), theShowAction);
// Combo search params
addParams(parameters, "UniqueIndexes", "ComboStringUnique", theExistingParams.myComboStringUniques, theNewParams.myComboStringUniques, new ComboStringUniquePopulator(), theShowAction);
addParams(parameters, "NonUniqueIndexes", "ComboTokenNonUnique", theExistingParams.myComboTokenNonUnique, theNewParams.myComboTokenNonUnique, new ComboTokenNonUniquePopulator(), theShowAction);
// Missing (:missing) indexes
addParamsMissing(parameters, "Coords", theExistingParams.myCoordsParams, theNewParams.myCoordsParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "Date", theExistingParams.myDateParams, theNewParams.myDateParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "Number", theExistingParams.myNumberParams, theNewParams.myNumberParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "Quantity", theExistingParams.myQuantityParams, theNewParams.myQuantityParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "QuantityNormalized", theExistingParams.myQuantityNormalizedParams, theNewParams.myQuantityNormalizedParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "Uri", theExistingParams.myUriParams, theNewParams.myUriParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "String", theExistingParams.myStringParams, theNewParams.myStringParams, new MissingIndexParamPopulator<>(), theShowAction);
addParamsMissing(parameters, "Token", theExistingParams.myTokenParams, theNewParams.myTokenParams, new MissingIndexParamPopulator<>(), theShowAction);
addParams(parameters, "MissingIndexes", "Reference", theExistingParams.mySearchParamPresentEntities, theNewParams.mySearchParamPresentEntities, new SearchParamPresentParamPopulator(), theShowAction);
String narrativeText = myNarrativeGenerator.generateResourceNarrative(myContextR4, parameters);
narrativeParameter.setValue(new StringType(narrativeText));
return parameters;
}
/**
* The {@link SearchParamPresentEntity} entity doesn't actually store the parameter names
* in the database entity, it only stores a hash. So we brute force possible hashes here
* to figure out the associated param names.
*/
private void fillInParamNames(ResourceTable theEntity, Collection<SearchParamPresentEntity> theTarget, String theResourceName) {
Map<Long, String> hashes = new HashMap<>();
ResourceSearchParams searchParams = mySearchParamRegistry.getActiveSearchParams(theResourceName);
for (RuntimeSearchParam next : searchParams.values()) {
hashes.put(SearchParamPresentEntity.calculateHashPresence(myPartitionSettings, theEntity.getPartitionId(), theResourceName, next.getName(), true), next.getName());
hashes.put(SearchParamPresentEntity.calculateHashPresence(myPartitionSettings, theEntity.getPartitionId(), theResourceName, next.getName(), false), next.getName());
}
for (SearchParamPresentEntity next : theTarget) {
if (next.getParamName() == null) {
String name = hashes.get(next.getHashPresence());
name = defaultIfBlank(name, "(unknown)");
next.setParamName(name);
}
}
}
private enum ActionEnum {
ADD,
REMOVE,
UNKNOWN,
NO_CHANGE
}
private static abstract class BaseParamPopulator<T> {
@Nonnull
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, T theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = theParent
.addPart()
.setName(toPartName(theParam));
retVal
.addPart()
.setName("Action")
.setValue(new CodeType(theAction.name()));
if (theParamTypeName != null) {
retVal
.addPart()
.setName("Type")
.setValue(new CodeType(theParamTypeName));
}
return retVal;
}
protected abstract String toPartName(T theParam);
public void sort(List<T> theParams) {
theParams.sort(comparing(this::toPartName));
}
}
public static abstract class BaseIndexParamPopulator<T extends BaseResourceIndexedSearchParam> extends BaseParamPopulator<T> {
@Override
protected String toPartName(T theParam) {
return theParam.getParamName();
}
}
private static class ComboStringUniquePopulator extends BaseParamPopulator<ResourceIndexedComboStringUnique> {
@Override
protected String toPartName(ResourceIndexedComboStringUnique theParam) {
return theParam.getIndexString();
}
}
private static class ComboTokenNonUniquePopulator extends BaseParamPopulator<ResourceIndexedComboTokenNonUnique> {
@Override
protected String toPartName(ResourceIndexedComboTokenNonUnique theParam) {
return theParam.getIndexString();
}
}
private static class CoordsParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamCoords> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamCoords theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Latitude")
.setValue(new DecimalType(theParam.getLatitude()));
retVal
.addPart()
.setName("Longitude")
.setValue(new DecimalType(theParam.getLongitude()));
return retVal;
}
}
private static class DateParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamDate> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamDate theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("High")
.setValue(new InstantType(theParam.getValueHigh()));
retVal
.addPart()
.setName("Low")
.setValue(new InstantType(theParam.getValueLow()));
return retVal;
}
}
private static class MissingIndexParamPopulator<T extends BaseResourceIndexedSearchParam> extends BaseIndexParamPopulator<T> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, T theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Missing")
.setValue(new BooleanType(theParam.isMissing()));
return retVal;
}
}
private static class NumberParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamNumber> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamNumber theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Value")
.setValue(new DecimalType(theParam.getValue()));
return retVal;
}
}
private static class QuantityParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamQuantity> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamQuantity theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Value")
.setValue(new DecimalType(theParam.getValue()));
retVal
.addPart()
.setName("System")
.setValue(new UriType(theParam.getSystem()));
retVal
.addPart()
.setName("Units")
.setValue(new CodeType(theParam.getUnits()));
return retVal;
}
}
private static class QuantityNormalizedParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamQuantityNormalized> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamQuantityNormalized theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Value")
.setValue(new DecimalType(theParam.getValue()));
retVal
.addPart()
.setName("System")
.setValue(new UriType(theParam.getSystem()));
retVal
.addPart()
.setName("Units")
.setValue(new CodeType(theParam.getUnits()));
return retVal;
}
}
private static class ResourceLinkPopulator extends BaseParamPopulator<ResourceLink> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceLink theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
if (theParam.getTargetResourceId() != null) {
retVal
.addPart()
.setName("TargetId")
.setValue(new StringType(theParam.getTargetResourceType() + "/" + theParam.getTargetResourceId()));
} else if (theParam.getTargetResourceUrl() != null) {
retVal
.addPart()
.setName("TargetUrl")
.setValue(new UrlType(theParam.getTargetResourceUrl()));
}
if (theParam.getTargetResourceVersion() != null) {
retVal
.addPart()
.setName("TargetVersion")
.setValue(new StringType(theParam.getTargetResourceVersion().toString()));
}
return retVal;
}
@Override
protected String toPartName(ResourceLink theParam) {
return theParam.getSourcePath();
}
}
private static class SearchParamPresentParamPopulator extends BaseParamPopulator<SearchParamPresentEntity> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, SearchParamPresentEntity theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Missing")
.setValue(new BooleanType(!theParam.isPresent()));
return retVal;
}
@Override
protected String toPartName(SearchParamPresentEntity theParam) {
return theParam.getParamName();
}
}
private static class StringParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamString> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamString theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("ValueNormalized")
.setValue(new StringType(theParam.getValueNormalized()));
retVal
.addPart()
.setName("ValueExact")
.setValue(new StringType(theParam.getValueExact()));
return retVal;
}
}
private static class TokenParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamToken> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamToken theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
if (isNotBlank(theParam.getSystem())) {
retVal
.addPart()
.setName("System")
.setValue(new StringType(theParam.getSystem()));
}
if (isNotBlank(theParam.getValue())) {
retVal
.addPart()
.setName("Value")
.setValue(new StringType(theParam.getValue()));
}
return retVal;
}
}
private static class UriParamPopulator extends BaseIndexParamPopulator<ResourceIndexedSearchParamUri> {
@Nonnull
@Override
public Parameters.ParametersParameterComponent addIndexValue(ActionEnum theAction, Parameters.ParametersParameterComponent theParent, ResourceIndexedSearchParamUri theParam, String theParamTypeName) {
Parameters.ParametersParameterComponent retVal = super.addIndexValue(theAction, theParent, theParam, theParamTypeName);
retVal
.addPart()
.setName("Value")
.setValue(new UriType(theParam.getUri()));
return retVal;
}
}
/**
* Links loaded from the database have a PID link to their target, but the ones
* extracted from the resource in memory won't have the PID. So this method
* strips the PIDs so that the generated hashCodes and equals comparisons
* will actually be equal.
*/
private static List<ResourceLink> normalizeLinks(Collection<ResourceLink> theLinks) {
return theLinks
.stream()
.map(ResourceLink::cloneWithoutTargetPid)
.collect(Collectors.toList());
}
private static <T> void addParams(Parameters theParameters, String theSectionName, String theTypeName, Collection<T> theExistingParams, Collection<T> theNewParams, BaseParamPopulator<T> thePopulator, boolean theShowAction) {
List<T> addedParams = subtract(theNewParams, theExistingParams);
thePopulator.sort(addedParams);
for (T next : addedParams) {
Parameters.ParametersParameterComponent parent = getOrCreateSection(theParameters, theSectionName);
if (theShowAction) {
thePopulator.addIndexValue(ActionEnum.ADD, parent, next, theTypeName);
} else {
thePopulator.addIndexValue(ActionEnum.UNKNOWN, parent, next, theTypeName);
}
}
List<T> removedParams = subtract(theExistingParams, theNewParams);
addedParams.sort(comparing(thePopulator::toPartName));
for (T next : removedParams) {
Parameters.ParametersParameterComponent parent = getOrCreateSection(theParameters, theSectionName);
thePopulator.addIndexValue(ActionEnum.REMOVE, parent, next, theTypeName);
}
List<T> unchangedParams = new ArrayList<>(intersection(theNewParams, theExistingParams));
addedParams.sort(comparing(thePopulator::toPartName));
for (T next : unchangedParams) {
Parameters.ParametersParameterComponent parent = getOrCreateSection(theParameters, theSectionName);
thePopulator.addIndexValue(ActionEnum.NO_CHANGE, parent, next, theTypeName);
}
}
private static <T extends BaseResourceIndexedSearchParam> void addParamsNonMissing(Parameters theParameters, String theSectionName, String theTypeName, Collection<T> theExistingParams, Collection<T> theNewParams, BaseParamPopulator<T> thePopulator, boolean theShowAction) {
Collection<T> existingParams = filterWantMissing(theExistingParams, false);
Collection<T> newParams = filterWantMissing(theNewParams, false);
addParams(theParameters, theSectionName, theTypeName, existingParams, newParams, thePopulator, theShowAction);
}
private static <T extends BaseResourceIndexedSearchParam> void addParamsMissing(Parameters theParameters, String theTypeName, Collection<T> theExistingParams, Collection<T> theNewParams, BaseParamPopulator<T> thePopulator, boolean theShowAction) {
Collection<T> existingParams = filterWantMissing(theExistingParams, true);
Collection<T> newParams = filterWantMissing(theNewParams, true);
addParams(theParameters, "MissingIndexes", theTypeName, existingParams, newParams, thePopulator, theShowAction);
}
private static <T extends BaseResourceIndexedSearchParam> Collection<T> filterWantMissing(Collection<T> theNewParams, boolean theWantMissing) {
return theNewParams
.stream()
.filter(t -> t.isMissing() == theWantMissing)
.collect(Collectors.toList());
}
@Nonnull
private static Parameters.ParametersParameterComponent getOrCreateSection(Parameters theParameters, String theSectionName) {
Parameters.ParametersParameterComponent parent = theParameters.getParameter(theSectionName);
if (parent == null) {
parent = theParameters.addParameter();
parent.setName(theSectionName);
}
return parent;
}
}

View File

@ -20,12 +20,13 @@
package ca.uhn.fhir.jpa.sp;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
import java.util.Map;
import java.util.Collection;
public interface ISearchParamPresenceSvc {
AddRemoveCount updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence);
AddRemoveCount updatePresence(ResourceTable theResource, Collection<SearchParamPresentEntity> thePresenceEntities);
}

View File

@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.sp;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.jpa.util.AddRemoveCount;
@ -42,8 +41,6 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
@Autowired
private ISearchParamPresentDao mySearchParamPresentDao;
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired
private JpaStorageSettings myStorageSettings;
@ -53,14 +50,12 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
}
@Override
public AddRemoveCount updatePresence(ResourceTable theResource, Map<String, Boolean> theParamNameToPresence) {
public AddRemoveCount updatePresence(ResourceTable theResource, Collection<SearchParamPresentEntity> thePresenceEntities) {
AddRemoveCount retVal = new AddRemoveCount();
if (myStorageSettings.getIndexMissingFields() == JpaStorageSettings.IndexEnabledEnum.DISABLED) {
return retVal;
}
Map<String, Boolean> presenceMap = new HashMap<>(theParamNameToPresence);
// Find existing entries
Collection<SearchParamPresentEntity> existing = theResource.getSearchParamPresents();
Map<Long, SearchParamPresentEntity> existingHashToPresence = new HashMap<>();
@ -70,18 +65,8 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
// Find newly wanted set of entries
Map<Long, SearchParamPresentEntity> newHashToPresence = new HashMap<>();
for (Entry<String, Boolean> next : presenceMap.entrySet()) {
String paramName = next.getKey();
SearchParamPresentEntity present = new SearchParamPresentEntity();
present.setPartitionSettings(myPartitionSettings);
present.setResource(theResource);
present.setParamName(paramName);
present.setPresent(next.getValue());
present.setPartitionId(theResource.getPartitionId());
present.calculateHashes();
newHashToPresence.put(present.getHashPresence(), present);
for (SearchParamPresentEntity next : thePresenceEntities) {
newHashToPresence.put(next.getHashPresence(), next);
}
// Delete any that should be deleted

View File

@ -0,0 +1,31 @@
<div xmlns:th="http://www.thymeleaf.org">
<!--/*
Each index will be in a Patameters.part instance that looks like the following:
"part": [ {
"name": "uri",
"part": [ {
"name": "Action",
"valueCode": "ADD"
}, {
"name": "Type",
"valueCode": "Uri"
}, {
"name": "Value",
"valueUri": "http://some-codesystem"
}
]
This fragment takes the containing part (via thePart) and the name of the
child part (via theName) and outputs the primitive value of the child part's
value object. So, given the example above, for name="Type" this would
output "Uri".
*/-->
<th:block th:fragment="renderPart (thePart, theName)">
<th:block th:each="childPart : ${thePart.getPart()}">
<th:block th:if="${childPart.name == theName}" th:text="${childPart.value.getValueAsString()}"/>
</th:block>
</th:block>
</div>

View File

@ -0,0 +1,7 @@
parameters.resourceType = Parameters
parameters.style = thymeleaf
parameters.narrative = classpath:ca/uhn/fhir/jpa/search/reindex/reindex-outcome.html
parameters-fragments.fragmentName=ParametersFragments
parameters-fragments.narrative=classpath:ca/uhn/fhir/jpa/search/reindex/fragments.html

View File

@ -0,0 +1,219 @@
<div xmlns:th="http://www.thymeleaf.org">
<!--/* Number Indexes */-->
<div th:if="${resource.hasParameter('NumberIndexes')}" id="NumberIndexes">
<h1>Number Indexes</h1>
<table id="NumberIndexesTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('NumberIndexes').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Value')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* Quantity Indexes */-->
<div th:if="${resource.hasParameter('QuantityIndexes')}" id="QuantityIndexes">
<h1>Quantity Indexes</h1>
<table id="QuantityIndexesTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>Value</th>
<th>System</th>
<th>Units</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('QuantityIndexes').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Value')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'System')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Units')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* String Indexes */-->
<div th:if="${resource.hasParameter('StringIndexes')}" id="StringIndexes">
<h1>String Indexes</h1>
<table id="StringIndexesTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>ValueNormalized</th>
<th>ValueExact</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('StringIndexes').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'ValueNormalized')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'ValueExact')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* Token Indexes */-->
<div th:if="${resource.hasParameter('TokenIndexes')}" id="TokenIndexes">
<h1>Token Indexes</h1>
<table id="TokenIndexesTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>System</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('TokenIndexes').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'System')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Value')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* URI Indexes */-->
<div th:if="${resource.hasParameter('UriIndexes')}" id="UriIndexes">
<h1>URI Indexes</h1>
<table id="UriIndexesTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('UriIndexes').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Value')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* Resource Links */-->
<div th:if="${resource.hasParameter('ResourceLinks')}" id="ResourceLinks">
<h1>Resource Links</h1>
<table id="ResourceLinksTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>Target ID (Local)</th>
<th>Target Version (Local)</th>
<th>Target URL (Non-Local)</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('ResourceLinks').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'TargetId')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'TargetVersion')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'TargetUrl')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* Missing Indexes */-->
<div th:if="${resource.hasParameter('MissingIndexes')}" id="MissingIndexes">
<h1>Missing Indexes (:missing)</h1>
<table id="MissingIndexesTable">
<thead>
<tr>
<th>Name</th>
<th>Action</th>
<th>Type</th>
<th>Missing</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('MissingIndexes').part}">
<td th:text="${part.name}"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Missing')"></td>
</tr>
</tbody>
</table>
</div>
<!--/* Combo Non-Unique Indexes */-->
<div th:if="${resource.hasParameter('NonUniqueIndexes')}" id="NonUniqueIndexes">
<h1>Combo Indexes (Non-Unique)</h1>
<table id="NonUniqueIndexesTable">
<thead>
<tr>
<th>Action</th>
<th>Type</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('NonUniqueIndexes').part}">
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:text="${part.name}"></td>
</tr>
</tbody>
</table>
</div>
<!--/* Combo Unique Indexes */-->
<div th:if="${resource.hasParameter('UniqueIndexes')}" id="UniqueIndexes">
<h1>Combo Indexes (Unique)</h1>
<table id="UniqueIndexesTable">
<thead>
<tr>
<th>Action</th>
<th>Type</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr th:each="part : ${resource.getParameter('UniqueIndexes').part}">
<td th:insert="ParametersFragments :: renderPart(${part}, 'Action')"></td>
<td th:insert="ParametersFragments :: renderPart(${part}, 'Type')"></td>
<td th:text="${part.name}"></td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,84 @@
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.search.reindex.IInstanceReindexService;
import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderExtension;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Set;
import static ca.uhn.fhir.rest.server.provider.ProviderConstants.OPERATION_REINDEX_DRYRUN;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
public class InstanceReindexProviderTest {
@Mock
private IInstanceReindexService myDryRunService;
@RegisterExtension
@Order(0)
private RestfulServerExtension myServer = new RestfulServerExtension(FhirVersionEnum.R4)
.withServer(server -> server.registerProvider(new InstanceReindexProvider(myDryRunService)));
@RegisterExtension
@Order(1)
private HashMapResourceProviderExtension<Patient> myPatientProvider = new HashMapResourceProviderExtension<>(myServer, Patient.class);
@Captor
private ArgumentCaptor<Set<String>> myCodeCaptor;
@Test
public void testDryRun() {
Parameters parameters = new Parameters();
parameters.addParameter("foo", "bar");
when(myDryRunService.reindexDryRun(any(), any(), any())).thenReturn(parameters);
Parameters outcome = myServer
.getFhirClient()
.operation()
.onInstance(new IdType("Patient/123"))
.named(OPERATION_REINDEX_DRYRUN)
.withNoParameters(Parameters.class)
.useHttpGet()
.execute();
assertEquals("foo", outcome.getParameter().get(0).getName());
}
@Test
public void testDryRun_WithCodes() {
Parameters parameters = new Parameters();
parameters.addParameter("foo", "bar");
when(myDryRunService.reindexDryRun(any(), any(), any())).thenReturn(parameters);
Parameters outcome = myServer
.getFhirClient()
.operation()
.onInstance(new IdType("Patient/123"))
.named(OPERATION_REINDEX_DRYRUN)
.withParameter(Parameters.class, "code", new CodeType("blah"))
.useHttpGet()
.execute();
assertEquals("foo", outcome.getParameter().get(0).getName());
verify(myDryRunService, times(1)).reindexDryRun(any(), any(), myCodeCaptor.capture());
assertThat(myCodeCaptor.getValue(), contains("blah"));
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -110,15 +110,17 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
ResourceIndexedComboTokenNonUnique that = (ResourceIndexedComboTokenNonUnique) theO;
return new EqualsBuilder()
.append(myResource, that.myResource)
.append(myHashComplete, that.myHashComplete)
.isEquals();
EqualsBuilder b = new EqualsBuilder();
b.append(myIndexString, that.myIndexString);
return b.isEquals();
}
@Override
public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
throw new IllegalStateException(Msg.code(1528));
ResourceIndexedComboTokenNonUnique source = (ResourceIndexedComboTokenNonUnique) theSource;
myPartitionSettings = source.myPartitionSettings;
myHashComplete = source.myHashComplete;
myIndexString = source.myIndexString;
}
@Override
@ -151,8 +153,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myResource)
.append(myHashComplete)
.append(myIndexString)
.toHashCode();
}
@ -164,6 +165,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
myPartitionSettings = thePartitionSettings;
}
@Override
public ResourceTable getResource() {
return myResource;
}

View File

@ -128,11 +128,18 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
b.append(getResourceType(), obj.getResourceType());
b.append(getParamName(), obj.getParamName());
b.append(getHashIdentity(), obj.getHashIdentity());
b.append(getValue(), obj.getValue());
b.append(normalizeForEqualityComparison(getValue()), normalizeForEqualityComparison(obj.getValue()));
b.append(isMissing(), obj.isMissing());
return b.isEquals();
}
private Double normalizeForEqualityComparison(BigDecimal theValue) {
if (theValue == null) {
return null;
}
return theValue.doubleValue();
}
public void setHashIdentity(Long theHashIdentity) {
myHashIdentity = theHashIdentity;
}
@ -161,7 +168,7 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
b.append(getResourceType());
b.append(getParamName());
b.append(getHashIdentity());
b.append(getValue());
b.append(normalizeForEqualityComparison(getValue()));
b.append(isMissing());
return b.toHashCode();
}

View File

@ -131,6 +131,18 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
calculateHashes();
}
/**
* Constructor
*/
public ResourceIndexedSearchParamToken(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, boolean theMissing) {
super();
setPartitionSettings(thePartitionSettings);
setResourceType(theResourceType);
setParamName(theParamName);
setMissing(theMissing);
calculateHashes();
}
@Override
public <T extends BaseResourceIndex> void copyMutableValuesFrom(T theSource) {
super.copyMutableValuesFrom(theSource);

View File

@ -294,6 +294,28 @@ public class ResourceLink extends BaseResourceIndex {
return myTargetResource;
}
/**
* Creates a clone of this resourcelink which doesn't contain the internal PID
* of the target resource.
*/
public ResourceLink cloneWithoutTargetPid() {
ResourceLink retVal = new ResourceLink();
retVal.mySourceResource = mySourceResource;
retVal.mySourceResourcePid = mySourceResource.getId();
retVal.mySourceResourceType = mySourceResource.getResourceType();
retVal.mySourcePath = mySourcePath;
retVal.myUpdated = myUpdated;
retVal.myTargetResourceType = myTargetResourceType;
if (myTargetResourceId != null) {
retVal.myTargetResourceId = myTargetResourceId;
} else if (myTargetResource != null) {
retVal.myTargetResourceId = myTargetResource.getIdDt().getIdPart();
}
retVal.myTargetResourceUrl = myTargetResourceUrl;
retVal.myTargetResourceVersion = myTargetResourceVersion;
return retVal;
}
public static ResourceLink forAbsoluteReference(String theSourcePath, ResourceTable theSourceResource, IIdType theTargetResourceUrl, Date theUpdated) {
ResourceLink retVal = new ResourceLink();
retVal.setSourcePath(theSourcePath);

View File

@ -598,7 +598,7 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
}
public void setParamsComboTokensNonUniquePresent(boolean theParamsComboTokensNonUniquePresent) {
myParamsComboStringUniquePresent = theParamsComboTokensNonUniquePresent;
myParamsComboTokensNonUniquePresent = theParamsComboTokensNonUniquePresent;
}
public boolean isParamsCoordsPopulated() {

View File

@ -21,6 +21,9 @@ package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -63,6 +66,14 @@ public class SearchParamPresentEntity extends BasePartitionable implements Seria
super();
}
/**
* Constructor
*/
public SearchParamPresentEntity(String theParamName, boolean thePresent) {
myParamName = theParamName;
myPresent = thePresent;
}
@SuppressWarnings("unused")
@PrePersist
public void calculateHashes() {
@ -75,6 +86,7 @@ public class SearchParamPresentEntity extends BasePartitionable implements Seria
}
public Long getHashPresence() {
Validate.notNull(myHashPresence);
return myHashPresence;
}
@ -106,6 +118,26 @@ public class SearchParamPresentEntity extends BasePartitionable implements Seria
myPresent = thePresent;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
SearchParamPresentEntity that = (SearchParamPresentEntity) theO;
EqualsBuilder b = new EqualsBuilder();
b.append(getHashPresence(), that.getHashPresence());
return b.isEquals();
}
@Override
public int hashCode() {
HashCodeBuilder b = new HashCodeBuilder(17, 37);
b.append(getHashPresence());
return b.toHashCode();
}
@Override
public String toString() {
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);

View File

@ -116,6 +116,8 @@ public class StorageSettings {
private boolean myAllowMdmExpansion = false;
private boolean myAutoSupportDefaultSearchParams = true;
private boolean myIndexIdentifierOfType = false;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/**
* Since 6.4.0
*/
@ -259,6 +261,53 @@ public class StorageSettings {
myEnableInMemorySubscriptionMatching = theEnableInMemorySubscriptionMatching;
}
/**
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
* the server will not create search indexes for search parameters with no values in resources.
* <p>
* Disabling this feature means that the <code>:missing</code> search modifier will not be
* supported on the server, but also means that storage and indexing (i.e. writes to the
* database) may be much faster on servers which have lots of search parameters and need
* to write quickly.
* </p>
* <p>
* This feature may be enabled on servers where supporting the use of the :missing parameter is
* of higher importance than raw write performance
* </p>
*/
public IndexEnabledEnum getIndexMissingFields() {
return myIndexMissingFieldsEnabled;
}
/**
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
* the server will not create search indexes for search parameters with no values in resources.
* <p>
* Disabling this feature means that the <code>:missing</code> search modifier will not be
* supported on the server, but also means that storage and indexing (i.e. writes to the
* database) may be much faster on servers which have lots of search parameters and need
* to write quickly.
* </p>
* <p>
* This feature may be enabled on servers where supporting the use of the :missing parameter is
* of higher importance than raw write performance
* </p>
* <p>
* Note that this setting also has an impact on sorting (i.e. using the
* <code>_sort</code> parameter on searches): If the server is configured
* to not index missing field.
* </p>
* <p>
* The following index may need to be added into the indexed tables such as <code>HFJ_SPIDX_TOKEN</code>
* to improve the search performance while <code>:missing</code> is enabled.
* <code>RES_TYPE, SP_NAME, SP_MISSING</code>
* </p>
*/
public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) {
Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null");
myIndexMissingFieldsEnabled = theIndexMissingFields;
}
/**
* If this is enabled (disabled by default), Mass Ingestion Mode is enabled. In this mode, a number of
* runtime checks are disabled. This mode is designed for rapid backloading of data while the system is not
@ -1220,4 +1269,9 @@ public class StorageSettings {
}
public enum IndexEnabledEnum {
ENABLED,
DISABLED
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -269,9 +269,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamComposite> extractor = createCompositeExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.COMPOSITE, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.COMPOSITE, false, theSearchParamFilter);
}
private IExtractor<ResourceIndexedSearchParamComposite> createCompositeExtractor(IBaseResource theResource) {
@ -557,9 +557,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false, theSearchParamFilter);
}
@Override
@ -592,10 +592,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
String resourceTypeName = toRootTypeName(theResource);
IExtractor<BaseResourceIndexedSearchParam> extractor = createSpecialExtractor(resourceTypeName);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false, theSearchParamFilter);
}
private IExtractor<BaseResourceIndexedSearchParam> createSpecialExtractor(String theResourceTypeName) {
@ -612,9 +612,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamUri> extractor = createUriExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false, theSearchParamFilter);
}
private IExtractor<ResourceIndexedSearchParamUri> createUriExtractor(IBaseResource theResource) {
@ -637,9 +637,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamDate> extractor = createDateExtractor(theResource);
return extractSearchParams(theResource, extractor, DATE, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, DATE, false, theSearchParamFilter);
}
private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) {
@ -653,9 +653,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamNumber> extractor = createNumberExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false, theSearchParamFilter);
}
private IExtractor<ResourceIndexedSearchParamNumber> createNumberExtractor(IBaseResource theResource) {
@ -688,16 +688,16 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamQuantity> extractor = createQuantityUnnormalizedExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter);
}
@Override
public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false, theSearchParamFilter);
}
@Nonnull
@ -767,10 +767,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
@Override
public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource, Set<String> theParamsToIndex) {
public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource, ISearchParamFilter theSearchParamFilter) {
IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false, theParamsToIndex);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false, theSearchParamFilter);
}
private IExtractor<ResourceIndexedSearchParamString> createStringExtractor(IBaseResource theResource) {
@ -1321,22 +1321,22 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
* This is the only way you could actually specify a FhirPath expression for those
* prior to 6.2.0 so this isn't a breaking change.
*/
<T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences, Set<String> theParamsToIndex) {
<T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences, ISearchParamFilter theSearchParamFilter) {
SearchParamSet<T> retVal = new SearchParamSet<>();
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
cleanUpContainedResourceReferences(theResource, theSearchParamType, searchParams);
int preFilterSize = searchParams.size();
Collection<RuntimeSearchParam> filteredSearchParams = theSearchParamFilter.filterSearchParams(searchParams);
assert filteredSearchParams.size() == preFilterSize || searchParams != filteredSearchParams;
for (RuntimeSearchParam nextSpDef : searchParams) {
cleanUpContainedResourceReferences(theResource, theSearchParamType, filteredSearchParams);
for (RuntimeSearchParam nextSpDef : filteredSearchParams) {
if (nextSpDef.getParamType() != theSearchParamType) {
continue;
}
if (!theParamsToIndex.equals(ISearchParamExtractor.ALL_PARAMS) && !theParamsToIndex.contains(nextSpDef.getName())) {
continue;
}
// See the method javadoc for an explanation of this
if (startsWith(nextSpDef.getPath(), "Resource.")) {
continue;

View File

@ -21,78 +21,77 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public interface ISearchParamExtractor {
/**
* Constant for the {@literal theParamsToIndex} parameters on this interface
* Constant for the {@literal theSearchParamFilter} parameters on this interface
* indicating that all search parameters should be indexed.
*/
Set<String> ALL_PARAMS = Set.of("*");
ISearchParamFilter ALL_PARAMS = t -> t;
/**
* Constant for the {@literal theSearchParamFilter} parameters on this interface
* indicating that no search parameters should be indexed.
*/
ISearchParamFilter NO_PARAMS = t -> Collections.emptyList();
default SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource) {
return extractSearchParamDates(theResource, ALL_PARAMS);
}
SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
default SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource) {
return extractSearchParamNumber(theResource, ALL_PARAMS);
}
SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
default SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource) {
return extractSearchParamQuantity(theResource, ALL_PARAMS);
}
SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
default SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
return extractSearchParamQuantityNormalized(theResource, ALL_PARAMS);
}
SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
default SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
return extractSearchParamStrings(theResource, ALL_PARAMS);
}
SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
default SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource) {
return extractSearchParamComposites(theResource, ALL_PARAMS);
}
SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
default SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) {
return extractSearchParamTokens(theResource, ALL_PARAMS);
}
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
SearchParamSet<ResourceIndexedComboStringUnique> extractSearchParamComboUnique(String theResourceType, ResourceIndexedSearchParams theParams);
SearchParamSet<ResourceIndexedComboTokenNonUnique> extractSearchParamComboNonUnique(String theResourceType, ResourceIndexedSearchParams theParams);
@ -102,7 +101,7 @@ public interface ISearchParamExtractor {
}
SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource, Set<String> theParamsToIndex);
SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource, ISearchParamFilter theSearchParamFilter);
SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences);
@ -130,6 +129,20 @@ public interface ISearchParamExtractor {
String getDisplayTextFromCodeableConcept(IBase theValue);
@FunctionalInterface
interface ISearchParamFilter {
/**
* Given the list of search parameters for indexing, an implementation of this
* interface may selectively remove any that it wants to remove (or can add if desired).
* <p>
* Implementations must not modify the list that is passed in. If changes are
* desired, a new list must be created and returned.
*/
Collection<RuntimeSearchParam> filterSearchParams(Collection<RuntimeSearchParam> theSearchParams);
}
class SearchParamSet<T> extends HashSet<T> {
private List<String> myWarnings;

View File

@ -59,6 +59,7 @@ public final class ResourceIndexedSearchParams {
final public Collection<ResourceIndexedComboTokenNonUnique> myComboTokenNonUnique = new HashSet<>();
final public Collection<ResourceLink> myLinks = new HashSet<>();
final public Set<String> myPopulatedResourceLinkParameters = new HashSet<>();
final public Collection<SearchParamPresentEntity> mySearchParamPresentEntities = new HashSet<>();
final public Collection<ResourceIndexedSearchParamComposite> myCompositeParams = new HashSet<>();
public ResourceIndexedSearchParams() {
@ -116,6 +117,7 @@ public final class ResourceIndexedSearchParams {
theEntity.setParamsUriPopulated(myUriParams.isEmpty() == false);
theEntity.setParamsCoordsPopulated(myCoordsParams.isEmpty() == false);
theEntity.setParamsComboStringUniquePresent(myComboStringUniques.isEmpty() == false);
theEntity.setParamsComboTokensNonUniquePresent(myComboTokenNonUnique.isEmpty() == false);
theEntity.setHasLinks(myLinks.isEmpty() == false);
}

View File

@ -47,9 +47,9 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
@ -57,6 +57,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.fhir.util.FhirTerser;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
@ -70,10 +71,13 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -94,8 +98,6 @@ public class SearchParamExtractorService {
private PartitionSettings myPartitionSettings;
@Autowired(required = false)
private IResourceLinkResolver myResourceLinkResolver;
@Autowired
private IRequestPartitionHelperSvc myPartitionHelperSvc;
@VisibleForTesting
public void setSearchParamExtractor(ISearchParamExtractor theSearchParamExtractor) {
@ -104,7 +106,7 @@ public class SearchParamExtractorService {
public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference) {
extractFromResource(theRequestPartitionId, theRequestDetails, theParams, new ResourceIndexedSearchParams(), theEntity, theResource, theTransactionDetails, theFailOnInvalidReference);
extractFromResource(theRequestPartitionId, theRequestDetails, theParams, new ResourceIndexedSearchParams(), theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, ISearchParamExtractor.ALL_PARAMS);
}
/**
@ -113,11 +115,11 @@ public class SearchParamExtractorService {
* a given resource type, it extracts the associated indexes and populates
* {@literal theParams}.
*/
public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theNewParams, ResourceIndexedSearchParams theExistingParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference) {
public void extractFromResource(RequestPartitionId theRequestPartitionId, RequestDetails theRequestDetails, ResourceIndexedSearchParams theNewParams, ResourceIndexedSearchParams theExistingParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, @Nonnull ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
// All search parameter types except Reference
ResourceIndexedSearchParams normalParams = new ResourceIndexedSearchParams();
extractSearchIndexParameters(theRequestDetails, normalParams, theResource, ISearchParamExtractor.ALL_PARAMS);
extractSearchIndexParameters(theRequestDetails, normalParams, theResource, theSearchParamFilter);
mergeParams(normalParams, theNewParams);
boolean indexOnContainedResources = myStorageSettings.isIndexOnContainedResources();
@ -146,9 +148,51 @@ public class SearchParamExtractorService {
extractResourceLinksForContainedResources(theRequestPartitionId, theNewParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails);
}
// Missing (:missing) Indexes - These are indexes to satisfy the :missing
// modifier
if (myStorageSettings.getIndexMissingFields() == StorageSettings.IndexEnabledEnum.ENABLED) {
// References
Map<String, Boolean> presenceMap = getReferenceSearchParamPresenceMap(theEntity, theNewParams);
presenceMap.forEach((key, value) -> {
SearchParamPresentEntity present = new SearchParamPresentEntity();
present.setPartitionSettings(myPartitionSettings);
present.setResource(theEntity);
present.setParamName(key);
present.setPresent(value);
present.setPartitionId(theEntity.getPartitionId());
present.calculateHashes();
theNewParams.mySearchParamPresentEntities.add(present);
});
// Everything else
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType());
theNewParams.findMissingSearchParams(myPartitionSettings, myStorageSettings, theEntity, activeSearchParams);
}
extractSearchParamComboUnique(theEntity, theNewParams);
extractSearchParamComboNonUnique(theEntity, theNewParams);
theNewParams.setUpdatedTime(theTransactionDetails.getTransactionDate());
}
@Nonnull
private Map<String, Boolean> getReferenceSearchParamPresenceMap(ResourceTable entity, ResourceIndexedSearchParams newParams) {
Map<String, Boolean> retval = new HashMap<>();
for (String nextKey : newParams.getPopulatedResourceLinkParameters()) {
retval.put(nextKey, Boolean.TRUE);
}
ResourceSearchParams activeSearchParams = mySearchParamRegistry.getActiveSearchParams(entity.getResourceType());
activeSearchParams
.getReferenceSearchParamNames()
.forEach(key -> retval.putIfAbsent(key, Boolean.FALSE));
return retval;
}
@VisibleForTesting
public void setStorageSettings(StorageSettings theStorageSettings) {
myStorageSettings = theStorageSettings;
@ -169,8 +213,9 @@ public class SearchParamExtractorService {
// Extract search parameters
IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() {
@Nonnull
@Override
public Set<String> getChainedSearchParametersToIndexForPath(PathAndRef thePathAndRef) {
public ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef) {
// Currently for contained resources we always index all search parameters
// on all contained resources. A potential nice future optimization would
// be to make this configurable, perhaps with an optional extension you could
@ -179,7 +224,7 @@ public class SearchParamExtractorService {
}
@Override
public IBaseResource fetchResourceAtPath(PathAndRef thePathAndRef) {
public IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef) {
return findContainedResource(containedResources, thePathAndRef.getRef());
}
};
@ -197,15 +242,23 @@ public class SearchParamExtractorService {
private void extractSearchIndexParametersForUpliftedRefchains(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, ResourceTable theEntity, RequestPartitionId theRequestPartitionId, TransactionDetails theTransactionDetails, ISearchParamExtractor.SearchParamSet<PathAndRef> theIndexedReferences) {
IChainedSearchParameterExtractionStrategy strategy = new IChainedSearchParameterExtractionStrategy() {
@Nonnull
@Override
public Set<String> getChainedSearchParametersToIndexForPath(PathAndRef thePathAndRef) {
public ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef) {
String searchParamName = thePathAndRef.getSearchParamName();
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theEntity.getResourceType(), searchParamName);
return searchParam.getUpliftRefchainCodes();
Set<String> upliftRefchainCodes = searchParam.getUpliftRefchainCodes();
if (upliftRefchainCodes.isEmpty()) {
return ISearchParamExtractor.NO_PARAMS;
}
return sp -> sp
.stream()
.filter(t -> upliftRefchainCodes.contains(t.getName()))
.collect(Collectors.toList());
}
@Override
public IBaseResource fetchResourceAtPath(PathAndRef thePathAndRef) {
public IBaseResource fetchResourceAtPath(@Nonnull PathAndRef thePathAndRef) {
// The PathAndRef will contain a resource if the SP path was inside a Bundle
// and pointed to a resource (e.g. Bundle.entry.resource) as opposed to
// pointing to a reference (e.g. Observation.subject)
@ -264,8 +317,8 @@ public class SearchParamExtractorService {
continue;
// 3.1.2 check if this ref actually applies here
Set<String> searchParamsToIndex = theTargetIndexingStrategy.getChainedSearchParametersToIndexForPath(nextPathAndRef);
if (searchParamsToIndex.isEmpty()) {
ISearchParamExtractor.ISearchParamFilter searchParamsToIndex = theTargetIndexingStrategy.getSearchParamFilter(nextPathAndRef);
if (searchParamsToIndex == ISearchParamExtractor.NO_PARAMS) {
continue;
}
@ -332,42 +385,42 @@ public class SearchParamExtractorService {
theTargetParams.myCompositeParams.addAll(theSrcParams.myCompositeParams);
}
void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, Set<String> theParamsToIndex) {
void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, @Nonnull ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
// Strings
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> strings = extractSearchParamStrings(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> strings = extractSearchParamStrings(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, strings);
theParams.myStringParams.addAll(strings);
// Numbers
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> numbers = extractSearchParamNumber(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> numbers = extractSearchParamNumber(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, numbers);
theParams.myNumberParams.addAll(numbers);
// Quantities
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> quantities = extractSearchParamQuantity(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> quantities = extractSearchParamQuantity(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities);
theParams.myQuantityParams.addAll(quantities);
if (myStorageSettings.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED) || myStorageSettings.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) {
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> quantitiesNormalized = extractSearchParamQuantityNormalized(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> quantitiesNormalized = extractSearchParamQuantityNormalized(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized);
theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized);
}
// Dates
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> dates = extractSearchParamDates(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> dates = extractSearchParamDates(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, dates);
theParams.myDateParams.addAll(dates);
// URIs
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> uris = extractSearchParamUri(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> uris = extractSearchParamUri(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, uris);
theParams.myUriParams.addAll(uris);
// Tokens (can result in both Token and String, as we index the display name for
// the types: Coding, CodeableConcept)
ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> tokens = extractSearchParamTokens(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> tokens = extractSearchParamTokens(theResource, theSearchParamFilter);
for (BaseResourceIndexedSearchParam next : tokens) {
if (next instanceof ResourceIndexedSearchParamToken) {
theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next);
@ -381,13 +434,13 @@ public class SearchParamExtractorService {
// Composites
// dst2 composites use stuff like value[x] , and we don't support them.
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamComposite> composites = extractSearchParamComposites(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamComposite> composites = extractSearchParamComposites(theResource, theSearchParamFilter);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, composites);
theParams.myCompositeParams.addAll(composites);
}
// Specials
ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> specials = extractSearchParamSpecial(theResource, theParamsToIndex);
ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> specials = extractSearchParamSpecial(theResource, theSearchParamFilter);
for (BaseResourceIndexedSearchParam next : specials) {
if (next instanceof ResourceIndexedSearchParamCoords) {
theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next);
@ -543,7 +596,7 @@ public class SearchParamExtractorService {
*/
myResourceLinkResolver.validateTypeOrThrowException(type);
/**
/*
* We need to obtain a resourceLink out of the provided {@literal thePathAndRef}. In the case
* where we are updating a resource that already has resourceLinks (stored in {@literal theExistingParams.getResourceLinks()}),
* let's try to match thePathAndRef to an already existing resourceLink to avoid the
@ -664,9 +717,8 @@ public class SearchParamExtractorService {
}
}
@SuppressWarnings("unchecked")
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, String theSourceResourceName, PathAndRef thePathAndRef, ResourceTable theEntity, Date theUpdateTime, IIdType theNextId, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
assert theRequestPartitionId != null;
JpaPid resolvedResourceId = (JpaPid) theTransactionDetails.getResolvedResourceId(theNextId);
if (resolvedResourceId != null) {
String targetResourceType = theNextId.getResourceType();
@ -742,40 +794,40 @@ public class SearchParamExtractorService {
}
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamDates(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamDates(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamNumber(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamNumber(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamQuantity(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamQuantity(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamQuantityNormalized(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamStrings(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamStrings(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamTokens(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamTokens(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamSpecial(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamSpecial(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamUri(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamUri(theResource, theSearchParamFilter);
}
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource, Set<String> theParamsToIndex) {
return mySearchParamExtractor.extractSearchParamComposites(theResource, theParamsToIndex);
private ISearchParamExtractor.SearchParamSet<ResourceIndexedSearchParamComposite> extractSearchParamComposites(IBaseResource theResource, ISearchParamExtractor.ISearchParamFilter theSearchParamFilter) {
return mySearchParamExtractor.extractSearchParamComposites(theResource, theSearchParamFilter);
}
@VisibleForTesting
@ -812,12 +864,13 @@ public class SearchParamExtractorService {
/**
* Which search parameters should be indexed for the resource target
* at the given path. In other words if thePathAndRef contains
* "Patient/123", then we could return a Set containing "name" and "gender"
* if we only want those two parameters to be indexed for the
* resolved Patient resource with that ID.
* "Patient/123", then we could return a filter that only lets the
* "name" and "gender" search params through if we only want those
* two parameters to be indexed for the resolved Patient resource
* with that ID.
*/
@Nonnull
Set<String> getChainedSearchParametersToIndexForPath(@Nonnull PathAndRef thePathAndRef);
ISearchParamExtractor.ISearchParamFilter getSearchParamFilter(@Nonnull PathAndRef thePathAndRef);
/**
* Actually fetch the resource at the given path, or return

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -28,8 +28,10 @@ import java.util.Comparator;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.in;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -167,6 +169,59 @@ public class FhirResourceDaoR4ComboNonUniqueParamTest extends BaseComboParamsR4T
}
@Test
public void testCreateAndUpdateResource() {
createNamesAndGenderSp();
// Create a resource patching the unique SP
myCaptureQueriesListener.clear();
IIdType id1 = createPatient1();
assertNotNull(id1);
assertEquals(0, myCaptureQueriesListener.countSelectQueries());
assertEquals(12, myCaptureQueriesListener.countInsertQueries());
assertEquals(0, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
assertEquals(1, myCaptureQueriesListener.countCommits());
assertEquals(0, myCaptureQueriesListener.countRollbacks());
runInTransaction(()->{
List<String> indexes = myResourceIndexedComboTokensNonUniqueDao
.findAll()
.stream()
.map(ResourceIndexedComboTokenNonUnique::getIndexString)
.toList();
assertThat(indexes.toString(), indexes, contains("Patient?family=FAMILY1%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1"));
});
/*
* Now update the resource
*/
Patient patient = myPatientDao.read(id1, mySrd);
patient.getNameFirstRep().setFamily("Family2");
myCaptureQueriesListener.clear();
myPatientDao.update(patient, mySrd);
assertEquals(6, myCaptureQueriesListener.countSelectQueries());
assertEquals(1, myCaptureQueriesListener.countInsertQueries());
assertEquals(5, myCaptureQueriesListener.countUpdateQueries());
assertEquals(0, myCaptureQueriesListener.countDeleteQueries());
assertEquals(1, myCaptureQueriesListener.countCommits());
assertEquals(0, myCaptureQueriesListener.countRollbacks());
runInTransaction(()->{
List<String> indexes = myResourceIndexedComboTokensNonUniqueDao
.findAll()
.stream()
.map(ResourceIndexedComboTokenNonUnique::getIndexString)
.toList();
assertThat(indexes.toString(), indexes, contains("Patient?family=FAMILY2%5C%7C&gender=http%3A%2F%2Fhl7.org%2Ffhir%2Fadministrative-gender%7Cmale&given=GIVEN1"));
});
}
@Test
public void testSearchWithExtraParameters() {
createNamesAndGenderSp();

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -28,6 +28,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -453,7 +453,6 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil
purgeHibernateSearch(myEntityManager);
myStorageSettings.setSchedulingDisabled(true);
myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.ENABLED);
}
@BeforeEach

View File

@ -0,0 +1,312 @@
package ca.uhn.fhir.jpa.search.reindex;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.entity.SearchParamPresentEntity;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.test.utilities.HtmlUtil;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlTable;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Tests the narrative generation in {@link InstanceReindexServiceImpl}. This is a separate test
* from {@literal ReindexDryRunServiceImplTest} because this test doesn't need the JPA
* infrastructure.
*/
@SuppressWarnings({"unchecked", "SqlDialectInspection"})
public class InstanceReindexServiceImplNarrativeR5Test {
private static final Logger ourLog = LoggerFactory.getLogger(InstanceReindexServiceImplNarrativeR5Test.class);
private final FhirContext myCtx = FhirContext.forR4Cached();
private final InstanceReindexServiceImpl mySvc = new InstanceReindexServiceImpl();
private final PartitionSettings myPartitionSettings = new PartitionSettings();
private final JpaStorageSettings myStorageSettings = new JpaStorageSettings();
private final ResourceTable myEntity = new ResourceTable();
@Test
public void testIndexComboNonUnique() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myComboTokenNonUnique.add(new ResourceIndexedComboTokenNonUnique(myPartitionSettings, myEntity, "Patient?identifier=123"));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("NonUniqueIndexesTable");
assertEquals("ADD", getBodyCellValue(table, 0, 0));
assertEquals("ComboTokenNonUnique", getBodyCellValue(table, 0, 1));
assertEquals("Patient?identifier=123", getBodyCellValue(table, 0, 2));
}
@Test
public void testIndexComboUnique() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myComboStringUniques.add(new ResourceIndexedComboStringUnique(myEntity, "Patient?identifier=123", new IdType("Parameter/foo")));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("UniqueIndexesTable");
assertEquals("ADD", getBodyCellValue(table, 0, 0));
assertEquals("ComboStringUnique", getBodyCellValue(table, 0, 1));
assertEquals("Patient?identifier=123", getBodyCellValue(table, 0, 2));
}
@Test
public void testIndexMissing() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myTokenParams.add(new ResourceIndexedSearchParamToken(myPartitionSettings, "Observation", "identifier", true));
SearchParamPresentEntity subject = new SearchParamPresentEntity("subject", false);
subject.setResource(new ResourceTable());
subject.setPartitionSettings(myPartitionSettings);
subject.calculateHashes();
newParams.mySearchParamPresentEntities.add(subject);
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("MissingIndexesTable");
assertEquals("identifier", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Token", getBodyCellValue(table, 0, 2));
assertEquals("true", getBodyCellValue(table, 0, 3));
assertEquals("subject", getBodyCellValue(table, 1, 0));
assertEquals("ADD", getBodyCellValue(table, 1, 1));
assertEquals("Reference", getBodyCellValue(table, 1, 2));
assertEquals("true", getBodyCellValue(table, 1, 3));
}
@Test
public void testIndexNumber() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myNumberParams.add(new ResourceIndexedSearchParamNumber(myPartitionSettings, "Immunization", "dose", BigDecimal.ONE));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("NumberIndexesTable");
assertEquals("dose", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Number", getBodyCellValue(table, 0, 2));
assertEquals("1", getBodyCellValue(table, 0, 3));
}
@Test
public void testIndexResourceLink() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myLinks.add(ResourceLink.forLocalReference("Observation.subject", myEntity, "Patient", 123L, "123", new Date(), 555L));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("ResourceLinksTable");
assertEquals("Observation.subject", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Reference", getBodyCellValue(table, 0, 2));
assertEquals("Patient/123", getBodyCellValue(table, 0, 3));
assertEquals("555", getBodyCellValue(table, 0, 4));
}
@Test
public void testIndexResourceLinkLogical() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myLinks.add(ResourceLink.forLogicalReference("Observation.subject", myEntity, "http://foo/base/Patient/456", new Date()));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("ResourceLinksTable");
assertEquals("Observation.subject", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Reference", getBodyCellValue(table, 0, 2));
assertEquals("", getBodyCellValue(table, 0, 3));
assertEquals("", getBodyCellValue(table, 0, 4));
assertEquals("http://foo/base/Patient/456", getBodyCellValue(table, 0, 5));
}
@Test
public void testIndexResourceLinkAbsolute() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myLinks.add(ResourceLink.forAbsoluteReference("Observation.subject", myEntity, new IdType("http://foo/base/Patient/123"), new Date()));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("ResourceLinksTable");
assertEquals("Observation.subject", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Reference", getBodyCellValue(table, 0, 2));
assertEquals("", getBodyCellValue(table, 0, 3));
assertEquals("", getBodyCellValue(table, 0, 4));
assertEquals("http://foo/base/Patient/123", getBodyCellValue(table, 0, 5));
}
@Test
public void testIndexQuantity() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myQuantityParams.add(new ResourceIndexedSearchParamQuantity(myPartitionSettings, "Observation", "value-quantity", BigDecimal.valueOf(123), "http://unitsofmeasure.org", "kg"));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("QuantityIndexesTable");
assertEquals("value-quantity", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Quantity", getBodyCellValue(table, 0, 2));
assertEquals("123", getBodyCellValue(table, 0, 3));
assertEquals("http://unitsofmeasure.org", getBodyCellValue(table, 0, 4));
assertEquals("kg", getBodyCellValue(table, 0, 5));
}
@Test
public void testIndexQuantityNormalized() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myQuantityNormalizedParams.add(new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, "Observation", "value-quantity", 123.0, "http://unitsofmeasure.org", "kg"));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("QuantityIndexesTable");
assertEquals("value-quantity", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("QuantityNormalized", getBodyCellValue(table, 0, 2));
assertEquals("123.0", getBodyCellValue(table, 0, 3));
assertEquals("http://unitsofmeasure.org", getBodyCellValue(table, 0, 4));
assertEquals("kg", getBodyCellValue(table, 0, 5));
}
@Test
public void testIndexString() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myStringParams.add(new ResourceIndexedSearchParamString(myPartitionSettings, myStorageSettings, "Patient", "family", "Simpson", "SIMPSON"));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("StringIndexesTable");
assertEquals("family", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("String", getBodyCellValue(table, 0, 2));
assertEquals("Simpson", getBodyCellValue(table, 0, 3));
assertEquals("SIMPSON", getBodyCellValue(table, 0, 4));
}
@Test
public void testIndexToken() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myTokenParams.add(new ResourceIndexedSearchParamToken(myPartitionSettings, "Observation", "identifier", "http://id-system", "id-value"));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("TokenIndexesTable");
assertEquals("identifier", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Token", getBodyCellValue(table, 0, 2));
assertEquals("http://id-system", getBodyCellValue(table, 0, 3));
assertEquals("id-value", getBodyCellValue(table, 0, 4));
}
@Test
public void testIndexUrl() throws IOException {
// Setup
ResourceIndexedSearchParams newParams = newParams();
newParams.myUriParams.add(new ResourceIndexedSearchParamUri(myPartitionSettings, "CodeSystem", "uri", "http://some-codesystem"));
// Test
Parameters outcome = mySvc.buildIndexResponse(newParams(), newParams, true);
ourLog.info("Output:\n{}", myCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify
HtmlPage narrativeHtml = extractNarrative(outcome);
HtmlTable table = (HtmlTable) narrativeHtml.getElementById("UriIndexesTable");
assertEquals("uri", getBodyCellValue(table, 0, 0));
assertEquals("ADD", getBodyCellValue(table, 0, 1));
assertEquals("Uri", getBodyCellValue(table, 0, 2));
assertEquals("http://some-codesystem", getBodyCellValue(table, 0, 3));
}
@Nonnull
private static HtmlPage extractNarrative(Parameters outcome) throws IOException {
StringType narrative = (StringType) outcome.getParameter().get(0).getValue();
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(narrative.getValueAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
return narrativeHtml;
}
private static String getBodyCellValue(HtmlTable table, int theRow, int theCol) {
return table.getBodies().get(0).getRows().get(theRow).getCell(theCol).asNormalizedText();
}
@Nonnull
private static ResourceIndexedSearchParams newParams() {
return new ResourceIndexedSearchParams();
}
}

View File

@ -0,0 +1,398 @@
package ca.uhn.fhir.jpa.search.reindex;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.util.HapiExtensions;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.Enumerations;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.ResearchStudy;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.StringType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.junit.jupiter.api.Assertions.assertEquals;
@SuppressWarnings({"unchecked", "SqlDialectInspection"})
public class InstanceReindexServiceImplR5Test extends BaseJpaR5Test {
@Autowired
private IInstanceReindexService mySvc;
@Override
@BeforeEach
public void beforeResetConfig() {
super.beforeResetConfig();
myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.DISABLED);
}
@Override
public void afterCleanupDao() {
super.afterCleanupDao();
JpaStorageSettings defaults = new JpaStorageSettings();
myStorageSettings.setIndexMissingFields(defaults.getIndexMissingFields());
myStorageSettings.setNormalizedQuantitySearchLevel(defaults.getNormalizedQuantitySearchLevel());
}
@Test
public void testDryRunMissing() {
myStorageSettings.setIndexMissingFields(JpaStorageSettings.IndexEnabledEnum.ENABLED);
IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
List<Parameters.ParametersParameterComponent> sections = outcome.getParameters("MissingIndexes");
assertEquals(1, sections.size());
List<String> indexInstances = sections
.get(0)
.getPart()
.stream()
.map(t -> t.getName() + " " + getPartValue("Action", t) + " " + getPartValue("Type", t) + " " + getPartValue("Missing", t))
.sorted()
.toList();
assertThat(indexInstances.toString(), indexInstances, contains(
"_profile NO_CHANGE Reference true",
"active NO_CHANGE Token true",
"address NO_CHANGE String true",
"address-city NO_CHANGE String true",
"address-country NO_CHANGE String true",
"address-postalcode NO_CHANGE String true",
"address-state NO_CHANGE String true",
"address-use NO_CHANGE Token true",
"age NO_CHANGE Number true",
"birthOrderBoolean NO_CHANGE Token true",
"birthdate NO_CHANGE Date true",
"death-date NO_CHANGE Date true",
"email NO_CHANGE Token true",
"gender NO_CHANGE Token true",
"general-practitioner NO_CHANGE Reference true",
"identifier NO_CHANGE Token true",
"language NO_CHANGE Token true",
"link NO_CHANGE Reference true",
"mothersMaidenName NO_CHANGE String true",
"organization NO_CHANGE Reference true",
"part-agree NO_CHANGE Reference true",
"phone NO_CHANGE Token true",
"telecom NO_CHANGE Token true"
));
}
@Test
public void testDryRunTypes_ComboNonUniqueSearchParam() {
createNamesAndGenderSp(false);
IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer"));
runInTransaction(this::logAllNonUniqueIndexes);
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "Patient?family=SIMPSON%5C%7C&given=HOMER", "NonUniqueIndexes");
assertEquals("NO_CHANGE", getPartValue("Action", index));
}
@Test
public void testDryRunTypes_ComboUniqueSearchParam() {
createNamesAndGenderSp(true);
IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findIndexes(outcome, "Patient?family=Simpson%5C%7C&given=Homer", 1, "UniqueIndexes").get(0);
assertEquals("NO_CHANGE", getPartValue("Action", index));
}
@Test
public void testDryRunTypes_Number() {
IIdType id = createResource("ResearchStudy", withPrimitiveAttribute("recruitment.targetNumber", "3"));
logAllNumberIndexes();
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, ResearchStudy.SP_RECRUITMENTTARGET, "NumberIndexes");
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("Number", getPartValue("Type", index));
assertEquals("3", getPartValue("Value", index));
}
@Test
public void testDryRunTypes_Quantity() {
myStorageSettings.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
IIdType id = createObservation(withQuantityAtPath("valueQuantity", 1.2, "http://unitsofmeasure.org", "kg"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "value-quantity", "QuantityIndexes");
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("Quantity", getPartValue("Type", index));
assertEquals("http://unitsofmeasure.org", getPartValue("System", index));
assertEquals("kg", getPartValue("Units", index));
assertEquals(1.2d, getPartValueDecimal(index), 0.001d);
}
@Test
public void testDryRunTypes_QuantityNormalized() {
myStorageSettings.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
IIdType id = createObservation(withQuantityAtPath("valueQuantity", 1.2, "http://unitsofmeasure.org", "mg"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index;
index = findIndexes(outcome, "value-quantity", 2, "QuantityIndexes").get(0);
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("Quantity", getPartValue("Type", index));
assertEquals("http://unitsofmeasure.org", getPartValue("System", index));
assertEquals("mg", getPartValue("Units", index));
assertEquals(1.2d, getPartValueDecimal(index), 0.001d);
index = findIndexes(outcome, "value-quantity", 2, "QuantityIndexes").get(1);
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("QuantityNormalized", getPartValue("Type", index));
assertEquals("http://unitsofmeasure.org", getPartValue("System", index));
assertEquals("g", getPartValue("Units", index));
assertEquals(0.0012d, getPartValueDecimal(index), 0.001d);
}
@Test
public void testDryRunTypes_ResourceLink() {
createPatient(withId("A"), withActiveTrue());
IIdType id = createObservation(withSubject("Patient/A"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "Observation.subject", "ResourceLinks");
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("Reference", getPartValue("Type", index));
assertEquals("Patient/A", getPartValue("TargetId", index));
}
@Test
public void testDryRunTypes_ResourceLink_WithUrl() {
createPatient(withId("A"), withActiveTrue());
IIdType id = createObservation(withSubject("Patient/A"));
runInTransaction(() -> {
assertEquals(2, myEntityManager.createNativeQuery("update HFJ_RES_LINK set TARGET_RESOURCE_ID = null").executeUpdate());
assertEquals(2, myEntityManager.createNativeQuery("update HFJ_RES_LINK set TARGET_RESOURCE_URL = 'http://foo'").executeUpdate());
assertEquals(2, myEntityManager.createNativeQuery("update HFJ_RES_LINK set TARGET_RESOURCE_VERSION = 1").executeUpdate());
});
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
List<Parameters.ParametersParameterComponent> indexes = findIndexes(outcome, "Observation.subject", 2, "ResourceLinks");
Parameters.ParametersParameterComponent index;
index = indexes.get(0);
assertEquals("ADD", getPartValue("Action", index));
assertEquals("Reference", getPartValue("Type", index));
assertEquals("Patient/A", getPartValue("TargetId", index));
index = indexes.get(1);
assertEquals("REMOVE", getPartValue("Action", index));
assertEquals("Reference", getPartValue("Type", index));
assertEquals("http://foo", getPartValue("TargetUrl", index));
assertEquals("1", getPartValue("TargetVersion", index));
}
@Test
public void testDryRunTypes_String() {
IIdType id = createPatient(withIdentifier("http://identifiers", "123"), withFamily("Smith"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "family", "StringIndexes");
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("String", getPartValue("Type", index));
assertEquals("SMITH", getPartValue("ValueNormalized", index));
assertEquals("Smith", getPartValue("ValueExact", index));
}
@Test
public void testDryRunTypes_String_SpecificParameter() {
IIdType id = createPatient(withIdentifier("http://identifiers", "123"), withFamily("Simpson"), withGiven("Homer"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, Set.of("family"));
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "family", "StringIndexes");
assertEquals("UNKNOWN", getPartValue("Action", index));
assertEquals("String", getPartValue("Type", index));
assertEquals("SIMPSON", getPartValue("ValueNormalized", index));
assertEquals("Simpson", getPartValue("ValueExact", index));
findIndexes(outcome, "family", 1, "StringIndexes");
findIndexes(outcome, "given", 0, "StringIndexes");
}
@Test
public void testDryRunTypes_Token() {
IIdType id = createPatient(withIdentifier("http://identifiers", "123"), withFamily("Smith"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "identifier", "TokenIndexes");
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("Token", getPartValue("Type", index));
assertEquals("http://identifiers", getPartValue("System", index));
assertEquals("123", getPartValue("Value", index));
}
@Test
public void testDryRunTypes_Uri() {
IIdType id = createResource("CodeSystem", withPrimitiveAttribute("url", "http://foo"));
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "system", "UriIndexes");
assertEquals("NO_CHANGE", getPartValue("Action", index));
assertEquals("Uri", getPartValue("Type", index));
assertEquals("http://foo", getPartValue("Value", index));
}
@Test
public void testReindexInstance() {
Patient p1 = new Patient();
p1.setActive(true);
p1.addExtension()
.setUrl("http://acme.org/eyecolour")
.setValue(new StringType("Gold"));
IIdType p1id = myPatientDao.create(p1, mySrd).getId().toUnqualifiedVersionless();
SearchParameter eyeColourSp = new SearchParameter();
eyeColourSp.addBase("Patient");
eyeColourSp.setCode("eyecolour");
eyeColourSp.setType(Enumerations.SearchParamType.STRING);
eyeColourSp.setTitle("Eye Colour");
eyeColourSp.setExpression("Patient.extension('http://acme.org/eyecolour')");
eyeColourSp.setStatus(Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(eyeColourSp, mySrd);
mySearchParamRegistry.forceRefresh();
SearchParameterMap map = SearchParameterMap.newSynchronous("eyecolour", new StringParam("GOLD"));
assertEquals(0, myPatientDao.search(map, mySrd).size());
Parameters outcome = (Parameters) mySvc.reindex(mySrd, p1id);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
Parameters.ParametersParameterComponent index = findSingleIndex(outcome, "eyecolour", "StringIndexes");
assertEquals("ADD", getPartValue("Action", index));
assertEquals("String", getPartValue("Type", index));
assertEquals("GOLD", getPartValue("ValueNormalized", index));
assertEquals("Gold", getPartValue("ValueExact", index));
assertEquals(1, myPatientDao.search(map, mySrd).size());
}
private void createNamesAndGenderSp(boolean theUnique) {
SearchParameter sp = new SearchParameter();
sp.setId("SearchParameter/patient-family");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("family");
sp.setExpression("Patient.name.family + '|'");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp, mySrd);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-given");
sp.setType(Enumerations.SearchParamType.STRING);
sp.setCode("given");
sp.setExpression("Patient.name.given");
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
mySearchParameterDao.update(sp, mySrd);
sp = new SearchParameter();
sp.setId("SearchParameter/patient-names-and-gender");
sp.setType(Enumerations.SearchParamType.COMPOSITE);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
sp.addBase("Patient");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-family");
sp.addComponent()
.setExpression("Patient")
.setDefinition("SearchParameter/patient-given");
sp.addExtension()
.setUrl(HapiExtensions.EXT_SP_UNIQUE)
.setValue(new BooleanType(theUnique));
mySearchParameterDao.update(sp, mySrd);
mySearchParamRegistry.forceRefresh();
}
private double getPartValueDecimal(Parameters.ParametersParameterComponent theParent) {
return Double.parseDouble(getPartValue("Value", theParent));
}
private static Parameters.ParametersParameterComponent findSingleIndex(Parameters theResponse, String theParamName, String theSectionName) {
List<Parameters.ParametersParameterComponent> indexInstances = findIndexes(theResponse, theParamName, 1, theSectionName);
return indexInstances.get(0);
}
@Nonnull
private static List<Parameters.ParametersParameterComponent> findIndexes(Parameters theResponse, String theParamName, int theExpectedSize, String theSectionName) {
List<Parameters.ParametersParameterComponent> indexes = theResponse.getParameters(theSectionName);
assertEquals(1, indexes.size());
List<Parameters.ParametersParameterComponent> indexInstances = indexes
.get(0)
.getPart()
.stream()
.filter(t -> t.getName().equals(theParamName))
.toList();
assertEquals(theExpectedSize, indexInstances.size());
return indexInstances;
}
@Nonnull
private static String getPartValue(String thePartName, Parameters.ParametersParameterComponent theParent) {
return theParent
.getPart()
.stream()
.filter(t2 -> t2.getName().equals(thePartName))
.findFirst()
.map(t -> (IPrimitiveType<?>) t.getValue())
.map(IPrimitiveType::getValueAsString)
.orElseThrow(() -> new IllegalArgumentException("Couldn't find part with name: " + thePartName));
}
}

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -17,6 +17,7 @@ import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
import ca.uhn.fhir.jpa.provider.InstanceReindexProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
@ -197,6 +198,7 @@ public class TestRestfulServer extends RestfulServer {
}
providers.add(myAppCtx.getBean(JpaSystemProvider.class));
providers.add(myAppCtx.getBean(InstanceReindexProvider.class));
/*
* On the DSTU2 endpoint, we want to enable ETag support

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -163,6 +163,11 @@ public class ProviderConstants {
*/
public static final String OPERATION_REINDEX = "$reindex";
/**
* Operation name for the $reindex operation
*/
public static final String OPERATION_REINDEX_DRYRUN = "$reindex-dryrun";
/**
* Operation name for the $invalidate-expansion operation
*/

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@ -21,7 +21,7 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-caching-api</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
</dependency>
<dependency>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir-serviceloaders</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<artifactId>hapi-fhir</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
</parent>
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-spring-boot</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -56,7 +56,7 @@ public class ReindexProvider {
}
@Operation(name = ProviderConstants.OPERATION_REINDEX, idempotent = false)
public IBaseParameters Reindex(
public IBaseParameters reindex(
@OperationParam(name = ProviderConstants.OPERATION_REINDEX_PARAM_URL, typeName = "string", min = 0, max = OperationParam.MAX_UNLIMITED) List<IPrimitiveType<String>> theUrlsToReindex,
RequestDetails theRequestDetails
) {

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -119,7 +119,6 @@ public class JpaStorageSettings extends StorageSettings {
*/
@Nullable
private Integer myMaximumIncludesToLoadPerPage = DEFAULT_MAXIMUM_INCLUDES_TO_LOAD_PER_PAGE;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/**
* update setter javadoc if default changes
*/
@ -847,53 +846,6 @@ public class JpaStorageSettings extends StorageSettings {
myFetchSizeDefaultMaximum = theFetchSizeDefaultMaximum;
}
/**
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
* the server will not create search indexes for search parameters with no values in resources.
* <p>
* Disabling this feature means that the <code>:missing</code> search modifier will not be
* supported on the server, but also means that storage and indexing (i.e. writes to the
* database) may be much faster on servers which have lots of search parameters and need
* to write quickly.
* </p>
* <p>
* This feature may be enabled on servers where supporting the use of the :missing parameter is
* of higher importance than raw write performance
* </p>
*/
public IndexEnabledEnum getIndexMissingFields() {
return myIndexMissingFieldsEnabled;
}
/**
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
* the server will not create search indexes for search parameters with no values in resources.
* <p>
* Disabling this feature means that the <code>:missing</code> search modifier will not be
* supported on the server, but also means that storage and indexing (i.e. writes to the
* database) may be much faster on servers which have lots of search parameters and need
* to write quickly.
* </p>
* <p>
* This feature may be enabled on servers where supporting the use of the :missing parameter is
* of higher importance than raw write performance
* </p>
* <p>
* Note that this setting also has an impact on sorting (i.e. using the
* <code>_sort</code> parameter on searches): If the server is configured
* to not index missing field.
* </p>
* <p>
* The following index may need to be added into the indexed tables such as <code>HFJ_SPIDX_TOKEN</code>
* to improve the search performance while <code>:missing</code> is enabled.
* <code>RES_TYPE, SP_NAME, SP_MISSING</code>
* </p>
*/
public void setIndexMissingFields(IndexEnabledEnum theIndexMissingFields) {
Validate.notNull(theIndexMissingFields, "theIndexMissingFields must not be null");
myIndexMissingFieldsEnabled = theIndexMissingFields;
}
/**
* See {@link #setMaximumExpansionSize(int)}
*/
@ -2379,10 +2331,6 @@ public class JpaStorageSettings extends StorageSettings {
}
public enum IndexEnabledEnum {
ENABLED,
DISABLED
}
/**
* This enum provides allowable options for {@link #setResourceServerIdStrategy(IdStrategyEnum)}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>6.5.11-SNAPSHOT</version>
<version>6.5.12-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Some files were not shown because too many files have changed in this diff Show More