Add support for of-type token modifier (#3309)

* Add support for of-type token modifier

* Add changelog

* Clean up hash calculation logic

* Test fix

* Rework hash generation

* Work on hash generation

* Test fix

* Avoid redundant hash calculations

* Revert some useless changes

* Cleanup

* Test fixes
This commit is contained in:
James Agnew 2022-01-21 11:10:51 -05:00 committed by GitHub
parent 48caff30d9
commit 3fa76a9027
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 730 additions and 318 deletions

View File

@ -55,7 +55,7 @@ public class ParserOptions {
* serialized. * serialized.
* </p> * </p>
* *
* @since 6.0.0 * @since 5.7.0
*/ */
public boolean isAutoContainReferenceTargetsWithNoId() { public boolean isAutoContainReferenceTargetsWithNoId() {
return myAutoContainReferenceTargetsWithNoId; return myAutoContainReferenceTargetsWithNoId;
@ -72,7 +72,7 @@ public class ParserOptions {
* serialized. * serialized.
* </p> * </p>
* *
* @since 6.0.0 * @since 5.7.0
*/ */
public void setAutoContainReferenceTargetsWithNoId(boolean theAllowAutoContainedReferences) { public void setAutoContainReferenceTargetsWithNoId(boolean theAllowAutoContainedReferences) {
myAutoContainReferenceTargetsWithNoId = theAllowAutoContainedReferences; myAutoContainReferenceTargetsWithNoId = theAllowAutoContainedReferences;

View File

@ -217,6 +217,7 @@ public class Constants {
public static final String PARAMQUALIFIER_STRING_EXACT = ":exact"; public static final String PARAMQUALIFIER_STRING_EXACT = ":exact";
public static final String PARAMQUALIFIER_TOKEN_TEXT = ":text"; public static final String PARAMQUALIFIER_TOKEN_TEXT = ":text";
public static final String PARAMQUALIFIER_MDM = ":mdm"; public static final String PARAMQUALIFIER_MDM = ":mdm";
public static final String PARAMQUALIFIER_TOKEN_OF_TYPE = ":of-type";
public static final int STATUS_HTTP_200_OK = 200; public static final int STATUS_HTTP_200_OK = 200;
public static final int STATUS_HTTP_201_CREATED = 201; public static final int STATUS_HTTP_201_CREATED = 201;
public static final int STATUS_HTTP_204_NO_CONTENT = 204; public static final int STATUS_HTTP_204_NO_CONTENT = 204;

View File

@ -62,7 +62,7 @@ public enum TokenParamModifier {
/** /**
* :of-type * :of-type
*/ */
OF_TYPE(":of-type"); OF_TYPE(Constants.PARAMQUALIFIER_TOKEN_OF_TYPE);
private static final Map<String, TokenParamModifier> VALUE_TO_ENUM; private static final Map<String, TokenParamModifier> VALUE_TO_ENUM;

View File

@ -0,0 +1,5 @@
---
type: add
issue: 1892
title: "In the JPA server, the token `:of-type` modifier is now supported. This is an optional feature and
must be explicitly enabled (default is disabled)."

View File

@ -0,0 +1,4 @@
---
type: perf
issue: 1892
title: "A redundant set of hash calculations in the JPA server was eliminated."

View File

@ -0,0 +1,7 @@
---
type: remove
issue: 1892
title: "In the JPA server, searching using the token `:text` modifier will no longer index and match
values in `Identifier.type.text`. Previously we indexed this element, but the FHIR specification does not
actually show this field as being indexed for this modifier, and intuitively it doesn't make sense to
do so."

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.api.dao.IDao;
@ -134,7 +135,6 @@ import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters; import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent; import javax.xml.stream.events.XMLEvent;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@ -1229,7 +1229,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) { if (thePerformIndexing || ((ResourceTable) theEntity).getVersion() == 1) {
newParams = new ResourceIndexedSearchParams(); newParams = new ResourceIndexedSearchParams();
mySearchParamWithInlineReferencesExtractor.populateFromResource(newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
RequestPartitionId requestPartitionId;
if (!myPartitionSettings.isPartitioningEnabled()) {
requestPartitionId = RequestPartitionId.allPartitions();
} else if (entity.getPartitionId() != null) {
requestPartitionId = entity.getPartitionId().toPartitionId();
} else {
requestPartitionId = RequestPartitionId.defaultPartition();
}
mySearchParamWithInlineReferencesExtractor.populateFromResource(requestPartitionId, newParams, theTransactionDetails, entity, theResource, existingParams, theRequest, thePerformIndexing);
changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true); changed = populateResourceIntoEntity(theTransactionDetails, theRequest, theResource, entity, true);
@ -1656,20 +1665,23 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
myConfig = theDaoConfig; myConfig = theDaoConfig;
} }
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization { public void populateFullTextFields(final FhirContext theContext, final IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) {
if (theEntity.getDeleted() != null) {
private final TagDefinition myTagDefinition; theEntity.setNarrativeText(null);
private final MemoryCacheService.TagDefinitionCacheKey myKey; theEntity.setContentText(null);
} else {
public AddTagDefinitionToCacheAfterCommitSynchronization(MemoryCacheService.TagDefinitionCacheKey theKey, TagDefinition theTagDefinition) { theEntity.setNarrativeText(parseNarrativeTextIntoWords(theResource));
myTagDefinition = theTagDefinition; theEntity.setContentText(parseContentTextIntoWords(theContext, theResource));
myKey = theKey; if (myDaoConfig.isAdvancedLuceneIndexing()) {
ExtendedLuceneIndexData luceneIndexData = myFulltextSearchSvc.extractLuceneIndexData(theResource, theNewParams);
theEntity.setLuceneIndexData(luceneIndexData);
}
}
} }
@Override @VisibleForTesting
public void afterCommit() { public void setPartitionSettingsForUnitTest(PartitionSettings thePartitionSettings) {
myMemoryCacheService.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, myKey, myTagDefinition); myPartitionSettings = thePartitionSettings;
}
} }
@Nonnull @Nonnull
@ -1706,20 +1718,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
return retVal.toString(); return retVal.toString();
} }
public void populateFullTextFields(final FhirContext theContext, final IBaseResource theResource, ResourceTable theEntity, ResourceIndexedSearchParams theNewParams) {
if (theEntity.getDeleted() != null) {
theEntity.setNarrativeText(null);
theEntity.setContentText(null);
} else {
theEntity.setNarrativeText(parseNarrativeTextIntoWords(theResource));
theEntity.setContentText(parseContentTextIntoWords(theContext, theResource));
if (myDaoConfig.isAdvancedLuceneIndexing()) {
ExtendedLuceneIndexData luceneIndexData = myFulltextSearchSvc.extractLuceneIndexData(theResource, theNewParams);
theEntity.setLuceneIndexData(luceneIndexData);
}
}
}
public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) { public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnum theResourceEncoding) {
String resourceText = null; String resourceText = null;
switch (theResourceEncoding) { switch (theResourceEncoding) {
@ -1804,4 +1802,20 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
} }
} }
private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization {
private final TagDefinition myTagDefinition;
private final MemoryCacheService.TagDefinitionCacheKey myKey;
public AddTagDefinitionToCacheAfterCommitSynchronization(MemoryCacheService.TagDefinitionCacheKey theKey, TagDefinition theTagDefinition) {
myTagDefinition = theTagDefinition;
myKey = theKey;
}
@Override
public void afterCommit() {
myMemoryCacheService.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, myKey, myTagDefinition);
}
}
} }

View File

@ -121,7 +121,6 @@ public class DaoSearchParamSynchronizer {
// Take a row we were going to remove, and repurpose its ID // Take a row we were going to remove, and repurpose its ID
T entityToReuse = theIndexesToRemove.remove(theIndexesToRemove.size() - 1); T entityToReuse = theIndexesToRemove.remove(theIndexesToRemove.size() - 1);
entityToReuse.copyMutableValuesFrom(targetEntity); entityToReuse.copyMutableValuesFrom(targetEntity);
entityToReuse.calculateHashes();
theIndexesToAdd.set(addIndex, entityToReuse); theIndexesToAdd.set(addIndex, entityToReuse);
} }
} }

View File

@ -131,6 +131,7 @@ public class IdHelperService {
@Nonnull @Nonnull
public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId) throws ResourceNotFoundException { public IResourceLookup resolveResourceIdentity(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theResourceId) throws ResourceNotFoundException {
assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive(); assert myDontCheckActiveTransactionForUnitTest || TransactionSynchronizationManager.isSynchronizationActive();
assert theRequestPartitionId != null;
IdDt id = new IdDt(theResourceType, theResourceId); IdDt id = new IdDt(theResourceType, theResourceId);
Map<String, List<IResourceLookup>> matches = translateForcedIdToPids(theRequestPartitionId, Map<String, List<IResourceLookup>> matches = translateForcedIdToPids(theRequestPartitionId,
@ -377,6 +378,8 @@ public class IdHelperService {
} }
private Map<String, List<IResourceLookup>> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, Collection<IIdType> theId) { private Map<String, List<IResourceLookup>> translateForcedIdToPids(@Nonnull RequestPartitionId theRequestPartitionId, Collection<IIdType> theId) {
assert theRequestPartitionId != null;
theId.forEach(id -> Validate.isTrue(id.hasIdPart())); theId.forEach(id -> Validate.isTrue(id.hasIdPart()));
if (theId.isEmpty()) { if (theId.isEmpty()) {

View File

@ -31,7 +31,6 @@ import ca.uhn.fhir.jpa.dao.MatchResourceUrlService;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedComboTokenNonUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceLink;
@ -56,12 +55,12 @@ import com.google.common.annotations.VisibleForTesting;
import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceContextType;
@ -104,6 +103,8 @@ public class SearchParamWithInlineReferencesExtractor {
private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao; private IResourceIndexedComboStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired @Autowired
private PartitionSettings myPartitionSettings; private PartitionSettings myPartitionSettings;
@Autowired
private MemoryCacheService myMemoryCacheService;
@VisibleForTesting @VisibleForTesting
public void setPartitionSettings(PartitionSettings thePartitionSettings) { public void setPartitionSettings(PartitionSettings thePartitionSettings) {
@ -120,17 +121,10 @@ public class SearchParamWithInlineReferencesExtractor {
mySearchParamRegistry = theSearchParamRegistry; mySearchParamRegistry = theSearchParamRegistry;
} }
public void populateFromResource(ResourceIndexedSearchParams theParams, TransactionDetails theTransactionDetails, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest, boolean theFailOnInvalidReference) { public void populateFromResource(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, TransactionDetails theTransactionDetails, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams, RequestDetails theRequest, boolean theFailOnInvalidReference) {
extractInlineReferences(theResource, theTransactionDetails, theRequest); extractInlineReferences(theResource, theTransactionDetails, theRequest);
RequestPartitionId partitionId; mySearchParamExtractorService.extractFromResource(theRequestPartitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference);
if (myPartitionSettings.isPartitioningEnabled()) {
partitionId = PartitionablePartitionId.toRequestPartitionId(theEntity.getPartitionId());
} else {
partitionId = RequestPartitionId.allPartitions();
}
mySearchParamExtractorService.extractFromResource(partitionId, theRequest, theParams, theEntity, theResource, theTransactionDetails, theFailOnInvalidReference);
Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet(); Set<Map.Entry<String, RuntimeSearchParam>> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet();
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) { if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.ENABLED) {
@ -299,13 +293,12 @@ public class SearchParamWithInlineReferencesExtractor {
if (paramsListForCompositePart != null) { if (paramsListForCompositePart != null) {
paramsListForCompositePart = paramsListForCompositePart paramsListForCompositePart = paramsListForCompositePart
.stream() .stream()
.filter(t->t.getParamName().equals(nextCompositeOf.getName())) .filter(t -> t.getParamName().equals(nextCompositeOf.getName()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
return paramsListForCompositePart; return paramsListForCompositePart;
} }
@VisibleForTesting @VisibleForTesting
public void setDaoConfig(DaoConfig theDaoConfig) { public void setDaoConfig(DaoConfig theDaoConfig) {
myDaoConfig = theDaoConfig; myDaoConfig = theDaoConfig;
@ -316,12 +309,6 @@ public class SearchParamWithInlineReferencesExtractor {
myContext = theContext; myContext = theContext;
} }
@Autowired
private MemoryCacheService myMemoryCacheService;
/** /**
* Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the * Handle references within the resource that are match URLs, for example references like "Patient?identifier=foo". These match URLs are resolved and replaced with the ID of the
* matching resource. * matching resource.

View File

@ -61,15 +61,14 @@ import static org.hl7.fhir.common.hapi.validation.support.ValidationConstants.LO
public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> { public class FhirResourceDaoCodeSystemR4 extends BaseHapiFhirResourceDao<CodeSystem> implements IFhirResourceDaoCodeSystem<CodeSystem, Coding, CodeableConcept> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCodeSystemR4.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCodeSystemR4.class);
@Autowired
private IValidationSupport myValidationSupport;
@Autowired @Autowired
protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc; protected ITermCodeSystemStorageSvc myTerminologyCodeSystemStorageSvc;
@Autowired @Autowired
private FhirContext myFhirContext;
@Autowired
protected ITermDeferredStorageSvc myTermDeferredStorageSvc; protected ITermDeferredStorageSvc myTermDeferredStorageSvc;
@Autowired
private IValidationSupport myValidationSupport;
@Autowired
private FhirContext myFhirContext;
@Override @Override
public List<IIdType> findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { public List<IIdType> findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) {

View File

@ -20,9 +20,9 @@ package ca.uhn.fhir.jpa.dao.r4;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.TranslateConceptResults;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.context.support.TranslateConceptResults;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;

View File

@ -22,20 +22,20 @@ package ca.uhn.fhir.jpa.dao.r5;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult; import ca.uhn.fhir.context.support.IValidationSupport.CodeValidationResult;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoCodeSystem;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.index.IdHelperService; import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
@ -65,11 +65,11 @@ public class FhirResourceDaoCodeSystemR5 extends BaseHapiFhirResourceDao<CodeSys
@Autowired @Autowired
protected IdHelperService myIdHelperService; protected IdHelperService myIdHelperService;
@Autowired @Autowired
protected ITermDeferredStorageSvc myTermDeferredStorageSvc;
@Autowired
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
@Autowired
protected ITermDeferredStorageSvc myTermDeferredStorageSvc;
@Override @Override
public List<IIdType> findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) { public List<IIdType> findCodeSystemIdsContainingSystemAndCode(String theCode, String theSystem, RequestDetails theRequest) {

View File

@ -20,9 +20,9 @@ package ca.uhn.fhir.jpa.dao.r5;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.support.TranslateConceptResults;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoConceptMap;
import ca.uhn.fhir.jpa.api.model.TranslationRequest; import ca.uhn.fhir.jpa.api.model.TranslationRequest;
import ca.uhn.fhir.context.support.TranslateConceptResults;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.LegacySearchBuilder; import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser; import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.search.builder.QueryStack; import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder; import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
@ -35,16 +36,19 @@ import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; import ca.uhn.fhir.model.base.composite.BaseIdentifierDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.NumberParam;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier; import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import ca.uhn.fhir.util.FhirVersionIndependentConcept; import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.healthmarketscience.sqlbuilder.BinaryCondition; import com.healthmarketscience.sqlbuilder.BinaryCondition;
import com.healthmarketscience.sqlbuilder.Condition; import com.healthmarketscience.sqlbuilder.Condition;
import com.healthmarketscience.sqlbuilder.InCondition; import com.healthmarketscience.sqlbuilder.InCondition;
import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn; import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -73,7 +77,8 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
@Autowired @Autowired
private ITermReadSvc myTerminologySvc; private ITermReadSvc myTerminologySvc;
@Autowired
private ModelConfig myModelConfig;
/** /**
* Constructor * Constructor
@ -129,7 +134,7 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
if (nextParameter instanceof TokenParam) { if (nextParameter instanceof TokenParam) {
TokenParam id = (TokenParam) nextParameter; TokenParam id = (TokenParam) nextParameter;
system = id.getSystem(); system = id.getSystem();
code = (id.getValue()); code = id.getValue();
modifier = id.getModifier(); modifier = id.getModifier();
} else if (nextParameter instanceof BaseIdentifierDt) { } else if (nextParameter instanceof BaseIdentifierDt) {
BaseIdentifierDt id = (BaseIdentifierDt) nextParameter; BaseIdentifierDt id = (BaseIdentifierDt) nextParameter;
@ -171,6 +176,20 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
system = determineSystemIfMissing(theSearchParam, code, system); system = determineSystemIfMissing(theSearchParam, code, system);
validateHaveSystemAndCodeForToken(paramName, code, system); validateHaveSystemAndCodeForToken(paramName, code, system);
codes.addAll(myTerminologySvc.findCodesBelow(system, code)); codes.addAll(myTerminologySvc.findCodesBelow(system, code));
} else if (modifier == TokenParamModifier.OF_TYPE) {
if (!myModelConfig.isIndexIdentifierOfType()) {
throw new MethodNotAllowedException("The :of-type modifier is not enabled on this server");
}
if (isBlank(system) || isBlank(code)) {
throw new InvalidRequestException("Invalid parameter value for :of-type query");
}
int pipeIdx = code.indexOf('|');
if (pipeIdx < 1 || pipeIdx == code.length() - 1) {
throw new InvalidRequestException("Invalid parameter value for :of-type query");
}
paramName = paramName + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE;
codes.add(new FhirVersionIndependentConcept(system, code));
} else { } else {
if (modifier == TokenParamModifier.NOT && operation == null) { if (modifier == TokenParamModifier.NOT && operation == null) {
operation = SearchFilterParser.CompareOperation.ne; operation = SearchFilterParser.CompareOperation.ne;

View File

@ -15,6 +15,7 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboTokensNonUniqueDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamDateDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao;
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao;
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
@ -94,6 +95,7 @@ import javax.persistence.EntityManager;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -168,6 +170,8 @@ public abstract class BaseJpaTest extends BaseTest {
@Autowired @Autowired
protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao; protected IResourceIndexedSearchParamTokenDao myResourceIndexedSearchParamTokenDao;
@Autowired @Autowired
protected IResourceIndexedSearchParamStringDao myResourceIndexedSearchParamStringDao;
@Autowired
protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao; protected IResourceIndexedSearchParamDateDao myResourceIndexedSearchParamDateDao;
@Autowired @Autowired
protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao; protected IResourceIndexedComboTokensNonUniqueDao myResourceIndexedComboTokensNonUniqueDao;
@ -346,6 +350,19 @@ public abstract class BaseJpaTest extends BaseTest {
}); });
} }
protected void logAllStringIndexes(String... theParamNames) {
String messageSuffix = theParamNames.length > 0 ? " containing " + Arrays.asList(theParamNames) : "";
runInTransaction(() -> {
String message = myResourceIndexedSearchParamStringDao
.findAll()
.stream()
.filter(t->theParamNames.length == 0 ? true : Arrays.asList(theParamNames).contains(t.getParamName()))
.map(t -> t.toString())
.collect(Collectors.joining("\n * "));
ourLog.info("String indexes{}:\n * {}", messageSuffix, message);
});
}
protected void logAllResourceTags() { protected void logAllResourceTags() {
runInTransaction(() -> { runInTransaction(() -> {
ourLog.info("Token tags:\n * {}", myResourceTagDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); ourLog.info("Token tags:\n * {}", myResourceTagDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));

View File

@ -1193,8 +1193,12 @@ public class ChainingR4SearchTest extends BaseJpaR4Test {
String url = "/Observation?subject.organization.partof.name=HealthCo"; String url = "/Observation?subject.organization.partof.name=HealthCo";
logAllStringIndexes();
// execute // execute
myCaptureQueriesListener.clear();
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url); List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(url);
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
// validate // validate
assertEquals(1L, oids.size()); assertEquals(1L, oids.size());

View File

@ -303,7 +303,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
List<ResourceTable> resources = myResourceTableDao.findAll(); List<ResourceTable> resources = myResourceTableDao.findAll();
String versions = "Resource Versions:\n * " + resources String versions = "Resource Versions:\n * " + resources
.stream() .stream()
.map(t->"Resource " + t.getIdDt() + " has version: " + t.getVersion()) .map(t -> "Resource " + t.getIdDt() + " has version: " + t.getVersion())
.collect(Collectors.joining("\n * ")); .collect(Collectors.joining("\n * "));
for (ResourceTable next : resources) { for (ResourceTable next : resources) {
@ -368,7 +368,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
List<ResourceTable> resources = myResourceTableDao.findAll(); List<ResourceTable> resources = myResourceTableDao.findAll();
String versions = "Resource Versions:\n * " + resources String versions = "Resource Versions:\n * " + resources
.stream() .stream()
.map(t->"Resource " + t.getIdDt() + " has version: " + t.getVersion()) .map(t -> "Resource " + t.getIdDt() + " has version: " + t.getVersion())
.collect(Collectors.joining("\n * ")); .collect(Collectors.joining("\n * "));
for (ResourceTable next : resources) { for (ResourceTable next : resources) {
@ -554,9 +554,9 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
public void assertNoPartitionSelectors() { public void assertNoPartitionSelectors() {
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread(); List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueriesForCurrentThread();
for (SqlQuery next : selectQueries) { for (SqlQuery next : selectQueries) {
assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id is null")); assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id is null"), () -> next.getSql(true, true));
assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id=")); assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id="), () -> next.getSql(true, true));
assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id =")); assertEquals(0, StringUtils.countMatches(next.getSql(true, true).toLowerCase(), "partition_id ="), () -> next.getSql(true, true));
} }
} }

View File

@ -125,7 +125,6 @@ import org.hl7.fhir.r4.model.Substance;
import org.hl7.fhir.r4.model.Task; import org.hl7.fhir.r4.model.Task;
import org.hl7.fhir.r4.model.Timing; import org.hl7.fhir.r4.model.Timing;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.Ignore;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -170,6 +169,7 @@ import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -193,9 +193,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches()); myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds()); myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
myModelConfig.setAutoSupportDefaultSearchParams(true); myModelConfig.setAutoSupportDefaultSearchParams(true);
myModelConfig.setIndexIdentifierOfType(new ModelConfig().isIndexIdentifierOfType());
mySearchParamRegistry.resetForUnitTest(); mySearchParamRegistry.resetForUnitTest();
} }
@ -3793,6 +3795,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
patient.addIdentifier().setSystem("urn:system").setValue("TOKENB"); patient.addIdentifier().setSystem("urn:system").setValue("TOKENB");
String idBoth = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); String idBoth = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
logAllTokenIndexes();
patient = new Patient(); patient = new Patient();
patient.addIdentifier().setSystem("urn:system").setValue("TOKENA"); patient.addIdentifier().setSystem("urn:system").setValue("TOKENA");
String idA = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); String idA = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
@ -5135,6 +5139,74 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
runInTransaction(() -> assertEquals(2, mySearchEntityDao.count())); runInTransaction(() -> assertEquals(2, mySearchEntityDao.count()));
} }
@Test
public void testTokenOfType() {
myModelConfig.setIndexIdentifierOfType(true);
Patient patient = new Patient();
patient
.addIdentifier()
.setSystem("http://foo1")
.setValue("bar1")
.getType()
.addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setCode("MR");
IIdType id1 = myPatientDao.create(patient).getId().toUnqualifiedVersionless();
runInTransaction(()->{
List<ResourceIndexedSearchParamToken> params = myResourceIndexedSearchParamTokenDao
.findAll()
.stream()
.filter(t -> t.getParamName().equals("identifier:of-type"))
.collect(Collectors.toList());
assertEquals(1, params.size());
assertNotNull(params.get(0).getHashSystemAndValue());
assertNull(params.get(0).getHashSystem());
assertNull(params.get(0).getHashValue());
});
// Shouldn't match
patient = new Patient();
patient
.addIdentifier()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setValue("MR|bar1");
myPatientDao.create(patient);
TokenParam param = new TokenParam()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setValue("MR|bar1")
.setModifier(TokenParamModifier.OF_TYPE);
SearchParameterMap map = SearchParameterMap.newSynchronous("identifier", param);
logAllTokenIndexes();
myCaptureQueriesListener.clear();
IBundleProvider outcome = myPatientDao.search(map, mySrd);
List<IIdType> ids = toUnqualifiedVersionlessIds(outcome);
myCaptureQueriesListener.logSelectQueries();
assertThat(ids, contains(id1));
}
@Test
public void testTokenOfType_Disabled() {
try {
TokenParam param = new TokenParam()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setValue("MR|bar1")
.setModifier(TokenParamModifier.OF_TYPE);
SearchParameterMap map = SearchParameterMap.newSynchronous("identifier", param);
myPatientDao.search(map, mySrd);
fail();
} catch (MethodNotAllowedException e) {
assertEquals("The :of-type modifier is not enabled on this server", e.getMessage());
}
}
@Test @Test
public void testTokenTextDisabled_Global() { public void testTokenTextDisabled_Global() {
myModelConfig.setSuppressStringIndexingInTokens(true); myModelConfig.setSuppressStringIndexingInTokens(true);

View File

@ -1,9 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel; import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
@ -102,7 +100,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;

View File

@ -387,13 +387,6 @@ public class FhirResourceDaoR4SearchWithElasticSearchIT extends BaseJpaTest {
assertObservationSearchMatches(":text matches code.display", map, id2); assertObservationSearchMatches(":text matches code.display", map, id2);
} }
{
// Identifier Type
SearchParameterMap map = new SearchParameterMap();
map.add("identifier", new TokenParam("Random").setModifier(TokenParamModifier.TEXT));
assertObservationSearchMatches(":text matches identifier text", map, id4);
}
{ {
// multiple values means or // multiple values means or
SearchParameterMap map = new SearchParameterMap(); SearchParameterMap map = new SearchParameterMap();

View File

@ -1318,8 +1318,11 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
@Test @Test
public void testSearch_IdParamSecond_ForcedId_SpecificPartition() { public void testSearch_IdParamSecond_ForcedId_SpecificPartition() {
IIdType patientIdNull = createPatient(withPartition(null), withId("PT-NULL"), withActiveTrue()); // FIXME: move down
IIdType patientId1 = createPatient(withPartition(1), withId("PT-1"), withActiveTrue()); IIdType patientId1 = createPatient(withPartition(1), withId("PT-1"), withActiveTrue());
logAllTokenIndexes();
IIdType patientIdNull = createPatient(withPartition(null), withId("PT-NULL"), withActiveTrue());
IIdType patientId2 = createPatient(withPartition(2), withId("PT-2"), withActiveTrue()); IIdType patientId2 = createPatient(withPartition(2), withId("PT-2"), withActiveTrue());
/* ******************************* /* *******************************
@ -2198,6 +2201,8 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
IIdType patientId1 = createPatient(withPartition(1), withFamily("FAMILY")); IIdType patientId1 = createPatient(withPartition(1), withFamily("FAMILY"));
createPatient(withPartition(2), withFamily("FAMILY")); createPatient(withPartition(2), withFamily("FAMILY"));
logAllStringIndexes();
addReadPartition(1); addReadPartition(1);
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
@ -2207,7 +2212,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
IBundleProvider results = myPatientDao.search(map, mySrd); IBundleProvider results = myPatientDao.search(map, mySrd);
List<IIdType> ids = toUnqualifiedVersionlessIds(results); List<IIdType> ids = toUnqualifiedVersionlessIds(results);
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertThat(ids, contains(patientId1)); assertThat(ids.toString(), ids, contains(patientId1));
String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true);
ourLog.info("Search SQL:\n{}", searchSql); ourLog.info("Search SQL:\n{}", searchSql);

View File

@ -224,57 +224,6 @@ public class SearchParamExtractorR4Test {
} }
@Test
public void testTokenText_Enabled_Identifier() {
Observation obs = new Observation();
obs.addIdentifier().setSystem("sys").setValue("val").getType().setText("Help Im a Bug");
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, mySearchParamRegistry);
List<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(obs)
.stream()
.filter(t -> t.getParamName().equals("identifier"))
.sorted(comparing(o -> o.getClass().getName()).reversed())
.collect(Collectors.toList());
assertEquals(2, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
assertEquals("identifier", token.getParamName());
assertEquals("sys", token.getSystem());
assertEquals("val", token.getValue());
ResourceIndexedSearchParamString string = (ResourceIndexedSearchParamString) tokens.get(1);
assertEquals("identifier", string.getParamName());
assertEquals("Help Im a Bug", string.getValueExact());
}
@Test
public void testTokenText_DisabledInSearchParam_Identifier() {
RuntimeSearchParam existingCodeSp = mySearchParamRegistry.getActiveSearchParams("Observation").get("identifier");
RuntimeSearchParam codeSearchParam = new RuntimeSearchParam(existingCodeSp);
codeSearchParam.addExtension(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new Extension(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING, new BooleanType(true)));
mySearchParamRegistry.addSearchParam(codeSearchParam);
Observation obs = new Observation();
obs.addIdentifier().setSystem("sys").setValue("val").getType().setText("Help Im a Bug");
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, mySearchParamRegistry);
List<BaseResourceIndexedSearchParam> tokens = extractor.extractSearchParamTokens(obs)
.stream()
.filter(t -> t.getParamName().equals("identifier"))
.sorted(comparing(o -> o.getClass().getName()).reversed())
.collect(Collectors.toList());
assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.get(0);
assertEquals("identifier", token.getParamName());
assertEquals("sys", token.getSystem());
assertEquals("val", token.getValue());
}
@Test @Test
public void testReferenceWithResolve() { public void testReferenceWithResolve() {
Encounter enc = new Encounter(); Encounter enc = new Encounter();
@ -394,6 +343,49 @@ public class SearchParamExtractorR4Test {
assertEquals(2, list.size()); assertEquals(2, list.size());
} }
@Test
public void testExtractIdentifierOfType() {
ModelConfig modelConfig = new ModelConfig();
modelConfig.setIndexIdentifierOfType(true);
Patient patient = new Patient();
patient
.addIdentifier()
.setSystem("http://foo1")
.setValue("bar1")
.getType()
.addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setCode("MR");
patient
.addIdentifier()
.setSystem("http://foo2")
.setValue("bar2")
.getType()
.addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/v2-0203")
.setCode("MR");
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, mySearchParamRegistry);
List<ResourceIndexedSearchParamToken> list = extractor
.extractSearchParamTokens(patient)
.stream()
.map(t->(ResourceIndexedSearchParamToken)t)
.collect(Collectors.toList());
list.forEach(t->t.calculateHashes());
ourLog.info("Found tokens:\n * {}", list.stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
assertThat(list, containsInAnyOrder(
new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "deceased", null, "false"),
new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "identifier", "http://foo1", "bar1"),
new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "identifier", "http://foo2", "bar2"),
new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "identifier:of-type", "http://terminology.hl7.org/CodeSystem/v2-0203", "MR|bar1"),
new ResourceIndexedSearchParamToken(new PartitionSettings(), "Patient", "identifier:of-type", "http://terminology.hl7.org/CodeSystem/v2-0203", "MR|bar2")
));
}
private static class MySearchParamRegistry implements ISearchParamRegistry, ISearchParamRegistryController { private static class MySearchParamRegistry implements ISearchParamRegistry, ISearchParamRegistryController {

View File

@ -859,6 +859,7 @@ public class ResourceProviderR4SearchContainedTest extends BaseResourceProviderR
obs.getSubject().setReference("#patient1"); obs.getSubject().setReference("#patient1");
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
logAllStringIndexes("subject.family");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs)); ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
@ -877,7 +878,9 @@ public class ResourceProviderR4SearchContainedTest extends BaseResourceProviderR
} }
// -- update // -- update
oid1 = myObservationDao.update(createdObs, mySrd).getId().toUnqualifiedVersionless(); myObservationDao.update(createdObs, mySrd).getId().toUnqualifiedVersionless();
logAllStringIndexes("subject.family");
} }
{ {
@ -891,10 +894,12 @@ public class ResourceProviderR4SearchContainedTest extends BaseResourceProviderR
obs.getSubject().setReference("#patient1"); obs.getSubject().setReference("#patient1");
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
logAllStringIndexes("subject.family");
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs)); ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
} }
{ {
Patient p = new Patient(); Patient p = new Patient();
p.setId("patient1"); p.setId("patient1");

View File

@ -1,12 +1,15 @@
package ca.uhn.fhir.jpa.provider.r4; package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
@ -45,6 +48,8 @@ public class ResourceProviderSearchModifierR4Test extends BaseResourceProviderR4
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches()); myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
myModelConfig.setIndexIdentifierOfType(new ModelConfig().isIndexIdentifierOfType());
myClient.unregisterInterceptor(myCapturingInterceptor); myClient.unregisterInterceptor(myCapturingInterceptor);
} }
@ -199,7 +204,75 @@ public class ResourceProviderSearchModifierR4Test extends BaseResourceProviderR4
assertEquals(obsList.get(5).toString(), ids.get(3)); assertEquals(obsList.get(5).toString(), ids.get(3));
} }
@Test
public void testSearch_OfType_PartialBlocked() {
myModelConfig.setIndexIdentifierOfType(true);
try {
String uri = ourServerBase + "/Patient?identifier:of-type=A";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=A|B";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=A|B|";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=|B|C";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=||C";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=|B|";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=A||";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
try {
String uri = ourServerBase + "/Patient?identifier:of-type=||";
myClient.search().byUrl(uri).execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: Invalid parameter value for :of-type query", e.getMessage());
}
}
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String uri) throws IOException { private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String uri) throws IOException {
List<String> ids; List<String> ids;

View File

@ -182,7 +182,7 @@ public class GiantTransactionPerfTest {
myTransactionProcessor.setModelConfig(myDaoConfig.getModelConfig()); myTransactionProcessor.setModelConfig(myDaoConfig.getModelConfig());
myTransactionProcessor.setHapiTransactionService(myHapiTransactionService); myTransactionProcessor.setHapiTransactionService(myHapiTransactionService);
myTransactionProcessor.setDaoRegistry(myDaoRegistry); myTransactionProcessor.setDaoRegistry(myDaoRegistry);
myTransactionProcessor.setPartitionSettingsForUnitTest(myPartitionSettings); myTransactionProcessor.setPartitionSettingsForUnitTest(this.myPartitionSettings);
myTransactionProcessor.setIdHelperServiceForUnitTest(myIdHelperService); myTransactionProcessor.setIdHelperServiceForUnitTest(myIdHelperService);
myTransactionProcessor.setFhirContextForUnitTest(myCtx); myTransactionProcessor.setFhirContextForUnitTest(myCtx);
myTransactionProcessor.start(); myTransactionProcessor.start();
@ -190,6 +190,7 @@ public class GiantTransactionPerfTest {
mySystemDao = new FhirSystemDaoR4(); mySystemDao = new FhirSystemDaoR4();
mySystemDao.setTransactionProcessorForUnitTest(myTransactionProcessor); mySystemDao.setTransactionProcessorForUnitTest(myTransactionProcessor);
mySystemDao.setDaoConfigForUnitTest(myDaoConfig); mySystemDao.setDaoConfigForUnitTest(myDaoConfig);
mySystemDao.setPartitionSettingsForUnitTest(myPartitionSettings);
mySystemDao.start(); mySystemDao.start();
when(myAppCtx.getBean(eq(IInstanceValidatorModule.class))).thenReturn(myInstanceValidatorSvc); when(myAppCtx.getBean(eq(IInstanceValidatorModule.class))).thenReturn(myInstanceValidatorSvc);
@ -228,7 +229,7 @@ public class GiantTransactionPerfTest {
mySearchParamExtractor = new SearchParamExtractorR4(); mySearchParamExtractor = new SearchParamExtractorR4();
mySearchParamExtractor.setContext(myCtx); mySearchParamExtractor.setContext(myCtx);
mySearchParamExtractor.setSearchParamRegistry(mySearchParamRegistry); mySearchParamExtractor.setSearchParamRegistry(mySearchParamRegistry);
mySearchParamExtractor.setPartitionSettings(myPartitionSettings); mySearchParamExtractor.setPartitionSettings(this.myPartitionSettings);
mySearchParamExtractor.setModelConfig(myDaoConfig.getModelConfig()); mySearchParamExtractor.setModelConfig(myDaoConfig.getModelConfig());
mySearchParamExtractor.start(); mySearchParamExtractor.start();
@ -243,7 +244,7 @@ public class GiantTransactionPerfTest {
mySearchParamWithInlineReferencesExtractor = new SearchParamWithInlineReferencesExtractor(); mySearchParamWithInlineReferencesExtractor = new SearchParamWithInlineReferencesExtractor();
mySearchParamWithInlineReferencesExtractor.setDaoConfig(myDaoConfig); mySearchParamWithInlineReferencesExtractor.setDaoConfig(myDaoConfig);
mySearchParamWithInlineReferencesExtractor.setContext(myCtx); mySearchParamWithInlineReferencesExtractor.setContext(myCtx);
mySearchParamWithInlineReferencesExtractor.setPartitionSettings(myPartitionSettings); mySearchParamWithInlineReferencesExtractor.setPartitionSettings(this.myPartitionSettings);
mySearchParamWithInlineReferencesExtractor.setSearchParamExtractorService(mySearchParamExtractorSvc); mySearchParamWithInlineReferencesExtractor.setSearchParamExtractorService(mySearchParamExtractorSvc);
mySearchParamWithInlineReferencesExtractor.setSearchParamRegistry(mySearchParamRegistry); mySearchParamWithInlineReferencesExtractor.setSearchParamRegistry(mySearchParamRegistry);
mySearchParamWithInlineReferencesExtractor.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer); mySearchParamWithInlineReferencesExtractor.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer);
@ -263,6 +264,7 @@ public class GiantTransactionPerfTest {
myEobDao.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer); myEobDao.setDaoSearchParamSynchronizer(myDaoSearchParamSynchronizer);
myEobDao.setDaoConfigForUnitTest(myDaoConfig); myEobDao.setDaoConfigForUnitTest(myDaoConfig);
myEobDao.setIdHelperSvcForUnitTest(myIdHelperService); myEobDao.setIdHelperSvcForUnitTest(myIdHelperService);
myEobDao.setPartitionSettingsForUnitTest(myPartitionSettings);
myEobDao.start(); myEobDao.start();
myDaoRegistry.setResourceDaos(Lists.newArrayList(myEobDao)); myDaoRegistry.setResourceDaos(Lists.newArrayList(myEobDao));

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.model.entity;
* #L% * #L%
*/ */
import org.apache.commons.lang3.ObjectUtils;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import java.io.Serializable; import java.io.Serializable;
@ -32,6 +34,16 @@ public abstract class BaseResourceIndex extends BasePartitionable implements Ser
public abstract void calculateHashes(); public abstract void calculateHashes();
public abstract void clearHashes();
@Override
public void setPartitionId(PartitionablePartitionId thePartitionId) {
if (ObjectUtils.notEqual(getPartitionId(), thePartitionId)) {
super.setPartitionId(thePartitionId);
clearHashes();
}
}
/** /**
* Subclasses must implement * Subclasses must implement
*/ */

View File

@ -31,6 +31,7 @@ import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction; import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher; import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing; import com.google.common.hash.Hashing;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.GenericField;
@ -97,7 +98,10 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
} }
public void setParamName(String theName) { public void setParamName(String theName) {
if (!StringUtils.equals(myParamName, theName)) {
myParamName = theName; myParamName = theName;
clearHashes();
}
} }
public ResourceTable getResource() { public ResourceTable getResource() {
@ -222,6 +226,7 @@ public abstract class BaseResourceIndexedSearchParam extends BaseResourceIndex {
} }
HashCode hashCode = hasher.hash(); HashCode hashCode = hasher.hash();
return hashCode.asLong(); long retVal = hashCode.asLong();
return retVal;
} }
} }

View File

@ -50,12 +50,7 @@ public class ModelConfig {
* <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li> * <li><code>"http://hl7.org/fhir/StructureDefinition/*"</code></li>
* </ul> * </ul>
*/ */
public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( public static final Set<String> DEFAULT_LOGICAL_BASE_URLS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("http://hl7.org/fhir/ValueSet/*", "http://hl7.org/fhir/CodeSystem/*", "http://hl7.org/fhir/valueset-*", "http://hl7.org/fhir/codesystem-*", "http://hl7.org/fhir/StructureDefinition/*")));
"http://hl7.org/fhir/ValueSet/*",
"http://hl7.org/fhir/CodeSystem/*",
"http://hl7.org/fhir/valueset-*",
"http://hl7.org/fhir/codesystem-*",
"http://hl7.org/fhir/StructureDefinition/*")));
public static final String DEFAULT_WEBSOCKET_CONTEXT_PATH = "/websocket"; public static final String DEFAULT_WEBSOCKET_CONTEXT_PATH = "/websocket";
@ -101,6 +96,7 @@ public class ModelConfig {
private boolean myIndexOnContainedResourcesRecursively = false; private boolean myIndexOnContainedResourcesRecursively = false;
private boolean myAllowMdmExpansion = false; private boolean myAllowMdmExpansion = false;
private boolean myAutoSupportDefaultSearchParams = true; private boolean myAutoSupportDefaultSearchParams = true;
private boolean myIndexIdentifierOfType = false;
/** /**
* Constructor * Constructor
@ -111,6 +107,32 @@ public class ModelConfig {
setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED); setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
} }
/**
* If set to <code>true</code> (default is <code>false</code>) the
* <code>:of-type</code> modifier on token search parameters for
* identifiers will be supported. Enabling this causes additional
* indexing overhead (although very minor) so it is disabled unless it is
* actually needed.
*
* @since 5.7.0
*/
public boolean isIndexIdentifierOfType() {
return myIndexIdentifierOfType;
}
/**
* If set to <code>true</code> (default is <code>false</code>) the
* <code>:of-type</code> modifier on token search parameters for
* identifiers will be supported. Enabling this causes additional
* indexing overhead (although very minor) so it is disabled unless it is
* actually needed.
*
* @since 5.7.0
*/
public void setIndexIdentifierOfType(boolean theIndexIdentifierOfType) {
myIndexIdentifierOfType = theIndexIdentifierOfType;
}
/** /**
* If set to {@code true} the default search params (i.e. the search parameters that are * If set to {@code true} the default search params (i.e. the search parameters that are
* defined by the FHIR specification itself) may be overridden by uploading search * defined by the FHIR specification itself) may be overridden by uploading search
@ -816,7 +838,7 @@ public class ModelConfig {
* Disabling this obviously reduces the options for searching however. * Disabling this obviously reduces the options for searching however.
* </p> * </p>
* *
* @since 6.0.0 * @since 5.7.0
*/ */
public boolean isAutoSupportDefaultSearchParams() { public boolean isAutoSupportDefaultSearchParams() {
return myAutoSupportDefaultSearchParams; return myAutoSupportDefaultSearchParams;
@ -832,7 +854,7 @@ public class ModelConfig {
* Disabling this obviously reduces the options for searching however. * Disabling this obviously reduces the options for searching however.
* </p> * </p>
* *
* @since 6.0.0 * @since 5.7.0
*/ */
public void setAutoSupportDefaultSearchParams(boolean theAutoSupportDefaultSearchParams) { public void setAutoSupportDefaultSearchParams(boolean theAutoSupportDefaultSearchParams) {
myAutoSupportDefaultSearchParams = theAutoSupportDefaultSearchParams; myAutoSupportDefaultSearchParams = theAutoSupportDefaultSearchParams;

View File

@ -21,6 +21,8 @@ package ca.uhn.fhir.jpa.model.entity;
*/ */
import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -63,6 +65,21 @@ public class PartitionablePartitionId implements Cloneable {
return this; return this;
} }
@Override
public boolean equals(Object theO) {
if (!(theO instanceof PartitionablePartitionId)) {
return false;
}
PartitionablePartitionId that = (PartitionablePartitionId) theO;
return new EqualsBuilder().append(myPartitionId, that.myPartitionId).append(myPartitionDate, that.myPartitionDate).isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37).append(myPartitionId).append(myPartitionDate).toHashCode();
}
@Nullable @Nullable
public LocalDate getPartitionDate() { public LocalDate getPartitionDate() {
return myPartitionDate; return myPartitionDate;
@ -85,6 +102,13 @@ public class PartitionablePartitionId implements Cloneable {
return RequestPartitionId.fromPartitionId(getPartitionId(), getPartitionDate()); return RequestPartitionId.fromPartitionId(getPartitionId(), getPartitionDate());
} }
@Override
public String toString() {
return "[" +
getPartitionId() +
"]";
}
@Nonnull @Nonnull
public static RequestPartitionId toRequestPartitionId(@Nullable PartitionablePartitionId theRequestPartitionId) { public static RequestPartitionId toRequestPartitionId(@Nullable PartitionablePartitionId theRequestPartitionId) {
if (theRequestPartitionId != null) { if (theRequestPartitionId != null) {

View File

@ -82,6 +82,7 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
myPartitionSettings = thePartitionSettings; myPartitionSettings = thePartitionSettings;
myResource = theEntity; myResource = theEntity;
myIndexString = theQueryString; myIndexString = theQueryString;
calculateHashes();
} }
public String getIndexString() { public String getIndexString() {
@ -125,8 +126,17 @@ public class ResourceIndexedComboTokenNonUnique extends BaseResourceIndex implem
myId = theId; myId = theId;
} }
@Override
public void clearHashes() {
myHashComplete = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashComplete != null) {
return;
}
PartitionSettings partitionSettings = getPartitionSettings(); PartitionSettings partitionSettings = getPartitionSettings();
PartitionablePartitionId partitionId = getPartitionId(); PartitionablePartitionId partitionId = getPartitionId();
String queryString = myIndexString; String queryString = myIndexString;

View File

@ -60,12 +60,26 @@ public abstract class ResourceIndexedSearchParamBaseQuantity extends BaseResourc
@Column(name = "HASH_IDENTITY", nullable = true) @Column(name = "HASH_IDENTITY", nullable = true)
private Long myHashIdentity; private Long myHashIdentity;
/**
* Constructor
*/
public ResourceIndexedSearchParamBaseQuantity() { public ResourceIndexedSearchParamBaseQuantity() {
super(); super();
} }
@Override
public void clearHashes() {
myHashIdentity = null;
myHashIdentityAndUnits = null;
myHashIdentitySystemAndUnits = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null || myHashIdentityAndUnits != null || myHashIdentitySystemAndUnits != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
String units = getUnits(); String units = getUnits();

View File

@ -26,7 +26,6 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
@ -79,8 +78,17 @@ public class ResourceIndexedSearchParamCoords extends BaseResourceIndexedSearchP
calculateHashes(); calculateHashes();
} }
@Override
public void clearHashes() {
myHashIdentity = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.model.entity;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date; import java.util.Date;
import javax.persistence.Column; import javax.persistence.Column;
@ -129,7 +128,6 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
reComputeValueHighDate(theHigh, theHighString); reComputeValueHighDate(theHigh, theHighString);
myOriginalValue = theOriginalValue; myOriginalValue = theOriginalValue;
calculateHashes(); calculateHashes();
} }
private void computeValueHighDateOrdinal(String theHigh) { private void computeValueHighDateOrdinal(String theHigh) {
@ -202,8 +200,17 @@ public class ResourceIndexedSearchParamDate extends BaseResourceIndexedSearchPar
myHashIdentity = source.myHashIdentity; myHashIdentity = source.myHashIdentity;
} }
@Override
public void clearHashes() {
myHashIdentity = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));

View File

@ -86,9 +86,16 @@ public class ResourceIndexedSearchParamNumber extends BaseResourceIndexedSearchP
myHashIdentity = source.myHashIdentity; myHashIdentity = source.myHashIdentity;
} }
@Override
public void clearHashes() {
myHashIdentity = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));

View File

@ -39,7 +39,6 @@ import javax.persistence.Table;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;

View File

@ -29,10 +29,6 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.search.engine.backend.types.Projectable;
import org.hibernate.search.engine.backend.types.Searchable;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Embeddable; import javax.persistence.Embeddable;
@ -132,8 +128,20 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
myHashNormalizedPrefix = source.myHashNormalizedPrefix; myHashNormalizedPrefix = source.myHashNormalizedPrefix;
} }
@Override
public void clearHashes() {
myHashIdentity = null;
myHashNormalizedPrefix = null;
myHashExact = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null || myHashExact != null || myHashNormalizedPrefix != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
String valueNormalized = getValueNormalized(); String valueNormalized = getValueNormalized();
@ -245,6 +253,7 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
b.append("resourceId", getResourcePid()); b.append("resourceId", getResourcePid());
b.append("hashNormalizedPrefix", getHashNormalizedPrefix()); b.append("hashNormalizedPrefix", getHashNormalizedPrefix());
b.append("valueNormalized", getValueNormalized()); b.append("valueNormalized", getValueNormalized());
b.append("partitionId", getPartitionId());
return b.build(); return b.build();
} }
@ -284,7 +293,8 @@ public class ResourceIndexedSearchParamString extends BaseResourceIndexedSearchP
hashPrefixLength = 0; hashPrefixLength = 0;
} }
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, StringUtil.left(theValueNormalized, hashPrefixLength)); String value = StringUtil.left(theValueNormalized, hashPrefixLength);
return hash(thePartitionSettings, theRequestPartitionId, theResourceType, theParamName, value);
} }
} }

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
@ -54,7 +55,7 @@ import static org.apache.commons.lang3.StringUtils.trim;
* IDX_SP_TOKEN_UNQUAL * IDX_SP_TOKEN_UNQUAL
*/ */
// TODO PERF Recommend to drop this index: // TODO PERF Recommend to drop this index (added by JA - I don't actually think we even need the identity hash for this type, we could potentially drop the column too):
@Index(name = "IDX_SP_TOKEN_HASH", columnList = "HASH_IDENTITY"), @Index(name = "IDX_SP_TOKEN_HASH", columnList = "HASH_IDENTITY"),
@Index(name = "IDX_SP_TOKEN_HASH_S", columnList = "HASH_SYS"), @Index(name = "IDX_SP_TOKEN_HASH_S", columnList = "HASH_SYS"),
@Index(name = "IDX_SP_TOKEN_HASH_SV", columnList = "HASH_SYS_AND_VALUE"), @Index(name = "IDX_SP_TOKEN_HASH_SV", columnList = "HASH_SYS_AND_VALUE"),
@ -140,18 +141,36 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
myHashIdentity = source.myHashIdentity; myHashIdentity = source.myHashIdentity;
} }
@Override
public void clearHashes() {
myHashIdentity = null;
myHashSystem = null;
myHashSystemAndValue = null;
myHashValue = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null || myHashSystem != null || myHashValue != null || myHashSystemAndValue != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
String system = getSystem(); String system = getSystem();
String value = getValue(); String value = getValue();
setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName)); setHashIdentity(calculateHashIdentity(getPartitionSettings(), getPartitionId(), resourceType, paramName));
setHashSystem(calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system));
setHashSystemAndValue(calculateHashSystemAndValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value)); setHashSystemAndValue(calculateHashSystemAndValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, system, value));
// Searches using the :of-type modifier can never be partial (system-only or value-only) so don't
// bother saving these
boolean calculatePartialHashes = !StringUtils.endsWith(paramName, Constants.PARAMQUALIFIER_TOKEN_OF_TYPE);
if (calculatePartialHashes) {
setHashSystem(calculateHashSystem(getPartitionSettings(), getPartitionId(), resourceType, paramName, system));
setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value)); setHashValue(calculateHashValue(getPartitionSettings(), getPartitionId(), resourceType, paramName, value));
} }
}
@Override @Override
public boolean equals(Object theObj) { public boolean equals(Object theObj) {
@ -172,7 +191,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return b.isEquals(); return b.isEquals();
} }
Long getHashSystem() { public Long getHashSystem() {
return myHashSystem; return myHashSystem;
} }
@ -216,6 +235,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public void setSystem(String theSystem) { public void setSystem(String theSystem) {
mySystem = StringUtils.defaultIfBlank(theSystem, null); mySystem = StringUtils.defaultIfBlank(theSystem, null);
myHashSystemAndValue = null;
} }
public String getValue() { public String getValue() {
@ -224,6 +244,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
public ResourceIndexedSearchParamToken setValue(String theValue) { public ResourceIndexedSearchParamToken setValue(String theValue) {
myValue = StringUtils.defaultIfBlank(theValue, null); myValue = StringUtils.defaultIfBlank(theValue, null);
myHashSystemAndValue = null;
return this; return this;
} }
@ -261,6 +282,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
b.append("hashSystem", myHashSystem); b.append("hashSystem", myHashSystem);
b.append("hashValue", myHashValue); b.append("hashValue", myHashValue);
b.append("hashSysAndValue", myHashSystemAndValue); b.append("hashSysAndValue", myHashSystemAndValue);
b.append("partition", getPartitionId());
return b.build(); return b.build();
} }
@ -292,6 +314,7 @@ public class ResourceIndexedSearchParamToken extends BaseResourceIndexedSearchPa
return retVal; return retVal;
} }
public static long calculateHashSystem(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) { public static long calculateHashSystem(PartitionSettings thePartitionSettings, PartitionablePartitionId theRequestPartitionId, String theResourceType, String theParamName, String theSystem) {
RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId); RequestPartitionId requestPartitionId = PartitionablePartitionId.toRequestPartitionId(theRequestPartitionId);
return calculateHashSystem(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem); return calculateHashSystem(thePartitionSettings, requestPartitionId, theResourceType, theParamName, theSystem);

View File

@ -107,9 +107,19 @@ public class ResourceIndexedSearchParamUri extends BaseResourceIndexedSearchPara
myHashIdentity = source.myHashIdentity; myHashIdentity = source.myHashIdentity;
} }
@Override
public void clearHashes() {
myHashIdentity = null;
myHashUri = null;
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
if (myHashIdentity != null || myHashUri != null) {
return;
}
String resourceType = getResourceType(); String resourceType = getResourceType();
String paramName = getParamName(); String paramName = getParamName();
String uri = getUri(); String uri = getUri();

View File

@ -235,6 +235,11 @@ public class ResourceLink extends BaseResourceIndex {
myId = theId; myId = theId;
} }
@Override
public void clearHashes() {
// nothing right now
}
@Override @Override
public void calculateHashes() { public void calculateHashes() {
// nothing right now // nothing right now

View File

@ -42,6 +42,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil; import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
@ -88,7 +89,6 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.strip;
import static org.apache.commons.lang3.StringUtils.trim; import static org.apache.commons.lang3.StringUtils.trim;
public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { public abstract class BaseSearchParamExtractor implements ISearchParamExtractor {
@ -642,20 +642,35 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase)); high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
} }
@SuppressWarnings("unchecked")
private void addToken_Identifier(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { private void addToken_Identifier(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
String system = extractValueAsString(myIdentifierSystemValueChild, theValue); String system = extractValueAsString(myIdentifierSystemValueChild, theValue);
String value = extractValueAsString(myIdentifierValueValueChild, theValue); String value = extractValueAsString(myIdentifierValueValueChild, theValue);
if (isNotBlank(value)) { if (isNotBlank(value)) {
createTokenIndexIfNotBlank(theResourceType, theParams, theSearchParam, system, value); createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value);
}
if (shouldIndexTextComponentOfToken(theSearchParam)) { boolean indexIdentifierType = myModelConfig.isIndexIdentifierOfType();
if (indexIdentifierType) {
Optional<IBase> type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue); Optional<IBase> type = myIdentifierTypeValueChild.getAccessor().getFirstValueOrNull(theValue);
if (type.isPresent()) { if (type.isPresent()) {
String text = extractValueAsString(myIdentifierTypeTextValueChild, type.get()); List<IBase> codings = myCodeableConceptCodingValueChild.getAccessor().getValues(type.get());
createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, text); for (IBase nextCoding : codings) {
String typeSystem = myCodingSystemValueChild.getAccessor().getFirstValueOrNull(nextCoding).map(t -> ((IPrimitiveType<String>) t).getValue()).orElse(null);
String typeValue = myCodingCodeValueChild.getAccessor().getFirstValueOrNull(nextCoding).map(t -> ((IPrimitiveType<String>) t).getValue()).orElse(null);
if (isNotBlank(typeSystem) && isNotBlank(typeValue)) {
String paramName = theSearchParam.getName() + Constants.PARAMQUALIFIER_TOKEN_OF_TYPE;
ResourceIndexedSearchParamToken token = createTokenIndexIfNotBlank(theResourceType, typeSystem, typeValue + "|" + value, paramName);
if (token != null) {
theParams.add(token);
} }
} }
}
}
}
}
} }
protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) { protected boolean shouldIndexTextComponentOfToken(RuntimeSearchParam theSearchParam) {
@ -733,7 +748,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
private void addToken_ContactPoint(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { private void addToken_ContactPoint(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
String system = extractValueAsString(myContactPointSystemValueChild, theValue); String system = extractValueAsString(myContactPointSystemValueChild, theValue);
String value = extractValueAsString(myContactPointValueValueChild, theValue); String value = extractValueAsString(myContactPointValueValueChild, theValue);
createTokenIndexIfNotBlank(theResourceType, theParams, theSearchParam, system, value); createTokenIndexIfNotBlankAndAdd(theResourceType, theParams, theSearchParam, system, value);
} }
private void addToken_PatientCommunication(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) { private void addToken_PatientCommunication(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
@ -1062,7 +1077,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
} }
} }
private void createTokenIndexIfNotBlank(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theSystem, String theValue) { private void createTokenIndexIfNotBlankAndAdd(String theResourceType, Set<BaseResourceIndexedSearchParam> theParams, RuntimeSearchParam theSearchParam, String theSystem, String theValue) {
ResourceIndexedSearchParamToken nextEntity = createTokenIndexIfNotBlank(theResourceType, theSearchParam, theSystem, theValue); ResourceIndexedSearchParamToken nextEntity = createTokenIndexIfNotBlank(theResourceType, theSearchParam, theSystem, theValue);
if (nextEntity != null) { if (nextEntity != null) {
theParams.add(nextEntity); theParams.add(nextEntity);
@ -1075,6 +1090,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
} }
private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(String theResourceType, RuntimeSearchParam theSearchParam, String theSystem, String theValue) { private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(String theResourceType, RuntimeSearchParam theSearchParam, String theSystem, String theValue) {
String searchParamName = theSearchParam.getName();
return createTokenIndexIfNotBlank(theResourceType, theSystem, theValue, searchParamName);
}
private ResourceIndexedSearchParamToken createTokenIndexIfNotBlank(String theResourceType, String theSystem, String theValue, String searchParamName) {
String system = theSystem; String system = theSystem;
String value = theValue; String value = theValue;
ResourceIndexedSearchParamToken nextEntity = null; ResourceIndexedSearchParamToken nextEntity = null;
@ -1086,7 +1106,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
value = value.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH); value = value.substring(0, ResourceIndexedSearchParamToken.MAX_LENGTH);
} }
nextEntity = new ResourceIndexedSearchParamToken(myPartitionSettings, theResourceType, theSearchParam.getName(), system, value); nextEntity = new ResourceIndexedSearchParamToken(myPartitionSettings, theResourceType, searchParamName, system, value);
} }
return nextEntity; return nextEntity;
@ -1239,6 +1259,93 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
} }
/**
* Note that this should only be called for R4+ servers. Prior to
* R4 the paths could be separated by the word "or" or by a "|"
* character, so we used a slower splitting mechanism.
*/
@Nonnull
public static String[] splitPathsR4(@Nonnull String thePaths) {
StringTokenizer tok = new StringTokenizer(thePaths, " |");
tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher());
return tok.getTokenArray();
}
public static boolean tokenTextIndexingEnabledForSearchParam(ModelConfig theModelConfig, RuntimeSearchParam theSearchParam) {
Optional<Boolean> noSuppressForSearchParam = theSearchParam.getExtensions(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream()
.map(IBaseExtension::getValue)
.map(val -> (IPrimitiveType<?>) val)
.map(IPrimitiveType::getValueAsString)
.map(Boolean::parseBoolean)
.findFirst();
//if the SP doesn't care, use the system default.
if (!noSuppressForSearchParam.isPresent()) {
return !theModelConfig.isSuppressStringIndexingInTokens();
//If the SP does care, use its value.
} else {
boolean suppressForSearchParam = noSuppressForSearchParam.get();
ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam);
return !suppressForSearchParam;
}
}
private static void addIgnoredType(FhirContext theCtx, String theType, Set<String> theIgnoredTypes) {
BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {
theIgnoredTypes.add(elementDefinition.getName());
}
}
protected static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<?>>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValueAsString)
.orElse(null);
}
protected static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<Date>>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValue)
.orElse(null);
}
protected static BigDecimal extractValueAsBigDecimal(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<BigDecimal>>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValue)
.orElse(null);
}
@SuppressWarnings("unchecked")
protected static List<IPrimitiveType<Date>> extractValuesAsFhirDates(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return (List) theChildDefinition
.getAccessor()
.getValues(theElement);
}
protected static List<String> extractValuesAsStrings(BaseRuntimeChildDefinition theChildDefinition, IBase theValue) {
return theChildDefinition
.getAccessor()
.getValues(theValue)
.stream()
.map(t -> (IPrimitiveType) t)
.map(IPrimitiveType::getValueAsString)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
}
protected static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
}
private class ResourceLinkExtractor implements IExtractor<PathAndRef> { private class ResourceLinkExtractor implements IExtractor<PathAndRef> {
private PathAndRef myPathAndRef = null; private PathAndRef myPathAndRef = null;
@ -1459,7 +1566,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value; IBaseEnumeration<?> obj = (IBaseEnumeration<?>) value;
String system = extractSystem(obj); String system = extractSystem(obj);
String code = obj.getValueAsString(); String code = obj.getValueAsString();
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code); BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, system, code);
return; return;
} }
@ -1473,7 +1580,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
system = boundCode.getBinder().toSystemString(valueAsEnum); system = boundCode.getBinder().toSystemString(valueAsEnum);
} }
String code = boundCode.getValueAsString(); String code = boundCode.getValueAsString();
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code); BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, system, code);
return; return;
} }
@ -1491,7 +1598,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
valueAsString = ((IIdType) value).getIdPart(); valueAsString = ((IIdType) value).getIdPart();
} }
BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, systemAsString, valueAsString); BaseSearchParamExtractor.this.createTokenIndexIfNotBlankAndAdd(myResourceTypeName, params, searchParam, systemAsString, valueAsString);
return; return;
} }
@ -1552,87 +1659,4 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
} }
} }
@Nonnull
public static String[] splitPathsR4(@Nonnull String thePaths) {
StringTokenizer tok = new StringTokenizer(thePaths, " |");
tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher());
return tok.getTokenArray();
}
public static boolean tokenTextIndexingEnabledForSearchParam(ModelConfig theModelConfig, RuntimeSearchParam theSearchParam) {
Optional<Boolean> noSuppressForSearchParam = theSearchParam.getExtensions(HapiExtensions.EXT_SEARCHPARAM_TOKEN_SUPPRESS_TEXT_INDEXING).stream()
.map(IBaseExtension::getValue)
.map(val -> (IPrimitiveType<?>) val)
.map(IPrimitiveType::getValueAsString)
.map(Boolean::parseBoolean)
.findFirst();
//if the SP doesn't care, use the system default.
if (!noSuppressForSearchParam.isPresent()) {
return !theModelConfig.isSuppressStringIndexingInTokens();
//If the SP does care, use its value.
} else {
boolean suppressForSearchParam = noSuppressForSearchParam.get();
ourLog.trace("Text indexing for SearchParameter {}: {}", theSearchParam.getName(), suppressForSearchParam);
return !suppressForSearchParam;
}
}
private static void addIgnoredType(FhirContext theCtx, String theType, Set<String> theIgnoredTypes) {
BaseRuntimeElementDefinition<?> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {
theIgnoredTypes.add(elementDefinition.getName());
}
}
private static String extractValueAsString(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<?>>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValueAsString)
.orElse(null);
}
private static Date extractValueAsDate(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<Date>>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValue)
.orElse(null);
}
private static BigDecimal extractValueAsBigDecimal(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return theChildDefinition
.getAccessor()
.<IPrimitiveType<BigDecimal>>getFirstValueOrNull(theElement)
.map(IPrimitiveType::getValue)
.orElse(null);
}
@SuppressWarnings("unchecked")
private static List<IPrimitiveType<Date>> extractValuesAsFhirDates(BaseRuntimeChildDefinition theChildDefinition, IBase theElement) {
return (List) theChildDefinition
.getAccessor()
.getValues(theElement);
}
private static List<String> extractValuesAsStrings(BaseRuntimeChildDefinition theChildDefinition, IBase theValue) {
return theChildDefinition
.getAccessor()
.getValues(theValue)
.stream()
.map(t -> (IPrimitiveType) t)
.map(IPrimitiveType::getValueAsString)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
}
private static <T extends Enum<?>> String extractSystem(IBaseEnumeration<T> theBoundCode) {
if (theBoundCode.getValue() != null) {
return theBoundCode.getEnumFactory().toSystem(theBoundCode.getValue());
}
return null;
}
} }

View File

@ -22,17 +22,16 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/ */
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBase;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; 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.ResourceIndexedSearchParamUri;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.utils.FHIRPathEngine;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;

View File

@ -147,15 +147,15 @@ public final class ResourceIndexedSearchParams {
theEntity.setResourceLinks(myLinks); theEntity.setResourceLinks(myLinks);
} }
public void updateSpnamePrefixForIndexedOnContainedResource(String theSpnamePrefix) { public void updateSpnamePrefixForIndexedOnContainedResource(String theContainingType, String theSpnamePrefix) {
updateSpnamePrefixForIndexedOnContainedResource(myNumberParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myNumberParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myQuantityParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myQuantityParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myQuantityNormalizedParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myQuantityNormalizedParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myDateParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myDateParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myUriParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myUriParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myTokenParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myTokenParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myStringParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myStringParams, theSpnamePrefix);
updateSpnamePrefixForIndexedOnContainedResource(myCoordsParams, theSpnamePrefix); updateSpnamePrefixForIndexedOnContainedResource(theContainingType, myCoordsParams, theSpnamePrefix);
} }
public void updateSpnamePrefixForLinksOnContainedResource(String theSpNamePrefix) { public void updateSpnamePrefixForLinksOnContainedResource(String theSpNamePrefix) {
@ -190,11 +190,14 @@ public final class ResourceIndexedSearchParams {
} }
} }
private void updateSpnamePrefixForIndexedOnContainedResource(Collection<? extends BaseResourceIndexedSearchParam> theParams, @Nonnull String theSpnamePrefix) { private void updateSpnamePrefixForIndexedOnContainedResource(String theContainingType, Collection<? extends BaseResourceIndexedSearchParam> theParams, @Nonnull String theSpnamePrefix) {
for (BaseResourceIndexedSearchParam param : theParams) { for (BaseResourceIndexedSearchParam param : theParams) {
param.setResourceType(theContainingType);
param.setParamName(theSpnamePrefix + "." + param.getParamName()); param.setParamName(theSpnamePrefix + "." + param.getParamName());
param.calculateHashes(); // re-calculuteHashes
// re-calculate hashes
param.calculateHashes();
} }
} }

View File

@ -43,16 +43,15 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 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.util.FhirTerser; import ca.uhn.fhir.util.FhirTerser;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseReference;
@ -176,7 +175,7 @@ public class SearchParamExtractorService {
// 3.5 added reference name as a prefix for the contained resource if any // 3.5 added reference name as a prefix for the contained resource if any
// e.g. for Observation.subject contained reference // e.g. for Observation.subject contained reference
// the SP_NAME = subject.family // the SP_NAME = subject.family
currParams.updateSpnamePrefixForIndexedOnContainedResource(spnamePrefix); currParams.updateSpnamePrefixForIndexedOnContainedResource(theEntity.getResourceType(), spnamePrefix);
// 3.6 merge to the mainParams // 3.6 merge to the mainParams
// NOTE: the spname prefix is different // NOTE: the spname prefix is different
@ -239,7 +238,8 @@ public class SearchParamExtractorService {
// Tokens (can result in both Token and String, as we index the display name for // Tokens (can result in both Token and String, as we index the display name for
// the types: Coding, CodeableConcept) // the types: Coding, CodeableConcept)
for (BaseResourceIndexedSearchParam next : extractSearchParamTokens(theResource)) { ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> tokens = extractSearchParamTokens(theResource);
for (BaseResourceIndexedSearchParam next : tokens) {
if (next instanceof ResourceIndexedSearchParamToken) { if (next instanceof ResourceIndexedSearchParamToken) {
theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next); theParams.myTokenParams.add((ResourceIndexedSearchParamToken) next);
} else if (next instanceof ResourceIndexedSearchParamCoords) { } else if (next instanceof ResourceIndexedSearchParamCoords) {
@ -250,7 +250,8 @@ public class SearchParamExtractorService {
} }
// Specials // Specials
for (BaseResourceIndexedSearchParam next : extractSearchParamSpecial(theResource)) { ISearchParamExtractor.SearchParamSet<BaseResourceIndexedSearchParam> specials = extractSearchParamSpecial(theResource);
for (BaseResourceIndexedSearchParam next : specials) {
if (next instanceof ResourceIndexedSearchParamCoords) { if (next instanceof ResourceIndexedSearchParamCoords) {
theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next); theParams.myCoordsParams.add((ResourceIndexedSearchParamCoords) next);
} }
@ -437,7 +438,7 @@ public class SearchParamExtractorService {
// 2. Find referenced search parameters // 2. Find referenced search parameters
ISearchParamExtractor.SearchParamSet<PathAndRef> referencedSearchParamSet = mySearchParamExtractor.extractResourceLinks(theResource, true); ISearchParamExtractor.SearchParamSet<PathAndRef> referencedSearchParamSet = mySearchParamExtractor.extractResourceLinks(theResource, true);
String spNamePrefix = null; String spNamePrefix;
ResourceIndexedSearchParams currParams; ResourceIndexedSearchParams currParams;
// 3. for each referenced search parameter, create an index // 3. for each referenced search parameter, create an index
for (PathAndRef nextPathAndRef : referencedSearchParamSet) { for (PathAndRef nextPathAndRef : referencedSearchParamSet) {
@ -482,6 +483,7 @@ public class SearchParamExtractorService {
} }
private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails) { private ResourceLink resolveTargetAndCreateResourceLinkOrReturnNull(@Nonnull RequestPartitionId theRequestPartitionId, ResourceTable theEntity, Date theUpdateTime, RuntimeSearchParam nextSpDef, String theNextPathsUnsplit, PathAndRef nextPathAndRef, IIdType theNextId, String theTypeString, Class<? extends IBaseResource> theType, IBaseReference theReference, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
assert theRequestPartitionId != null;
ResourcePersistentId resolvedResourceId = theTransactionDetails.getResolvedResourceId(theNextId); ResourcePersistentId resolvedResourceId = theTransactionDetails.getResolvedResourceId(theNextId);
if (resolvedResourceId != null) { if (resolvedResourceId != null) {

View File

@ -30,10 +30,24 @@ import java.util.Date;
public interface IJpaDao<T extends IBaseResource> { public interface IJpaDao<T extends IBaseResource> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
IBasePersistedResource updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource IBasePersistedResource updateEntity(
theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, RequestDetails theRequest,
boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry); IBaseResource theResource,
IBasePersistedResource theEntity,
Date theDeletedTimestampOrNull,
boolean thePerformIndexing,
boolean theUpdateVersion,
TransactionDetails theTransactionDetails,
boolean theForceUpdate,
boolean theCreateNewHistoryEntry);
IBasePersistedResource updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, IBasePersistedResource updateInternal(
IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails); RequestDetails theRequestDetails,
T theResource,
boolean thePerformIndexing,
boolean theForceUpdateVersion,
IBasePersistedResource theEntity,
IIdType theResourceId,
IBaseResource theOldResource,
TransactionDetails theTransactionDetails);
} }

View File

@ -259,7 +259,7 @@ public abstract class BaseStorageDao {
* <p> * <p>
* We only do this if thePerformIndexing is true because if it's false, that means * We only do this if thePerformIndexing is true because if it's false, that means
* we're in a FHIR transaction during the first phase of write operation processing, * we're in a FHIR transaction during the first phase of write operation processing,
* meaning that the versions of other resources may not have need updated yet. For example * meaning that the versions of other resources may not have need updatd yet. For example
* we're about to store an Observation with a reference to a Patient, and that Patient * we're about to store an Observation with a reference to a Patient, and that Patient
* is also being updated in the same transaction, during the first "no index" phase, * is also being updated in the same transaction, during the first "no index" phase,
* the Patient will not yet have its version number incremented, so it would be wrong * the Patient will not yet have its version number incremented, so it would be wrong

View File

@ -1,10 +1,17 @@
package ca.uhn.fhir.rest.param; package ca.uhn.fhir.rest.param;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TokenParamTest { public class TokenParamTest {
private static final FhirContext ourCtx = FhirContext.forR4Cached();
@Test @Test
public void testEquals() { public void testEquals() {
TokenParam tokenParam1 = new TokenParam("foo", "bar"); TokenParam tokenParam1 = new TokenParam("foo", "bar");
@ -32,4 +39,13 @@ public class TokenParamTest {
assertEquals("", new TokenParam().getValueNotNull()); assertEquals("", new TokenParam().getValueNotNull());
} }
@Test
public void testOfType() {
TokenParam param = new TokenParam();
param.setValueAsQueryToken(ourCtx, "identifier", Constants.PARAMQUALIFIER_TOKEN_OF_TYPE, "http://type-system|type-value|identifier-value");
assertEquals(TokenParamModifier.OF_TYPE, param.getModifier());
assertEquals("http://type-system", param.getSystem());
assertEquals("type-value|identifier-value", param.getValue());
}
} }