Merge remote-tracking branch 'origin/master' into issue-2445-support-patient-level-export
This commit is contained in:
commit
e26d757ba3
|
@ -267,6 +267,7 @@ public class Constants {
|
|||
*/
|
||||
public static final String RESOURCE_PARTITION_ID = Constants.class.getName() + "_RESOURCE_PARTITION_ID";
|
||||
public static final String CT_APPLICATION_GZIP = "application/gzip";
|
||||
public static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
static {
|
||||
CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
package ca.uhn.fhir.rest.api;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Search Parameters
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum SearchContainedModeEnum {
|
||||
|
||||
/**
|
||||
* default, search on the non-contained (normal) resources
|
||||
*/
|
||||
FALSE("false"),
|
||||
|
||||
/**
|
||||
* search on the contained resources only
|
||||
*/
|
||||
TRUE("true"),
|
||||
|
||||
/**
|
||||
* Search on the normal resources and contained resources.
|
||||
* This option is not supported yet.
|
||||
*/
|
||||
BOTH("both");
|
||||
|
||||
private static volatile Map<String, SearchContainedModeEnum> ourCodeToEnum;
|
||||
private final String myCode;
|
||||
|
||||
SearchContainedModeEnum(String theCode) {
|
||||
myCode = theCode;
|
||||
}
|
||||
|
||||
private String getCode() {
|
||||
return myCode;
|
||||
}
|
||||
|
||||
public static SearchContainedModeEnum fromCode(String theCode) {
|
||||
Map<String, SearchContainedModeEnum> codeToEnum = ourCodeToEnum;
|
||||
if (codeToEnum == null) {
|
||||
codeToEnum = new HashMap<>();
|
||||
for (SearchContainedModeEnum next : values()) {
|
||||
codeToEnum.put(next.getCode(), next);
|
||||
}
|
||||
ourCodeToEnum = codeToEnum;
|
||||
}
|
||||
|
||||
SearchContainedModeEnum retVal = codeToEnum.get(theCode);
|
||||
if (retVal == null) {
|
||||
throw new InvalidRequestException("Invalid contained mode: " + UrlUtil.sanitizeUrlPart(theCode));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
|
@ -174,7 +174,7 @@ public interface IGenericClient extends IRestfulClient {
|
|||
void unregisterInterceptor(Object theInterceptor);
|
||||
|
||||
/**
|
||||
* Fluent method for the "update" operation, which performs a logical delete on a server resource
|
||||
* Fluent method for the "update" operation, which updates a resource instance on the server
|
||||
*/
|
||||
IUpdate update();
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
||||
/*
|
||||
|
@ -22,11 +27,6 @@ import static org.apache.commons.lang3.StringUtils.defaultString;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
/**
|
||||
* Implementation of the _has method parameter
|
||||
*/
|
||||
|
@ -55,7 +55,7 @@ public class HasParam extends BaseParam implements IQueryParameterType {
|
|||
|
||||
@Override
|
||||
String doGetQueryParameterQualifier() {
|
||||
return myTargetResourceType + ':' + myParameterName + ':' + myParameterValue;
|
||||
return ':' + myTargetResourceType + ':' + myReferenceFieldName + ':' + myParameterName;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -109,8 +109,7 @@ public class HapiExtensions {
|
|||
/**
|
||||
* URL for boolean extension added to all placeholder resources
|
||||
*/
|
||||
public static final String EXT_RESOURCE_META_PLACEHOLDER = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-placeholder";
|
||||
|
||||
public static final String EXT_RESOURCE_PLACEHOLDER = "http://hapifhir.io/fhir/StructureDefinition/resource-placeholder";
|
||||
|
||||
/**
|
||||
* Non instantiable
|
||||
|
|
|
@ -25,7 +25,6 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
|
|||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.search.HapiLuceneAnalysisConfigurer;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.search.backend.lucene.cfg.LuceneBackendSettings;
|
||||
import org.hibernate.search.backend.lucene.cfg.LuceneIndexSettings;
|
||||
|
@ -49,9 +48,6 @@ public class CommonConfig {
|
|||
@Bean
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setSubscriptionEnabled(true);
|
||||
retVal.setSubscriptionPollDelay(5000);
|
||||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -59,9 +59,6 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
|||
@Bean
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setSubscriptionEnabled(true);
|
||||
retVal.setSubscriptionPollDelay(5000);
|
||||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
return retVal;
|
||||
}
|
||||
|
@ -79,7 +76,6 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
|||
|
||||
/**
|
||||
* Do some fancy logging to create a nice access log that has details about each incoming request.
|
||||
* @return
|
||||
*/
|
||||
public LoggingInterceptor loggingInterceptor() {
|
||||
LoggingInterceptor retVal = new LoggingInterceptor();
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1953
|
||||
title: "A crash was fixed when performing a FHIR read on a partitioned server, where the requested ID is not known. Thanks
|
||||
to Umberto Cappellini for reporting!"
|
|
@ -1,5 +1,4 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2332
|
||||
title: "All created placeholder resources now have a meta extension with the url http://hapifhir.io/fhir/StructureDefinition/resource-meta-placeholder
|
||||
and the value 'true'. Also, terminology storage is now skipped for placeholder ValueSet and ConceptMap resources."
|
||||
title: "Terminology storage is now skipped for placeholder ValueSet and ConceptMap resources."
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2403
|
||||
title: "Optionally support '_contained' resource search by enabling the indexing on the contained resources in the ModelConfig."
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2446
|
||||
title: "Auto-created placeholder reference targets now have an extension with the URL
|
||||
`http://hapifhir.io/fhir/StructureDefinition/resource-placeholder` and a value of `true`."
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: change
|
||||
issue: 2446
|
||||
title: "DaoConfig setting for [Populate Identifier In Auto Created Placeholder Reference Targets](https://hapifhir.io/hapi-fhir/apidocs/hapi-fhir-jpaserver-api/ca/uhn/fhir/jpa/api/config/DaoConfig.html#setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(boolean))
|
||||
now defaults to `true`."
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
type: security
|
||||
issue: 2454
|
||||
title: "Addressed the following CVE report by bumping the minor version for Jetty in the root POM:
|
||||
<ul>
|
||||
<li>
|
||||
[CVE-2020-27223](https://nvd.nist.gov/vuln/detail/CVE-2020-27223)
|
||||
</li>
|
||||
</ul>"
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 2458
|
||||
title: "`HasParam#doGetQueryParameterQualifier()` returned a malformed modifier. For example, the modifier for
|
||||
`_has:Observation:patient:code=123` was returned as `Observation:code:123` when it should be
|
||||
`:Observation:patient:code`. This has been corrected."
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2441
|
||||
title: "Support has been added to the JPA server for indexing and searching using the `_contained` parameter, which
|
||||
allows searching using chained parameters that chain into contained resources. This feature is disabled by default
|
||||
but can be enabled via a setting on the ModelConfig object."
|
|
@ -118,11 +118,6 @@ public class DaoConfig {
|
|||
* update setter javadoc if default changes
|
||||
*/
|
||||
private Integer myFetchSizeDefaultMaximum = null;
|
||||
private int myHardTagListLimit = 1000;
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
private boolean myIndexContainedResources = true;
|
||||
private int myMaximumExpansionSize = DEFAULT_MAX_EXPANSION_SIZE;
|
||||
private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
|
||||
|
||||
|
@ -186,9 +181,11 @@ public class DaoConfig {
|
|||
private int myPreExpandValueSetsMaxCount = 1000;
|
||||
|
||||
/**
|
||||
* Do not change default of {@code true}!
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets;
|
||||
private boolean myPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets = true;
|
||||
|
||||
/**
|
||||
* @since 5.0.0
|
||||
|
@ -207,9 +204,6 @@ public class DaoConfig {
|
|||
* Constructor
|
||||
*/
|
||||
public DaoConfig() {
|
||||
setSubscriptionEnabled(true);
|
||||
setSubscriptionPollDelay(0);
|
||||
setSubscriptionPurgeInactiveAfterMillis(Long.MAX_VALUE);
|
||||
setMarkResourcesForReindexingUponSearchParameterChange(true);
|
||||
setReindexThreadCount(Runtime.getRuntime().availableProcessors());
|
||||
setExpungeThreadCount(Runtime.getRuntime().availableProcessors());
|
||||
|
@ -578,20 +572,6 @@ public class DaoConfig {
|
|||
myFetchSizeDefaultMaximum = theFetchSizeDefaultMaximum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of results to return in a GetTags query (DSTU1 only)
|
||||
*/
|
||||
public int getHardTagListLimit() {
|
||||
return myHardTagListLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of results to return in a GetTags query (DSTU1 only)
|
||||
*/
|
||||
public void setHardTagListLimit(int theHardTagListLimit) {
|
||||
myHardTagListLimit = theHardTagListLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to {@link IndexEnabledEnum#DISABLED} (default is {@link IndexEnabledEnum#DISABLED})
|
||||
* the server will not create search indexes for search parameters with no values in resources.
|
||||
|
@ -1068,8 +1048,8 @@ public class DaoConfig {
|
|||
* Note however that references containing purely numeric IDs will not be auto-created
|
||||
* as they are never allowed to be client supplied in HAPI FHIR JPA.
|
||||
*
|
||||
* All placeholder resources created in this way have a meta extension
|
||||
* with the URL {@link HapiExtensions#EXT_RESOURCE_META_PLACEHOLDER} and the value "true".
|
||||
* All placeholder resources created in this way have an extension
|
||||
* with the URL {@link HapiExtensions#EXT_RESOURCE_PLACEHOLDER} and the value "true".
|
||||
* </p>
|
||||
*/
|
||||
public boolean isAutoCreatePlaceholderReferenceTargets() {
|
||||
|
@ -1092,8 +1072,8 @@ public class DaoConfig {
|
|||
* Note however that references containing purely numeric IDs will not be auto-created
|
||||
* as they are never allowed to be client supplied in HAPI FHIR JPA.
|
||||
*
|
||||
* All placeholder resources created in this way have a meta extension
|
||||
* with the URL {@link HapiExtensions#EXT_RESOURCE_META_PLACEHOLDER} and the value "true".
|
||||
* All placeholder resources created in this way have an extension
|
||||
* with the URL {@link HapiExtensions#EXT_RESOURCE_PLACEHOLDER} and the value "true".
|
||||
* </p>
|
||||
*/
|
||||
public void setAutoCreatePlaceholderReferenceTargets(boolean theAutoCreatePlaceholderReferenceTargets) {
|
||||
|
@ -1102,7 +1082,7 @@ public class DaoConfig {
|
|||
|
||||
/**
|
||||
* When {@link #setAutoCreatePlaceholderReferenceTargets(boolean)} is enabled, if this
|
||||
* setting is set to <code>true</code> (default is <code>false</code>) and the source
|
||||
* setting is set to <code>true</code> (default is <code>true</code>) and the source
|
||||
* reference has an identifier populated, the identifier will be copied to the target
|
||||
* resource.
|
||||
* <p>
|
||||
|
@ -1144,6 +1124,41 @@ public class DaoConfig {
|
|||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Note that the default for this setting was previously <code>false</code>, and was changed to <code>true</code>
|
||||
* in 5.4.0 with consideration to the following:
|
||||
* </p>
|
||||
* <pre>
|
||||
* CP = Auto-Create Placeholder Reference Targets
|
||||
* PI = Populate Identifier in Auto-Created Placeholder Reference Targets
|
||||
*
|
||||
* CP | PI
|
||||
* -------
|
||||
* F | F <- PI=F is ignored
|
||||
* F | T <- PI=T is ignored
|
||||
* T | F <- resources may reference placeholder reference targets that are never updated : (
|
||||
* T | T <- placeholder reference targets can be updated : )
|
||||
* </pre>
|
||||
* <p>
|
||||
* Where CP=T and PI=F, the following could happen:
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>
|
||||
* Resource instance A is created with a reference to resource instance B. B is a placeholder reference target
|
||||
* without an identifier.
|
||||
* </li>
|
||||
* <li>
|
||||
* Resource instance C is conditionally created using a match URL. It is not matched to B although these
|
||||
* resources represent the same entity.
|
||||
* </li>
|
||||
* <li>
|
||||
* A continues to reference placeholder B, and does not reference populated C.
|
||||
* </li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* There may be cases where configuring this setting to <code>false</code> would be appropriate; however, these are
|
||||
* exceptional cases that should be opt-in.
|
||||
* </p>
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
|
@ -1153,7 +1168,7 @@ public class DaoConfig {
|
|||
|
||||
/**
|
||||
* When {@link #setAutoCreatePlaceholderReferenceTargets(boolean)} is enabled, if this
|
||||
* setting is set to <code>true</code> (default is <code>false</code>) and the source
|
||||
* setting is set to <code>true</code> (default is <code>true</code>) and the source
|
||||
* reference has an identifier populated, the identifier will be copied to the target
|
||||
* resource.
|
||||
* <p>
|
||||
|
@ -1195,6 +1210,41 @@ public class DaoConfig {
|
|||
* }
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Note that the default for this setting was previously <code>false</code>, and was changed to <code>true</code>
|
||||
* in 5.4.0 with consideration to the following:
|
||||
* </p>
|
||||
* <pre>
|
||||
* CP = Auto-Create Placeholder Reference Targets
|
||||
* PI = Populate Identifier in Auto-Created Placeholder Reference Targets
|
||||
*
|
||||
* CP | PI
|
||||
* -------
|
||||
* F | F <- PI=F is ignored
|
||||
* F | T <- PI=T is ignored
|
||||
* T | F <- resources may reference placeholder reference targets that are never updated : (
|
||||
* T | T <- placeholder reference targets can be updated : )
|
||||
* </pre>
|
||||
* <p>
|
||||
* Where CP=T and PI=F, the following could happen:
|
||||
* </p>
|
||||
* <ol>
|
||||
* <li>
|
||||
* Resource instance A is created with a reference to resource instance B. B is a placeholder reference target
|
||||
* without an identifier.
|
||||
* </li>
|
||||
* <li>
|
||||
* Resource instance C is conditionally created using a match URL. It is not matched to B although these
|
||||
* resources represent the same entity.
|
||||
* </li>
|
||||
* <li>
|
||||
* A continues to reference placeholder B, and does not reference populated C.
|
||||
* </li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* There may be cases where configuring this setting to <code>false</code> would be appropriate; however, these are
|
||||
* exceptional cases that should be opt-in.
|
||||
* </p>
|
||||
*
|
||||
* @since 4.2.0
|
||||
*/
|
||||
|
@ -1370,22 +1420,6 @@ public class DaoConfig {
|
|||
myExpungeBatchSize = theExpungeBatchSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should contained IDs be indexed the same way that non-contained IDs are (default is
|
||||
* <code>true</code>)
|
||||
*/
|
||||
public boolean isIndexContainedResources() {
|
||||
return myIndexContainedResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should contained IDs be indexed the same way that non-contained IDs are (default is
|
||||
* <code>true</code>)
|
||||
*/
|
||||
public void setIndexContainedResources(boolean theIndexContainedResources) {
|
||||
myIndexContainedResources = theIndexContainedResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should resources be marked as needing reindexing when a
|
||||
* SearchParameter resource is added or changed. This should generally
|
||||
|
@ -1519,62 +1553,6 @@ public class DaoConfig {
|
|||
myValidateSearchParameterExpressionsOnSave = theValidateSearchParameterExpressionsOnSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not call this method, it exists only for legacy reasons. It
|
||||
* will be removed in a future version. Configure the page size on your
|
||||
* paging provider instead.
|
||||
*
|
||||
* @deprecated This method does not do anything. Configure the page size on your
|
||||
* paging provider instead. Deprecated in HAPI FHIR 2.3 (Jan 2017)
|
||||
*/
|
||||
@Deprecated
|
||||
public void setHardSearchLimit(int theHardSearchLimit) {
|
||||
// this method does nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the maximum number of resources that will be added to a single page of returned resources. Because of
|
||||
* includes with wildcards and other possibilities it is possible for a client to make requests that include very
|
||||
* large amounts of data, so this hard limit can be imposed to prevent runaway requests.
|
||||
*
|
||||
* @deprecated Deprecated in HAPI FHIR 3.2.0 as this method doesn't actually do anything
|
||||
*/
|
||||
@Deprecated
|
||||
public void setIncludeLimit(@SuppressWarnings("unused") int theIncludeLimit) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for
|
||||
* detecting changes, so this setting has no effect
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSubscriptionEnabled(boolean theSubscriptionEnabled) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for
|
||||
* detecting changes, so this setting has no effect
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSubscriptionPollDelay(long theSubscriptionPollDelay) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of HAPI FHIR 3.0.0, subscriptions no longer use polling for
|
||||
* detecting changes, so this setting has no effect
|
||||
*/
|
||||
@Deprecated
|
||||
public void setSubscriptionPurgeInactiveAfterMillis(Long theMillis) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) {
|
||||
setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting sets the number of search results to prefetch. For example, if this list
|
||||
* is set to [100, 1000, -1] then the server will initially load 100 results and not
|
||||
|
|
|
@ -54,6 +54,7 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
|||
import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum;
|
||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
@ -1149,7 +1150,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
BaseHasResource entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong());
|
||||
|
||||
// Verify that the resource is for the correct partition
|
||||
if (!requestPartitionId.isAllPartitions()) {
|
||||
if (entity != null && !requestPartitionId.isAllPartitions()) {
|
||||
if (entity.getPartitionId() != null && entity.getPartitionId().getPartitionId() != null) {
|
||||
if (!requestPartitionId.hasPartitionId(entity.getPartitionId().getPartitionId())) {
|
||||
ourLog.debug("Performing a read for PartitionId={} but entity has partition: {}", requestPartitionId, entity.getPartitionId());
|
||||
|
@ -1291,6 +1292,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
@Override
|
||||
public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequest, HttpServletResponse theServletResponse) {
|
||||
|
||||
if (theParams.getSearchContainedMode() == SearchContainedModeEnum.BOTH) {
|
||||
throw new MethodNotAllowedException("Contained mode 'both' is not currently supported");
|
||||
}
|
||||
if (theParams.getSearchContainedMode() != SearchContainedModeEnum.FALSE && !myModelConfig.isIndexOnContainedResources()) {
|
||||
throw new MethodNotAllowedException("Searching with _contained mode enabled is not enabled on this server");
|
||||
}
|
||||
|
||||
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
|
||||
for (List<List<IQueryParameterType>> nextAnds : theParams.values()) {
|
||||
for (List<? extends IQueryParameterType> nextOrs : nextAnds) {
|
||||
|
|
|
@ -41,7 +41,6 @@ import ca.uhn.fhir.util.HapiExtensions;
|
|||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -123,10 +122,9 @@ public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
|||
@SuppressWarnings("unchecked")
|
||||
T newResource = (T) missingResourceDef.newInstance();
|
||||
|
||||
IBaseMetaType meta = newResource.getMeta();
|
||||
if (meta instanceof IBaseHasExtensions) {
|
||||
IBaseExtension<?, ?> extension = ((IBaseHasExtensions) meta).addExtension();
|
||||
extension.setUrl(HapiExtensions.EXT_RESOURCE_META_PLACEHOLDER);
|
||||
if (newResource instanceof IBaseHasExtensions) {
|
||||
IBaseExtension<?, ?> extension = ((IBaseHasExtensions) newResource).addExtension();
|
||||
extension.setUrl(HapiExtensions.EXT_RESOURCE_PLACEHOLDER);
|
||||
extension.setValue(myContext.getPrimitiveBoolean(true));
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ import ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder;
|
|||
import ca.uhn.fhir.jpa.search.builder.predicate.UriPredicateBuilder;
|
||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.SourceParam;
|
||||
|
@ -109,6 +110,7 @@ import java.util.Collections;
|
|||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -286,7 +288,7 @@ public class QueryStack {
|
|||
return new PredicateBuilderCacheLookupResult<>(cacheHit, (T) retVal);
|
||||
}
|
||||
|
||||
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
|
||||
private Condition createPredicateComposite(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParamDef, List<? extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
Condition orCondidtion = null;
|
||||
for (IQueryParameterType next : theNextAnd) {
|
||||
|
@ -298,11 +300,11 @@ public class QueryStack {
|
|||
|
||||
RuntimeSearchParam left = theParamDef.getCompositeOf().get(0);
|
||||
IQueryParameterType leftValue = cp.getLeftValue();
|
||||
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, left, leftValue, theRequestPartitionId);
|
||||
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, left, leftValue, theRequestPartitionId);
|
||||
|
||||
RuntimeSearchParam right = theParamDef.getCompositeOf().get(1);
|
||||
IQueryParameterType rightValue = cp.getRightValue();
|
||||
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, right, rightValue, theRequestPartitionId);
|
||||
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, theSpnamePrefix, right, rightValue, theRequestPartitionId);
|
||||
|
||||
Condition andCondition = toAndPredicate(leftPredicate, rightPredicate);
|
||||
|
||||
|
@ -316,20 +318,20 @@ public class QueryStack {
|
|||
return orCondidtion;
|
||||
}
|
||||
|
||||
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
|
||||
private Condition createPredicateCompositePart(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theSpnamePrefix, RuntimeSearchParam theParam, IQueryParameterType theParamValue, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
switch (theParam.getParamType()) {
|
||||
case STRING: {
|
||||
return createPredicateString(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
}
|
||||
case TOKEN: {
|
||||
return createPredicateToken(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
return createPredicateToken(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
}
|
||||
case DATE: {
|
||||
return createPredicateDate(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId);
|
||||
return createPredicateDate(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), toOperation(((DateParam) theParamValue).getPrefix()), theRequestPartitionId);
|
||||
}
|
||||
case QUANTITY: {
|
||||
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
return createPredicateQuantity(theSourceJoinColumn, theResourceName, theSpnamePrefix, theParam, Collections.singletonList(theParamValue), null, theRequestPartitionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,14 +359,11 @@ public class QueryStack {
|
|||
return predicateBuilder.combineWithRequestPartitionIdPredicate(theRequestPartitionId, ComboCondition.or(codePredicates.toArray(new Condition[0])));
|
||||
}
|
||||
|
||||
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
public Condition createPredicateDate(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
String paramName = theSearchParam.getName();
|
||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
PredicateBuilderCacheLookupResult<DatePredicateBuilder> predicateBuilderLookupResult = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.DATE, theSourceJoinColumn, paramName, () -> mySqlBuilder.addDatePredicateBuilder(theSourceJoinColumn));
|
||||
DatePredicateBuilder predicateBuilder = predicateBuilderLookupResult.getResult();
|
||||
|
@ -440,13 +439,13 @@ public class QueryStack {
|
|||
}
|
||||
RestSearchParameterTypeEnum typeEnum = searchParam.getParamType();
|
||||
if (typeEnum == RestSearchParameterTypeEnum.URI) {
|
||||
return theQueryStack3.createPredicateUri(null, theResourceName, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequest, theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateUri(null, theResourceName, null, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequest, theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.STRING) {
|
||||
return theQueryStack3.createPredicateString(null, theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateString(null, theResourceName, null, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.DATE) {
|
||||
return theQueryStack3.createPredicateDate(null, theResourceName, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateDate(null, theResourceName, null, searchParam, Collections.singletonList(new DateParam(fromOperation(theFilter.getOperation()), theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) {
|
||||
return theQueryStack3.createPredicateNumber(null, theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateNumber(null, theResourceName, null, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) {
|
||||
SearchFilterParser.CompareOperation operation = theFilter.getOperation();
|
||||
String resourceType = null; // The value can either have (Patient/123) or not have (123) a resource type, either way it's not needed here
|
||||
|
@ -455,7 +454,7 @@ public class QueryStack {
|
|||
ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value);
|
||||
return theQueryStack3.createPredicateReference(null, theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) {
|
||||
return theQueryStack3.createPredicateQuantity(null, theResourceName, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateQuantity(null, theResourceName, null, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId);
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) {
|
||||
throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses");
|
||||
} else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) {
|
||||
|
@ -464,7 +463,7 @@ public class QueryStack {
|
|||
null,
|
||||
null,
|
||||
theFilter.getValue());
|
||||
return theQueryStack3.createPredicateToken(null, theResourceName, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId);
|
||||
return theQueryStack3.createPredicateToken(null, theResourceName, null, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
@ -562,7 +561,7 @@ public class QueryStack {
|
|||
List<String> paths = join.createResourceLinkPaths(targetResourceType, paramReference);
|
||||
Condition typePredicate = BinaryCondition.equalTo(join.getColumnTargetResourceType(), mySqlBuilder.generatePlaceholder(theResourceType));
|
||||
Condition pathPredicate = toEqualToOrInPredicate(join.getColumnSourcePath(), mySqlBuilder.generatePlaceholders(paths));
|
||||
Condition linkedPredicate = searchForIdsWithAndOr(join.getColumnSrcResourceId(), targetResourceType, parameterName, Collections.singletonList(orValues), theRequest, theRequestPartitionId);
|
||||
Condition linkedPredicate = searchForIdsWithAndOr(join.getColumnSrcResourceId(), targetResourceType, parameterName, Collections.singletonList(orValues), theRequest, theRequestPartitionId, SearchContainedModeEnum.FALSE);
|
||||
andPredicates.add(toAndPredicate(partitionPredicate, pathPredicate, typePredicate, linkedPredicate));
|
||||
}
|
||||
|
||||
|
@ -607,17 +606,16 @@ public class QueryStack {
|
|||
return toAndPredicate(predicates);
|
||||
}
|
||||
|
||||
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
public Condition createPredicateNumber(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
NumberPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.NUMBER, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addNumberPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
NumberPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.NUMBER, theSourceJoinColumn, paramName, () -> mySqlBuilder.addNumberPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||
}
|
||||
|
||||
List<Condition> codePredicates = new ArrayList<>();
|
||||
|
@ -636,7 +634,8 @@ public class QueryStack {
|
|||
operation = toOperation(param.getPrefix());
|
||||
}
|
||||
|
||||
Condition predicate = join.createPredicateNumeric(theResourceName, theSearchParam.getName(), operation, value, theRequestPartitionId, nextOr);
|
||||
|
||||
Condition predicate = join.createPredicateNumeric(theResourceName, paramName, operation, value, theRequestPartitionId, nextOr);
|
||||
codePredicates.add(predicate);
|
||||
|
||||
} else {
|
||||
|
@ -648,16 +647,15 @@ public class QueryStack {
|
|||
return join.combineWithRequestPartitionIdPredicate(theRequestPartitionId, ComboCondition.or(codePredicates.toArray(new Condition[0])));
|
||||
}
|
||||
|
||||
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
public Condition createPredicateQuantity(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
QuantityBasePredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||
}
|
||||
|
||||
List<QuantityParam> quantityParams = theList
|
||||
|
@ -675,18 +673,18 @@ public class QueryStack {
|
|||
.collect(Collectors.toList());
|
||||
|
||||
if (normalizedQuantityParams.size() == quantityParams.size()) {
|
||||
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
quantityParams = normalizedQuantityParams;
|
||||
}
|
||||
}
|
||||
|
||||
if (join == null) {
|
||||
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, paramName, () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
}
|
||||
|
||||
List<Condition> codePredicates = new ArrayList<>();
|
||||
for (QuantityParam nextOr : quantityParams) {
|
||||
Condition singleCode = join.createPredicateQuantity(nextOr, theResourceName, theSearchParam.getName(), null, join, theOperation, theRequestPartitionId);
|
||||
Condition singleCode = join.createPredicateQuantity(nextOr, theResourceName, paramName, null, join, theOperation, theRequestPartitionId);
|
||||
codePredicates.add(singleCode);
|
||||
}
|
||||
|
||||
|
@ -720,6 +718,106 @@ public class QueryStack {
|
|||
return predicateBuilder.createPredicate(theRequest, theResourceName, theParamName, theList, theOperation, theRequestPartitionId);
|
||||
}
|
||||
|
||||
private Condition createPredicateReferenceForContainedResource(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName, String theParamName, RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList, SearchFilterParser.CompareOperation theOperation,
|
||||
RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
String spnamePrefix = theParamName;
|
||||
|
||||
String targetChain = null;
|
||||
String targetParamName = null;
|
||||
String targetQualifier = null;
|
||||
String targetValue = null;
|
||||
|
||||
RuntimeSearchParam targetParamDefinition = null;
|
||||
|
||||
ArrayList<IQueryParameterType> orValues = Lists.newArrayList();
|
||||
IQueryParameterType qp = null;
|
||||
|
||||
for (int orIdx = 0; orIdx < theList.size(); orIdx++) {
|
||||
|
||||
IQueryParameterType nextOr = theList.get(orIdx);
|
||||
|
||||
if (nextOr instanceof ReferenceParam) {
|
||||
|
||||
ReferenceParam referenceParam = (ReferenceParam) nextOr;
|
||||
|
||||
// 1. Find out the parameter, qualifier and the value
|
||||
targetChain = referenceParam.getChain();
|
||||
targetParamName = targetChain;
|
||||
targetValue = nextOr.getValueAsQueryToken(myFhirContext);
|
||||
|
||||
int qualifierIndex = targetChain.indexOf(':');
|
||||
if (qualifierIndex != -1) {
|
||||
targetParamName = targetChain.substring(0, qualifierIndex);
|
||||
targetQualifier = targetChain.substring(qualifierIndex);
|
||||
}
|
||||
|
||||
// 2. find out the data type
|
||||
if (targetParamDefinition == null) {
|
||||
Iterator<String> it = theSearchParam.getTargets().iterator();
|
||||
while (it.hasNext()) {
|
||||
targetParamDefinition = mySearchParamRegistry.getActiveSearchParam(it.next(), targetParamName);
|
||||
if (targetParamDefinition != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetParamDefinition == null) {
|
||||
throw new InvalidRequestException("Unknown search parameter name: " + theSearchParam.getName() + '.' + targetParamName + ".");
|
||||
}
|
||||
|
||||
qp = toParameterType(targetParamDefinition);
|
||||
qp.setValueAsQueryToken(myFhirContext, targetParamName, targetQualifier, targetValue);
|
||||
orValues.add(qp);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetParamDefinition == null) {
|
||||
throw new InvalidRequestException("Unknown search parameter name: " + theSearchParam.getName() + ".");
|
||||
}
|
||||
|
||||
// 3. create the query
|
||||
Condition containedCondition = null;
|
||||
|
||||
switch (targetParamDefinition.getParamType()) {
|
||||
case DATE:
|
||||
containedCondition = createPredicateDate(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case NUMBER:
|
||||
containedCondition = createPredicateNumber(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case QUANTITY:
|
||||
containedCondition = createPredicateQuantity(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case STRING:
|
||||
containedCondition = createPredicateString(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case TOKEN:
|
||||
containedCondition = createPredicateToken(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequestPartitionId);
|
||||
break;
|
||||
case COMPOSITE:
|
||||
containedCondition = createPredicateComposite(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theRequestPartitionId);
|
||||
break;
|
||||
case URI:
|
||||
containedCondition = createPredicateUri(null, theResourceName, spnamePrefix, targetParamDefinition,
|
||||
orValues, theOperation, theRequest, theRequestPartitionId);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException(
|
||||
"The search type:" + targetParamDefinition.getParamType() + " is not supported.");
|
||||
}
|
||||
|
||||
return containedCondition;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Condition createPredicateResourceId(@Nullable DbColumn theSourceJoinColumn, List<List<IQueryParameterType>> theValues, String theResourceName, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||
ResourceIdPredicateBuilder builder = mySqlBuilder.newResourceIdBuilder();
|
||||
|
@ -762,22 +860,21 @@ public class QueryStack {
|
|||
return toOrPredicate(orPredicates);
|
||||
}
|
||||
|
||||
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
public Condition createPredicateString(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
StringPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.STRING, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addStringPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
StringPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.STRING, theSourceJoinColumn, paramName, () -> mySqlBuilder.addStringPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||
}
|
||||
|
||||
List<Condition> codePredicates = new ArrayList<>();
|
||||
for (IQueryParameterType nextOr : theList) {
|
||||
Condition singleCode = join.createPredicateString(nextOr, theResourceName, theSearchParam, join, theOperation);
|
||||
Condition singleCode = join.createPredicateString(nextOr, theResourceName, theSpnamePrefix, theSearchParam, join, theOperation);
|
||||
codePredicates.add(singleCode);
|
||||
}
|
||||
|
||||
|
@ -873,12 +970,9 @@ public class QueryStack {
|
|||
return toAndPredicate(andPredicates);
|
||||
}
|
||||
|
||||
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
public Condition createPredicateToken(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
List<IQueryParameterType> tokens = new ArrayList<>();
|
||||
for (IQueryParameterType nextOr : theList) {
|
||||
|
@ -900,7 +994,7 @@ public class QueryStack {
|
|||
throw new MethodNotAllowedException(msg);
|
||||
}
|
||||
|
||||
return createPredicateString(theSourceJoinColumn, theResourceName, theSearchParam, theList, null, theRequestPartitionId);
|
||||
return createPredicateString(theSourceJoinColumn, theResourceName, theSpnamePrefix, theSearchParam, theList, null, theRequestPartitionId);
|
||||
}
|
||||
|
||||
tokens.add(nextOr);
|
||||
|
@ -918,25 +1012,25 @@ public class QueryStack {
|
|||
return null;
|
||||
}
|
||||
|
||||
TokenPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.TOKEN, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addTokenPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
TokenPredicateBuilder join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.TOKEN, theSourceJoinColumn, paramName, () -> mySqlBuilder.addTokenPredicateBuilder(theSourceJoinColumn)).getResult();
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), theRequestPartitionId);
|
||||
return join.createPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), theRequestPartitionId);
|
||||
}
|
||||
|
||||
Condition predicate = join.createPredicateToken(tokens, theResourceName, theSearchParam, theOperation, theRequestPartitionId);
|
||||
Condition predicate = join.createPredicateToken(tokens, theResourceName, theSpnamePrefix, theSearchParam, theOperation, theRequestPartitionId);
|
||||
return join.combineWithRequestPartitionIdPredicate(theRequestPartitionId, predicate);
|
||||
}
|
||||
|
||||
public Condition createPredicateUri(@Nullable DbColumn theSourceJoinColumn,
|
||||
String theResourceName,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestDetails theRequestDetails,
|
||||
public Condition createPredicateUri(@Nullable DbColumn theSourceJoinColumn, String theResourceName,
|
||||
String theSpnamePrefix, RuntimeSearchParam theSearchParam, List<? extends IQueryParameterType> theList,
|
||||
SearchFilterParser.CompareOperation theOperation, RequestDetails theRequestDetails,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
String paramName = theSearchParam.getName();
|
||||
String paramName = getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
UriPredicateBuilder join = mySqlBuilder.addUriPredicateBuilder(theSourceJoinColumn);
|
||||
|
||||
if (theList.get(0).getMissing() != null) {
|
||||
|
@ -952,7 +1046,7 @@ public class QueryStack {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public Condition searchForIdsWithAndOr(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
|
||||
public Condition searchForIdsWithAndOr(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theParamName, List<List<IQueryParameterType>> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId, SearchContainedModeEnum theSearchContainedMode) {
|
||||
|
||||
if (theAndOrParams.isEmpty()) {
|
||||
return null;
|
||||
|
@ -998,7 +1092,7 @@ public class QueryStack {
|
|||
DateParam param = (DateParam) nextAnd.get(0);
|
||||
operation = toOperation(param.getPrefix());
|
||||
}
|
||||
andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, operation, theRequestPartitionId));
|
||||
//andPredicates.add(createPredicateDate(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
|
@ -1009,17 +1103,20 @@ public class QueryStack {
|
|||
QuantityParam param = (QuantityParam) nextAnd.get(0);
|
||||
operation = toOperation(param.getPrefix());
|
||||
}
|
||||
andPredicates.add(createPredicateQuantity(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, operation, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateQuantity(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, operation, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case REFERENCE:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
if (theSearchContainedMode.equals(SearchContainedModeEnum.TRUE))
|
||||
andPredicates.add(createPredicateReferenceForContainedResource(theSourceJoinColumn, theResourceName, theParamName, nextParamDef, nextAnd, null, theRequest, theRequestPartitionId));
|
||||
else
|
||||
andPredicates.add(createPredicateReference(theSourceJoinColumn, theResourceName, theParamName, nextAnd, null, theRequest, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
andPredicates.add(createPredicateString(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateString(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case TOKEN:
|
||||
|
@ -1027,23 +1124,23 @@ public class QueryStack {
|
|||
if ("Location.position".equals(nextParamDef.getPath())) {
|
||||
andPredicates.add(createPredicateCoords(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, theRequestPartitionId));
|
||||
} else {
|
||||
andPredicates.add(createPredicateToken(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateToken(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NUMBER:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
andPredicates.add(createPredicateNumber(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateNumber(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, null, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case COMPOSITE:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
andPredicates.add(createPredicateComposite(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateComposite(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case URI:
|
||||
for (List<? extends IQueryParameterType> nextAnd : theAndOrParams) {
|
||||
andPredicates.add(createPredicateUri(theSourceJoinColumn, theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.eq, theRequest, theRequestPartitionId));
|
||||
andPredicates.add(createPredicateUri(theSourceJoinColumn, theResourceName, null, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.eq, theRequest, theRequestPartitionId));
|
||||
}
|
||||
break;
|
||||
case HAS:
|
||||
|
@ -1246,4 +1343,48 @@ public class QueryStack {
|
|||
return parameter.substring(parameter.indexOf(".") + 1);
|
||||
}
|
||||
|
||||
private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
|
||||
|
||||
IQueryParameterType qp;
|
||||
switch (theParam.getParamType()) {
|
||||
case DATE:
|
||||
qp = new DateParam();
|
||||
break;
|
||||
case NUMBER:
|
||||
qp = new NumberParam();
|
||||
break;
|
||||
case QUANTITY:
|
||||
qp = new QuantityParam();
|
||||
break;
|
||||
case STRING:
|
||||
qp = new StringParam();
|
||||
break;
|
||||
case TOKEN:
|
||||
qp = new TokenParam();
|
||||
break;
|
||||
case COMPOSITE:
|
||||
List<RuntimeSearchParam> compositeOf = theParam.getCompositeOf();
|
||||
if (compositeOf.size() != 2) {
|
||||
throw new InternalErrorException("Parameter " + theParam.getName() + " has " + compositeOf.size() + " composite parts. Don't know how handlt this.");
|
||||
}
|
||||
IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
|
||||
IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
|
||||
qp = new CompositeParam<>(leftParam, rightParam);
|
||||
break;
|
||||
case URI:
|
||||
qp = new UriParam();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidRequestException("The search type: " + theParam.getParamType() + " is not supported.");
|
||||
}
|
||||
return qp;
|
||||
}
|
||||
|
||||
public static String getParamNameWithPrefix(String theSpnamePrefix, String theParamName) {
|
||||
|
||||
if (isBlank(theSpnamePrefix))
|
||||
return theParamName;
|
||||
|
||||
return theSpnamePrefix + "." + theParamName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.Dstu3DistanceHelper;
|
||||
import ca.uhn.fhir.jpa.searchparam.util.LastNParameterHelper;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.jpa.util.BaseIterator;
|
||||
import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
|
@ -91,7 +92,6 @@ import com.healthmarketscience.sqlbuilder.Condition;
|
|||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -216,6 +216,8 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
attemptCompositeUniqueSpProcessing(theQueryStack, theParams, theRequest);
|
||||
}
|
||||
|
||||
SearchContainedModeEnum searchContainedMode = theParams.getSearchContainedMode();
|
||||
|
||||
// Handle each parameter
|
||||
List<String> paramNames = new ArrayList<>(myParams.keySet());
|
||||
for (String nextParamName : paramNames) {
|
||||
|
@ -224,7 +226,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
continue;
|
||||
}
|
||||
List<List<IQueryParameterType>> andOrParams = myParams.get(nextParamName);
|
||||
Condition predicate = theQueryStack.searchForIdsWithAndOr(null, myResourceName, nextParamName, andOrParams, theRequest, myRequestPartitionId);
|
||||
Condition predicate = theQueryStack.searchForIdsWithAndOr(null, myResourceName, nextParamName, andOrParams, theRequest, myRequestPartitionId, searchContainedMode);
|
||||
if (predicate != null) {
|
||||
theSearchSqlBuilder.addPredicate(predicate);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
|||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
@ -394,7 +395,7 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder {
|
|||
List<Condition> andPredicates = new ArrayList<>();
|
||||
|
||||
List<List<IQueryParameterType>> chainParamValues = Collections.singletonList(orValues);
|
||||
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId));
|
||||
andPredicates.add(childQueryFactory.searchForIdsWithAndOr(myColumnTargetResourceId, subResourceName, chain, chainParamValues, theRequest, theRequestPartitionId, SearchContainedModeEnum.FALSE));
|
||||
|
||||
orPredicates.add(toAndPredicate(andPredicates));
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
|||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
|
@ -75,11 +76,13 @@ public class StringPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
|
||||
public Condition createPredicateString(IQueryParameterType theParameter,
|
||||
String theResourceName,
|
||||
String theSpnamePrefix,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
StringPredicateBuilder theFrom,
|
||||
SearchFilterParser.CompareOperation operation) {
|
||||
String rawSearchTerm;
|
||||
String paramName = theSearchParam.getName();
|
||||
String paramName = QueryStack.getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
if (theParameter instanceof TokenParam) {
|
||||
TokenParam id = (TokenParam) theParameter;
|
||||
if (!id.isText()) {
|
||||
|
|
|
@ -29,6 +29,7 @@ import ca.uhn.fhir.jpa.dao.LegacySearchBuilder;
|
|||
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
|
||||
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||
import ca.uhn.fhir.jpa.search.builder.QueryStack;
|
||||
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
|
@ -93,11 +94,13 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
|
||||
public Condition createPredicateToken(Collection<IQueryParameterType> theParameters,
|
||||
String theResourceName,
|
||||
String theSpnamePrefix,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
return createPredicateToken(
|
||||
theParameters,
|
||||
theResourceName,
|
||||
theSpnamePrefix,
|
||||
theSearchParam,
|
||||
null,
|
||||
theRequestPartitionId);
|
||||
|
@ -105,11 +108,15 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
|
||||
public Condition createPredicateToken(Collection<IQueryParameterType> theParameters,
|
||||
String theResourceName,
|
||||
String theSpnamePrefix,
|
||||
RuntimeSearchParam theSearchParam,
|
||||
SearchFilterParser.CompareOperation theOperation,
|
||||
RequestPartitionId theRequestPartitionId) {
|
||||
|
||||
|
||||
final List<FhirVersionIndependentConcept> codes = new ArrayList<>();
|
||||
String paramName = theSearchParam.getName();
|
||||
|
||||
String paramName = QueryStack.getParamNameWithPrefix(theSpnamePrefix, theSearchParam.getName());
|
||||
|
||||
SearchFilterParser.CompareOperation operation = theOperation;
|
||||
|
||||
|
@ -197,12 +204,12 @@ public class TokenPredicateBuilder extends BaseSearchParamPredicateBuilder {
|
|||
long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName);
|
||||
Condition hashIdentityPredicate = BinaryCondition.equalTo(getColumnHashIdentity(), generatePlaceholder(hashIdentity));
|
||||
|
||||
Condition hashValuePredicate = createPredicateOrList(theResourceName, theSearchParam.getName(), sortedCodesList, false);
|
||||
Condition hashValuePredicate = createPredicateOrList(theResourceName, paramName, sortedCodesList, false);
|
||||
predicate = toAndPredicate(hashIdentityPredicate, hashValuePredicate);
|
||||
|
||||
} else {
|
||||
|
||||
predicate = createPredicateOrList(theResourceName, theSearchParam.getName(), sortedCodesList, true);
|
||||
predicate = createPredicateOrList(theResourceName, paramName, sortedCodesList, true);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -119,15 +119,17 @@ import org.hl7.fhir.instance.model.api.IBaseDatatype;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.BooleanType;
|
||||
import org.hl7.fhir.r4.model.CanonicalType;
|
||||
import org.hl7.fhir.r4.model.CodeSystem;
|
||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.ConceptMap;
|
||||
import org.hl7.fhir.r4.model.DomainResource;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.IntegerType;
|
||||
import org.hl7.fhir.r4.model.MetadataResource;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.hl7.fhir.r4.model.ValueSet;
|
||||
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
|
||||
|
@ -2122,8 +2124,13 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
|
|||
}
|
||||
}
|
||||
|
||||
private boolean isPlaceholder(MetadataResource theResource) {
|
||||
return theResource.getMeta().getExtensionByUrl(HapiExtensions.EXT_RESOURCE_META_PLACEHOLDER) != null;
|
||||
private boolean isPlaceholder(DomainResource theResource) {
|
||||
boolean retVal = false;
|
||||
Extension extension = theResource.getExtensionByUrl(HapiExtensions.EXT_RESOURCE_PLACEHOLDER);
|
||||
if (extension != null && extension.hasValue() && extension.getValue() instanceof BooleanType) {
|
||||
retVal = ((BooleanType) extension.getValue()).booleanValue();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -244,9 +244,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeResetConfig() {
|
||||
myDaoConfig.setHardSearchLimit(1000);
|
||||
myDaoConfig.setHardTagListLimit(1000);
|
||||
myDaoConfig.setIncludeLimit(2000);
|
||||
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
|
||||
}
|
||||
|
||||
|
|
|
@ -396,9 +396,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeResetConfig() {
|
||||
myDaoConfig.setHardSearchLimit(1000);
|
||||
myDaoConfig.setHardTagListLimit(1000);
|
||||
myDaoConfig.setIncludeLimit(2000);
|
||||
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,10 @@ package ca.uhn.fhir.jpa.dao.dstu3;
|
|||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParamModifier;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Reference;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
@ -18,12 +16,6 @@ public class FhirResourceDaoDstu3ContainedTest extends BaseJpaDstu3Test {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3ContainedTest.class);
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void before() {
|
||||
myDaoConfig.setIndexContainedResources(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexContained() {
|
||||
Patient p = new Patient();
|
||||
|
|
|
@ -477,7 +477,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
|||
@Autowired
|
||||
private IValidationSupport myJpaValidationSupportChainR4;
|
||||
private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor;
|
||||
private List<Object> mySystemInterceptors;
|
||||
@Autowired
|
||||
private IBulkDataExportSvc myBulkDataExportSvc;
|
||||
@Autowired
|
||||
|
@ -525,8 +524,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
|||
|
||||
@BeforeEach
|
||||
public void beforeCreateInterceptor() {
|
||||
mySystemInterceptors = myInterceptorRegistry.getAllRegisteredInterceptors();
|
||||
|
||||
myInterceptor = mock(IServerInterceptor.class);
|
||||
|
||||
myPerformanceTracingLoggingInterceptor = new PerformanceTracingLoggingInterceptor();
|
||||
|
@ -558,9 +555,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
|
|||
|
||||
@BeforeEach
|
||||
public void beforeResetConfig() {
|
||||
myDaoConfig.setHardSearchLimit(1000);
|
||||
myDaoConfig.setHardTagListLimit(1000);
|
||||
myDaoConfig.setIncludeLimit(2000);
|
||||
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||
myValidationSettings.setLocalReferenceValidationDefaultPolicy(new ValidationSettings().getLocalReferenceValidationDefaultPolicy());
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@ import ca.uhn.fhir.rest.param.ReferenceParam;
|
|||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.AuditEvent;
|
||||
import org.hl7.fhir.r4.model.BooleanType;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Observation.ObservationStatus;
|
||||
|
@ -18,16 +20,18 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.Task;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.startsWith;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@SuppressWarnings({"ConstantConditions"})
|
||||
|
@ -174,9 +178,54 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePlaceholderWithMatchUrl_IdentifierNotCopiedByDefault() {
|
||||
public void testCreatePlaceholderExtension_WithUpdateToTarget() {
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
|
||||
// Create an Observation that references a Patient
|
||||
Observation obsToCreate = new Observation();
|
||||
obsToCreate.setStatus(ObservationStatus.FINAL);
|
||||
obsToCreate.getSubject().setReference("Patient/AAA");
|
||||
IIdType id = myObservationDao.create(obsToCreate, mySrd).getId();
|
||||
|
||||
// Read the Observation
|
||||
Observation createdObs = myObservationDao.read(id);
|
||||
ourLog.info("\nObservation created:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
|
||||
|
||||
/*
|
||||
* Read the placeholder Patient referenced by the Observation
|
||||
* Placeholder extension should exist and be true
|
||||
*/
|
||||
Patient placeholderPat = myPatientDao.read(new IdType(createdObs.getSubject().getReference()));
|
||||
IIdType placeholderPatId = placeholderPat.getIdElement();
|
||||
ourLog.info("\nPlaceholder Patient created:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(placeholderPat));
|
||||
assertEquals(0, placeholderPat.getIdentifier().size());
|
||||
Extension extension = placeholderPat.getExtensionByUrl(HapiExtensions.EXT_RESOURCE_PLACEHOLDER);
|
||||
assertNotNull(extension);
|
||||
assertTrue(extension.hasValue());
|
||||
assertTrue(((BooleanType) extension.getValue()).booleanValue());
|
||||
|
||||
// Update the Patient
|
||||
Patient patToUpdate = new Patient();
|
||||
patToUpdate.setId("Patient/AAA");
|
||||
patToUpdate.addIdentifier().setSystem("http://foo").setValue("123");
|
||||
IIdType updatedPatId = myPatientDao.update(patToUpdate).getId();
|
||||
|
||||
/*
|
||||
* Read the updated Patient
|
||||
* Placeholder extension should not exist
|
||||
*/
|
||||
Patient updatedPat = myPatientDao.read(updatedPatId);
|
||||
ourLog.info("\nUpdated Patient:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedPat));
|
||||
assertEquals(1, updatedPat.getIdentifier().size());
|
||||
extension = updatedPat.getExtensionByUrl(HapiExtensions.EXT_RESOURCE_PLACEHOLDER);
|
||||
assertNull(extension);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePlaceholderWithMatchUrl_IdentifierNotCopied() {
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setAllowInlineMatchUrlReferences(true);
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(false);
|
||||
|
||||
Observation obsToCreate = new Observation();
|
||||
obsToCreate.setStatus(ObservationStatus.FINAL);
|
||||
|
@ -192,12 +241,60 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
|
|||
assertEquals(0, patient.getIdentifier().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCreatePlaceholderWithMatchUrl_IdentifierCopied_NotPreExisting() {
|
||||
public void testCreatePlaceholderWithMatchUrl_IdentifierCopiedByDefault_WithUpdateToTarget() {
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setAllowInlineMatchUrlReferences(true);
|
||||
|
||||
/*
|
||||
* Create an Observation that references a Patient
|
||||
* Reference is populated with inline match URL and includes identifier
|
||||
*/
|
||||
Observation obsToCreate = new Observation();
|
||||
obsToCreate.setStatus(ObservationStatus.FINAL);
|
||||
obsToCreate.getSubject().setReference("Patient?identifier=http://foo|123");
|
||||
obsToCreate.getSubject().getIdentifier().setSystem("http://foo").setValue("123");
|
||||
IIdType obsId = myObservationDao.create(obsToCreate, mySrd).getId();
|
||||
|
||||
// Read the Observation
|
||||
Observation createdObs = myObservationDao.read(obsId);
|
||||
ourLog.info("\nObservation created:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
|
||||
|
||||
/*
|
||||
* Read the placeholder Patient referenced by the Observation
|
||||
* Identifier should be populated since it was provided
|
||||
*/
|
||||
Patient placeholderPat = myPatientDao.read(new IdType(createdObs.getSubject().getReference()));
|
||||
IIdType placeholderPatId = placeholderPat.getIdElement();
|
||||
ourLog.info("\nPlaceholder Patient created:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(placeholderPat));
|
||||
assertEquals(1, placeholderPat.getIdentifier().size());
|
||||
assertEquals(createdObs.getSubject().getReference(), placeholderPatId.toUnqualifiedVersionless().getValueAsString());
|
||||
|
||||
// Conditionally update a Patient with the same identifier
|
||||
Patient patToConditionalUpdate = new Patient();
|
||||
patToConditionalUpdate.addIdentifier().setSystem("http://foo").setValue("123");
|
||||
patToConditionalUpdate.addName().setFamily("Simpson");
|
||||
IIdType conditionalUpdatePatId = myPatientDao.update(patToConditionalUpdate, "Patient?identifier=http://foo|123", mySrd).getId();
|
||||
|
||||
// Read the conditionally updated Patient
|
||||
Patient conditionalUpdatePat = myPatientDao.read(conditionalUpdatePatId);
|
||||
ourLog.info("\nConditionally updated Patient:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(conditionalUpdatePat));
|
||||
assertEquals(1, conditionalUpdatePat.getIdentifier().size());
|
||||
|
||||
/*
|
||||
* Observation should reference conditionally updated Patient
|
||||
* ID of placeholder Patient should match ID of conditionally updated Patient
|
||||
*/
|
||||
createdObs = myObservationDao.read(obsId);
|
||||
ourLog.info("\nObservation read after Patient update:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
|
||||
assertEquals(createdObs.getSubject().getReference(), conditionalUpdatePatId.toUnqualifiedVersionless().getValueAsString());
|
||||
assertEquals(placeholderPatId.toUnqualifiedVersionless().getValueAsString(), conditionalUpdatePatId.toUnqualifiedVersionless().getValueAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatePlaceholderWithMatchUrl_IdentifierCopiedByDefault_NotPreExisting() {
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setAllowInlineMatchUrlReferences(true);
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
|
||||
|
||||
Observation obsToCreate = new Observation();
|
||||
obsToCreate.setStatus(ObservationStatus.FINAL);
|
||||
|
@ -219,7 +316,6 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
|
|||
public void testCreatePlaceholderWithMatchUrl_IdentifierNotCopiedBecauseNoFieldMatches() {
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setAllowInlineMatchUrlReferences(true);
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setBundleTypesAllowedForStorage(Sets.newHashSet(""));
|
||||
|
||||
AuditEvent eventToCreate = new AuditEvent();
|
||||
|
@ -238,7 +334,6 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
|
|||
public void testCreatePlaceholderWithMatchUrl_PreExisting() {
|
||||
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
|
||||
myDaoConfig.setAllowInlineMatchUrlReferences(true);
|
||||
myDaoConfig.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(true);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("ABC");
|
||||
|
|
|
@ -1,57 +1,309 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.hl7.fhir.r4.model.Address;
|
||||
import org.hl7.fhir.r4.model.Address.AddressUse;
|
||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.Encounter.EncounterParticipantComponent;
|
||||
import org.hl7.fhir.r4.model.Encounter.EncounterStatus;
|
||||
import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestIntent;
|
||||
import org.hl7.fhir.r4.model.ServiceRequest.ServiceRequestStatus;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class FhirResourceDaoR4ContainedTest extends BaseJpaR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4ContainedTest.class);
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
myModelConfig.setIndexOnContainedResources(true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void before() {
|
||||
myDaoConfig.setIndexContainedResources(true);
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
myModelConfig.setIndexOnContainedResources(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIndexContained() {
|
||||
public void testCreateSimpleContainedResourceIndexWithGeneratedId() {
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId("#some_patient");
|
||||
p.addName().setFamily("MYFAMILY").addGiven("MYGIVEN");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
Observation o1 = new Observation();
|
||||
o1.getCode().setText("Some Observation");
|
||||
o1.setSubject(new Reference(p));
|
||||
IIdType oid1 = myObservationDao.create(o1, mySrd).getId().toUnqualifiedVersionless();
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Some Observation");
|
||||
obs.setSubject(new Reference(p));
|
||||
|
||||
Observation o2 = new Observation();
|
||||
o2.getCode().setText("Some Observation");
|
||||
o2.setSubject(new Reference(p));
|
||||
IIdType oid2 = myObservationDao.create(o2, mySrd).getId().toUnqualifiedVersionless();
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addName().setFamily("MYFAMILY").addGiven("MYGIVEN");
|
||||
IIdType pid2 = myPatientDao.create(p2, mySrd).getId().toUnqualifiedVersionless();
|
||||
IIdType id = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(o2));
|
||||
Observation createdObs = myObservationDao.read(id);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
|
||||
|
||||
runInTransaction(()->{
|
||||
Long i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'subject.family' AND s.myResourceType = 'Observation'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
});
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
// map = new SearchParameterMap();
|
||||
// map.add(Observation.SP_CODE, new TokenParam(null, "some observation").setModifier(TokenParamModifier.TEXT));
|
||||
// assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id1, id2)));
|
||||
map = new SearchParameterMap();
|
||||
map.add("subject", new ReferenceParam("name", "Smith"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSimpleContainedResourceIndexUserDefinedId() {
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setId("fooId");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Some Observation");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#fooId");
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
|
||||
IIdType id = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation createdObs = myObservationDao.read(id);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
|
||||
|
||||
runInTransaction(()->{
|
||||
Long i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'subject.family' AND s.myResourceType = 'Observation'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
});
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add("subject", new ReferenceParam("name", "Smith"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myObservationDao.search(map)), containsInAnyOrder(toValues(id)));
|
||||
}
|
||||
|
||||
|
||||
// TODO: make sure match URLs don't delete
|
||||
@Test
|
||||
public void testCreateMultipleContainedResourceIndex() {
|
||||
|
||||
Practitioner prac1 = new Practitioner();
|
||||
prac1.setId("prac1");
|
||||
prac1.setActive(true);
|
||||
prac1.setGender(AdministrativeGender.FEMALE);
|
||||
prac1.addName().setFamily("Smith").addGiven("John");
|
||||
Address address = prac1.addAddress();
|
||||
address.setUse(AddressUse.WORK);
|
||||
address.addLine("534 Erewhon St");
|
||||
address.setCity("PleasantVille");
|
||||
address.setState("NY");
|
||||
address.setPostalCode("12345");
|
||||
|
||||
Organization org1 = new Organization();
|
||||
org1.setId("org1");
|
||||
org1.setActive(true);
|
||||
org1.setName("org name 1");
|
||||
|
||||
Organization org2 = new Organization();
|
||||
org2.setId("org2");
|
||||
org2.setActive(false);
|
||||
org2.setName("org name 2");
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.getContained().add(prac1);
|
||||
patient.getContained().add(org1);
|
||||
patient.getContained().add(org2);
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
patient.addGeneralPractitioner().setReference("#prac1");
|
||||
patient.addGeneralPractitioner().setReference("#org1");
|
||||
patient.getManagingOrganization().setReference("#org2");
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient));
|
||||
|
||||
IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Patient createdPatient = myPatientDao.read(id);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdPatient));
|
||||
|
||||
runInTransaction(()->{
|
||||
Long i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'general-practitioner.family' AND s.myResourceType = 'Patient'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
|
||||
i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'general-practitioner.name' AND s.myResourceType = 'Patient'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(3L, i.longValue());
|
||||
|
||||
i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'organization.name' AND s.myResourceType = 'Patient'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
});
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add("general-practitioner", new ReferenceParam("family", "Smith"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), containsInAnyOrder(toValues(id)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateComplexContainedResourceIndex() {
|
||||
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
ServiceRequest serviceRequest = new ServiceRequest();
|
||||
serviceRequest.setId("serviceRequest1");
|
||||
serviceRequest.setStatus(ServiceRequestStatus.ACTIVE);
|
||||
serviceRequest.setIntent(ServiceRequestIntent.ORDER);
|
||||
serviceRequest.setAuthoredOnElement(new DateTimeType("2021-02-23"));
|
||||
encounter.addBasedOn().setReference("#serviceRequest1");
|
||||
encounter.getContained().add(serviceRequest);
|
||||
|
||||
Practitioner prac1 = new Practitioner();
|
||||
prac1.setId("prac1");
|
||||
prac1.setActive(true);
|
||||
prac1.setGender(AdministrativeGender.FEMALE);
|
||||
prac1.addName().setFamily("Smith").addGiven("John");
|
||||
EncounterParticipantComponent participient = encounter.addParticipant();
|
||||
participient.getIndividual().setReference("#prac1");
|
||||
encounter.getContained().add(prac1);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType id = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(id);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
|
||||
runInTransaction(()->{
|
||||
// The practitioner
|
||||
Long i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'participant.family' AND s.myResourceType = 'Encounter'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
|
||||
// The Patient
|
||||
i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamString s WHERE s.myParamName = 'subject.family' AND s.myResourceType = 'Encounter'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
|
||||
// The Observation
|
||||
i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamToken s WHERE s.myParamName = 'reason-reference.code' AND s.myResourceType = 'Encounter'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamToken s WHERE s.myParamName = 'reason-reference.combo-code' AND s.myResourceType = 'Encounter'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
|
||||
// The ServiceRequest
|
||||
i = myEntityManager
|
||||
.createQuery("SELECT count(s) FROM ResourceIndexedSearchParamDate s WHERE s.myParamName = 'based-on.authored' AND s.myResourceType = 'Encounter'", Long.class)
|
||||
.getSingleResult();
|
||||
assertEquals(1L, i.longValue());
|
||||
});
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add("based-on", new ReferenceParam("authored", "2021-02-23"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
assertThat(toUnqualifiedVersionlessIdValues(myEncounterDao.search(map)), containsInAnyOrder(toValues(id)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchWithNotSupportedSearchType() {
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add("subject", new ReferenceParam("near", "toronto"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
try {
|
||||
IBundleProvider outcome = myObservationDao.search(map);
|
||||
outcome.getResources(0, 1).get(0);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(e.getMessage(), "The search type: SPECIAL is not supported.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchWithNotSupportedSearchParameter() {
|
||||
|
||||
SearchParameterMap map;
|
||||
|
||||
map = new SearchParameterMap();
|
||||
map.add("subject", new ReferenceParam("marital-status", "M"));
|
||||
map.setSearchContainedMode(SearchContainedModeEnum.TRUE);
|
||||
|
||||
try {
|
||||
IBundleProvider outcome = myObservationDao.search(map);
|
||||
outcome.getResources(0, 1).get(0);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(e.getMessage(), "Unknown search parameter name: subject.marital-status.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,13 +148,6 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
|
|||
purgeDatabase(myDaoConfig, mySystemDao, myResourceReindexingSvc, mySearchCoordinatorSvc, mySearchParamRegistry, myBulkDataExportSvc);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void beforeResetConfig() {
|
||||
myDaoConfig.setHardSearchLimit(1000);
|
||||
myDaoConfig.setHardTagListLimit(1000);
|
||||
myDaoConfig.setIncludeLimit(2000);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PlatformTransactionManager getTxManager() {
|
||||
return myTxManager;
|
||||
|
@ -171,7 +164,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
|
|||
|
||||
Organization org = new Organization();
|
||||
org.setName(methodName);
|
||||
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
|
||||
myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(ca.uhn.fhir.rest.api.Constants.PARAM_CONTENT, new StringParam(methodName));
|
||||
|
@ -189,7 +182,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
|
|||
|
||||
Organization org = new Organization();
|
||||
org.setName(methodName);
|
||||
IIdType orgId = myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
|
||||
myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Organization.SP_NAME, new StringParam(methodName));
|
||||
|
|
|
@ -975,6 +975,58 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRead_PidId_UnknownResourceId() {
|
||||
// Read in specific Partition
|
||||
{
|
||||
addReadPartition(1);
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/1"), mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
// Read in null Partition
|
||||
{
|
||||
addReadDefaultPartition();
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/1"), mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead_PidId_ResourceIdOnlyExistsInDifferentPartition() {
|
||||
IIdType id = createPatient(withPartition(2), withActiveTrue());
|
||||
// Read in specific Partition
|
||||
{
|
||||
addReadPartition(1);
|
||||
try {
|
||||
myPatientDao.read(id, mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
// Read in null Partition
|
||||
{
|
||||
addReadDefaultPartition();
|
||||
try {
|
||||
myPatientDao.read(id, mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead_ForcedId_SpecificPartition() {
|
||||
IIdType patientIdNull = createPatient(withPutPartition(null), withActiveTrue(), withId("NULL"));
|
||||
|
|
|
@ -284,7 +284,7 @@ public class SearchParamExtractorR4Test {
|
|||
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
|
||||
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Encounter", "location");
|
||||
assertNotNull(param);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(enc);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(enc, false);
|
||||
assertEquals(1, links.size());
|
||||
assertEquals("location", links.iterator().next().getSearchParamName());
|
||||
assertEquals("Encounter.location.location", links.iterator().next().getPath());
|
||||
|
@ -299,7 +299,7 @@ public class SearchParamExtractorR4Test {
|
|||
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
|
||||
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE);
|
||||
assertNotNull(param);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(consent);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(consent, false);
|
||||
assertEquals(1, links.size());
|
||||
assertEquals("Consent.source", links.iterator().next().getPath());
|
||||
assertEquals("Consent/999", ((Reference) links.iterator().next().getRef()).getReference());
|
||||
|
@ -334,7 +334,7 @@ public class SearchParamExtractorR4Test {
|
|||
patient.addExtension("http://patext", new Reference("Organization/AAA"));
|
||||
|
||||
SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(patient);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> links = extractor.extractResourceLinks(patient, false);
|
||||
assertEquals(1, links.size());
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.param.HasParam;
|
||||
import ca.uhn.fhir.test.BaseTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -19,6 +18,6 @@ public class SearchParameterMapTest extends BaseTest {
|
|||
SearchParameterMap params = new SearchParameterMap();
|
||||
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
|
||||
String criteria = params.toNormalizedQueryString(myContext);
|
||||
assertEquals(criteria, "?_has:Observation:identifier:urn:system|FOO=urn%3Asystem%7CFOO");
|
||||
assertEquals(criteria, "?_has:Observation:subject:identifier=urn%3Asystem%7CFOO");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -480,7 +480,6 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
|
|||
|
||||
@BeforeEach
|
||||
public void beforeResetConfig() {
|
||||
myDaoConfig.setHardTagListLimit(1000);
|
||||
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||
}
|
||||
|
||||
|
|
|
@ -309,9 +309,6 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
|
||||
@Test
|
||||
public void testCountParam() {
|
||||
// NB this does not get used- The paging provider has its own limits built in
|
||||
myDaoConfig.setHardSearchLimit(100);
|
||||
|
||||
List<IBaseResource> resources = new ArrayList<IBaseResource>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Organization org = new Organization();
|
||||
|
|
|
@ -108,12 +108,14 @@ public class ResourceProviderHasParamR4Test extends BaseResourceProviderR4Test {
|
|||
obs.addIdentifier().setSystem("urn:system").setValue("NOLINK");
|
||||
obs.setDevice(new Reference(devId));
|
||||
myObservationDao.create(obs, mySrd);
|
||||
|
||||
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
|
||||
String uri = ourServerBase + "/Patient?_has:Observation:subject:identifier=" + UrlUtil.escapeUrlParam("urn:system|FOO");
|
||||
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
assertThat(ids, contains(pid0.getValue()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,994 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
import ca.uhn.fhir.rest.client.interceptor.CapturingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.hl7.fhir.r4.model.CarePlan;
|
||||
import org.hl7.fhir.r4.model.CarePlan.CarePlanIntent;
|
||||
import org.hl7.fhir.r4.model.CarePlan.CarePlanStatus;
|
||||
import org.hl7.fhir.r4.model.ClinicalImpression;
|
||||
import org.hl7.fhir.r4.model.ClinicalImpression.ClinicalImpressionStatus;
|
||||
import org.hl7.fhir.r4.model.CodeableConcept;
|
||||
import org.hl7.fhir.r4.model.DecimalType;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.Encounter.EncounterStatus;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Quantity;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.hl7.fhir.r4.model.RiskAssessment;
|
||||
import org.hl7.fhir.r4.model.RiskAssessment.RiskAssessmentStatus;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
|
||||
public class ResourceProviderR4SearchContainedTest extends BaseResourceProviderR4Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceProviderR4SearchContainedTest.class);
|
||||
@Autowired
|
||||
@Qualifier("myClinicalImpressionDaoR4")
|
||||
protected IFhirResourceDao<ClinicalImpression> myClinicalImpressionDao;
|
||||
private CapturingInterceptor myCapturingInterceptor = new CapturingInterceptor();
|
||||
|
||||
@Override
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
|
||||
myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo());
|
||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
|
||||
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
|
||||
|
||||
myClient.unregisterInterceptor(myCapturingInterceptor);
|
||||
myModelConfig.setIndexOnContainedResources(false);
|
||||
myModelConfig.setIndexOnContainedResources(new ModelConfig().isIndexOnContainedResources());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||
|
||||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
myClient.registerInterceptor(myCapturingInterceptor);
|
||||
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
|
||||
myModelConfig.setIndexOnContainedResources(true);
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedDisabled() throws Exception {
|
||||
myModelConfig.setIndexOnContainedResources(false);
|
||||
|
||||
String uri = ourServerBase + "/Observation?subject.name=Smith&_contained=true";
|
||||
try (CloseableHttpResponse response = ourHttpClient.execute(new HttpGet(uri))) {
|
||||
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(resp);
|
||||
assertEquals(MethodNotAllowedException.STATUS_CODE, response.getStatusLine().getStatusCode());
|
||||
assertThat(resp, containsString(">Searching with _contained mode enabled is not enabled on this server"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedBoth() throws Exception {
|
||||
String uri = ourServerBase + "/Observation?subject.name=Smith&_contained=both";
|
||||
try (CloseableHttpResponse response = ourHttpClient.execute(new HttpGet(uri))) {
|
||||
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(resp);
|
||||
assertEquals(MethodNotAllowedException.STATUS_CODE, response.getStatusLine().getStatusCode());
|
||||
assertThat(resp, containsString("Contained mode 'both' is not currently supported"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByName() throws Exception {
|
||||
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Doe").addGiven("Jane");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Jones").addGiven("Peter");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
|
||||
//-- Simple name match
|
||||
String uri = ourServerBase + "/Observation?subject.name=Smith&_contained=true";
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
//-- Simple name match with or
|
||||
uri = ourServerBase + "/Observation?subject.name=Smith,Jane&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(2L, oids.size());
|
||||
//assertEquals(oids.toString(), "[Observation/1, Observation/2]");
|
||||
|
||||
//-- Simple name match with qualifier
|
||||
uri = ourServerBase + "/Observation?subject.name:exact=Smith&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
//-- Simple name match with and
|
||||
uri = ourServerBase + "/Observation?subject.family=Smith&subject.given=John&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByDate() throws Exception {
|
||||
|
||||
IIdType oid1;
|
||||
IIdType oid3;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Doe").addGiven("Jane");
|
||||
p.getBirthDateElement().setValueAsString("2000-02-01");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Jones").addGiven("Peter");
|
||||
p.getBirthDateElement().setValueAsString("2000-03-01");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
oid3 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
//-- Search by date default op
|
||||
String uri = ourServerBase + "/Observation?subject.birthdate=2000-01-01&_contained=true";
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
//-- Search by date op=eq
|
||||
uri = ourServerBase + "/Observation?subject.birthdate=eq2000-01-01&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
//-- Search by date op=eq, with or
|
||||
uri = ourServerBase + "/Observation?subject.birthdate=2000-01-01,2000-02-01&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(2L, oids.size());
|
||||
//assertEquals(oids.toString(), "[Observation/1, Observation/2]");
|
||||
|
||||
//-- Simple name match with op = gt
|
||||
uri = ourServerBase + "/Observation?subject.birthdate=gt2000-02-10&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid3.getValue()));
|
||||
|
||||
//-- Simple name match with AND
|
||||
uri = ourServerBase + "/Observation?subject.family=Smith&subject.birthdate=eq2000-01-01&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
//-- Simple name match with AND - not found
|
||||
uri = ourServerBase + "/Observation?subject.family=Smith&subject.birthdate=eq2000-02-01&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(0L, oids.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByNumber() throws Exception {
|
||||
|
||||
IIdType cid1;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
|
||||
RiskAssessment risk = new RiskAssessment();
|
||||
risk.setId("risk1");
|
||||
risk.setStatus(RiskAssessmentStatus.CORRECTED);
|
||||
risk.getSubject().setReference("#patient1");
|
||||
risk.getPredictionFirstRep().setProbability(new DecimalType(2));
|
||||
|
||||
ClinicalImpression imp = new ClinicalImpression();
|
||||
imp.setStatus(ClinicalImpressionStatus.COMPLETED);
|
||||
|
||||
imp.getContained().add(p);
|
||||
imp.getSubject().setReference("#patient1");
|
||||
|
||||
imp.getContained().add(risk);
|
||||
imp.getInvestigationFirstRep().getItemFirstRep().setReference("#risk1");
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(imp));
|
||||
|
||||
cid1 = myClinicalImpressionDao.create(imp, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ClinicalImpression createdImp = myClinicalImpressionDao.read(cid1);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdImp));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
|
||||
RiskAssessment risk = new RiskAssessment();
|
||||
risk.setId("risk1");
|
||||
risk.setStatus(RiskAssessmentStatus.CORRECTED);
|
||||
risk.getSubject().setReference("#patient1");
|
||||
risk.getPredictionFirstRep().setProbability(new DecimalType(5));
|
||||
|
||||
ClinicalImpression imp = new ClinicalImpression();
|
||||
imp.setStatus(ClinicalImpressionStatus.COMPLETED);
|
||||
|
||||
imp.getContained().add(p);
|
||||
imp.getSubject().setReference("#patient1");
|
||||
|
||||
imp.getContained().add(risk);
|
||||
imp.getInvestigationFirstRep().getItemFirstRep().setReference("#risk1");
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(imp));
|
||||
|
||||
IIdType cid2 = myClinicalImpressionDao.create(imp, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ClinicalImpression createdImp = myClinicalImpressionDao.read(cid2);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdImp));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
|
||||
RiskAssessment risk = new RiskAssessment();
|
||||
risk.setId("risk1");
|
||||
risk.setStatus(RiskAssessmentStatus.CORRECTED);
|
||||
risk.getSubject().setReference("#patient1");
|
||||
risk.getPredictionFirstRep().setProbability(new DecimalType(10));
|
||||
|
||||
ClinicalImpression imp = new ClinicalImpression();
|
||||
imp.setStatus(ClinicalImpressionStatus.COMPLETED);
|
||||
|
||||
imp.getContained().add(p);
|
||||
imp.getSubject().setReference("#patient1");
|
||||
|
||||
imp.getContained().add(risk);
|
||||
imp.getInvestigationFirstRep().getItemFirstRep().setReference("#risk1");
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(imp));
|
||||
|
||||
IIdType cid3 = myClinicalImpressionDao.create(imp, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ClinicalImpression createdImp = myClinicalImpressionDao.read(cid3);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdImp));
|
||||
}
|
||||
|
||||
//-- Search by number
|
||||
String uri = ourServerBase + "/ClinicalImpression?investigation.probability=2&_contained=true";
|
||||
List<String> cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, cids.size());
|
||||
assertThat(cids, contains(cid1.getValue()));
|
||||
|
||||
|
||||
//-- Search by number with op = eq
|
||||
uri = ourServerBase + "/ClinicalImpression?investigation.probability=eq2&_contained=true";
|
||||
cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, cids.size());
|
||||
assertThat(cids, contains(cid1.getValue()));
|
||||
|
||||
|
||||
//-- Search by number with op = eq and or
|
||||
uri = ourServerBase + "/ClinicalImpression?investigation.probability=eq2,10&_contained=true";
|
||||
cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
assertEquals(2L, cids.size());
|
||||
|
||||
//-- Search by number with op = lt
|
||||
uri = ourServerBase + "/ClinicalImpression?investigation.probability=lt4&_contained=true";
|
||||
cids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, cids.size());
|
||||
assertThat(cids, contains(cid1.getValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByQuantity() throws Exception {
|
||||
|
||||
IIdType eid1;
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(200);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
eid1 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid1);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(300);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType eid2 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid2);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(400);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType eid3 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid3);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
//-- Search by quantity
|
||||
String uri = ourServerBase + "/Encounter?reason-reference.combo-value-quantity=200&_contained=true";
|
||||
List<String> eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, eids.size());
|
||||
assertThat(eids, contains(eid1.getValue()));
|
||||
|
||||
|
||||
//-- Search by quantity
|
||||
uri = ourServerBase + "/Encounter?reason-reference.combo-value-quantity=le400&_contained=true";
|
||||
eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(3L, eids.size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByToken() throws Exception {
|
||||
|
||||
IIdType eid1;
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(200);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
eid1 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid1);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-8").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(300);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType eid2 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid2);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-9").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(400);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType eid3 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid3);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
//-- Search by code
|
||||
String uri = ourServerBase + "/Encounter?reason-reference.code=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7") + "&_contained=true";
|
||||
List<String> eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, eids.size());
|
||||
assertThat(eids, contains(eid1.getValue()));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByComposite() throws Exception {
|
||||
|
||||
IIdType eid2;
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-7").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(200);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType eid1 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid1);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-8").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(300);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
eid2 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid2);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
{
|
||||
Encounter encounter = new Encounter();
|
||||
encounter.setStatus(EncounterStatus.ARRIVED);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("patient1");
|
||||
patient.addName().setFamily("Doe").addGiven("Jane");
|
||||
encounter.getSubject().setReference("#patient1");
|
||||
encounter.getContained().add(patient);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("obs1");
|
||||
obs.addIdentifier().setSystem("urn:system").setValue("FOO");
|
||||
obs.getSubject().setReference("#patient1");
|
||||
CodeableConcept cc = obs.getCode();
|
||||
cc.addCoding().setCode("2345-9").setSystem("http://loinc.org");
|
||||
Quantity quantity = obs.getValueQuantity();
|
||||
quantity.setValue(400);
|
||||
encounter.addReasonReference().setReference("#obs1");
|
||||
encounter.getContained().add(obs);
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(encounter));
|
||||
|
||||
IIdType eid3 = myEncounterDao.create(encounter, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Encounter createdEncounter = myEncounterDao.read(eid3);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdEncounter));
|
||||
}
|
||||
|
||||
//-- Search by composite
|
||||
String uri = ourServerBase + "/Encounter?reason-reference.combo-code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-8$300") + "&_contained=true";
|
||||
List<String> eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, eids.size());
|
||||
assertThat(eids, contains(eid2.getValue()));
|
||||
|
||||
//-- Search by composite - not found
|
||||
uri = ourServerBase + "/Encounter?reason-reference.combo-code-value-quantity=http://" + UrlUtil.escapeUrlParam("loinc.org|2345-7$300") + "&_contained=true";
|
||||
eids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(0L, eids.size());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContainedSearchByUri() throws Exception {
|
||||
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
CarePlan carePlan = new CarePlan();
|
||||
carePlan.setId("carePlan1");
|
||||
carePlan.setStatus(CarePlanStatus.ACTIVE);
|
||||
carePlan.setIntent(CarePlanIntent.ORDER);
|
||||
carePlan.getSubject().setReference("#patient1");
|
||||
carePlan.addInstantiatesUri("http://www.hl7.com");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
obs.getContained().add(carePlan);
|
||||
obs.getBasedOnFirstRep().setReference("#carePlan1");
|
||||
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
|
||||
Observation createdObs = myObservationDao.read(oid1);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient2");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
CarePlan carePlan = new CarePlan();
|
||||
carePlan.setId("carePlan2");
|
||||
carePlan.setStatus(CarePlanStatus.ACTIVE);
|
||||
carePlan.setIntent(CarePlanIntent.ORDER);
|
||||
carePlan.getSubject().setReference("#patient2");
|
||||
carePlan.addInstantiatesUri("http://www2.hl7.com");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient2");
|
||||
obs.getContained().add(carePlan);
|
||||
obs.getBasedOnFirstRep().setReference("#carePlan2");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient3");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
p.getBirthDateElement().setValueAsString("2000-01-01");
|
||||
|
||||
CarePlan carePlan = new CarePlan();
|
||||
carePlan.setId("carePlan3");
|
||||
carePlan.setStatus(CarePlanStatus.ACTIVE);
|
||||
carePlan.setIntent(CarePlanIntent.ORDER);
|
||||
carePlan.getSubject().setReference("#patient3");
|
||||
carePlan.addInstantiatesUri("http://www2.hl7.com");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 3");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient3");
|
||||
obs.getContained().add(carePlan);
|
||||
obs.getBasedOnFirstRep().setReference("#carePlan3");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
//-- Search by uri
|
||||
String uri = ourServerBase + "/Observation?based-on.instantiates-uri=http://www.hl7.com&_contained=true";
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
//-- Search by uri more than 1 results
|
||||
uri = ourServerBase + "/Observation?based-on.instantiates-uri=http://www2.hl7.com&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(2L, oids.size());
|
||||
|
||||
//-- Search by uri with 'or'
|
||||
uri = ourServerBase + "/Observation?based-on.instantiates-uri=http://www.hl7.com,http://www2.hl7.com&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(3L, oids.size());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateContainedResource() throws Exception {
|
||||
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
|
||||
Observation createdObs = myObservationDao.read(oid1);
|
||||
|
||||
//-- changed the last name to Doe
|
||||
List<Resource> containedResources = createdObs.getContained();
|
||||
|
||||
for (Resource res : containedResources) {
|
||||
if (res instanceof Patient) {
|
||||
Patient p1 = (Patient) res;
|
||||
HumanName name = p1.getNameFirstRep();
|
||||
name.setFamily("Doe");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// -- update
|
||||
oid1 = myObservationDao.update(createdObs, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Doe").addGiven("Jane");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
{
|
||||
Patient p = new Patient();
|
||||
p.setId("patient1");
|
||||
p.addName().setFamily("Jones").addGiven("Peter");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 2");
|
||||
obs.getContained().add(p);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
}
|
||||
|
||||
|
||||
//-- No Obs with Patient Smith
|
||||
String uri = ourServerBase + "/Observation?subject.family=Smith&_contained=true";
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(0L, oids.size());
|
||||
|
||||
//-- Two Obs with Patient Doe
|
||||
uri = ourServerBase + "/Observation?subject.family=Doe&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(2L, oids.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteContainedResource() throws Exception {
|
||||
|
||||
IIdType oid1;
|
||||
|
||||
{
|
||||
Patient p1 = new Patient();
|
||||
p1.setId("patient1");
|
||||
p1.addName().setFamily("Smith").addGiven("John");
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.setId("patient2");
|
||||
p2.addName().setFamily("Doe").addGiven("Jane");
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.getCode().setText("Observation 1");
|
||||
obs.getContained().add(p1);
|
||||
obs.getSubject().setReference("#patient1");
|
||||
|
||||
oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
// -- remove contained resource
|
||||
obs.getContained().remove(p1);
|
||||
// -- add new contained resource
|
||||
obs.getContained().add(p2);
|
||||
obs.getSubject().setReference("#patient2");
|
||||
|
||||
ourLog.info("Input: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
|
||||
|
||||
// -- update
|
||||
oid1 = myObservationDao.update(obs, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation updatedObs = myObservationDao.read(oid1);
|
||||
|
||||
ourLog.info("Output: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(updatedObs));
|
||||
}
|
||||
|
||||
//-- No Obs with Patient Smith
|
||||
String uri = ourServerBase + "/Observation?subject.family=Smith&_contained=true";
|
||||
List<String> oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(0L, oids.size());
|
||||
|
||||
//-- 1 Obs with Patient Doe
|
||||
uri = ourServerBase + "/Observation?subject.family=Doe&_contained=true";
|
||||
oids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
|
||||
|
||||
assertEquals(1L, oids.size());
|
||||
assertThat(oids, contains(oid1.getValue()));
|
||||
|
||||
}
|
||||
|
||||
private List<String> searchAndReturnUnqualifiedVersionlessIdValues(String uri) throws IOException {
|
||||
List<String> ids;
|
||||
HttpGet get = new HttpGet(uri);
|
||||
|
||||
try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
|
||||
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
ourLog.info(resp);
|
||||
Bundle bundle = myFhirCtx.newXmlParser().parseResource(Bundle.class, resp);
|
||||
ids = toUnqualifiedVersionlessIdValues(bundle);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,7 +7,12 @@ import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
|||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.r4.model.EventDefinition;
|
||||
import org.hl7.fhir.r4.model.Expression;
|
||||
import org.hl7.fhir.r4.model.Observation;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.hl7.fhir.r4.model.TriggerDefinition;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
|
@ -36,6 +41,7 @@ import java.util.List;
|
|||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
|
||||
/**
|
||||
* Ignored because this feature isn't implemented yet
|
||||
*/
|
||||
|
@ -77,15 +83,6 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
|
|||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionAddedTrigger() {
|
||||
/*
|
||||
|
|
|
@ -9,7 +9,12 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
|||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.CodeableConcept;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Reference;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -20,8 +25,8 @@ import java.net.URI;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
|
@ -71,9 +76,6 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs
|
|||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
mySubscriptionTestUtil.registerWebSocketInterceptor();
|
||||
|
||||
/*
|
||||
|
|
|
@ -99,6 +99,8 @@ public class ModelConfig {
|
|||
private Map<String, Set<String>> myTypeToAutoVersionReferenceAtPaths = Collections.emptyMap();
|
||||
private boolean myRespectVersionsForSearchIncludes;
|
||||
|
||||
private boolean myIndexOnContainedResources = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -730,6 +732,27 @@ public class ModelConfig {
|
|||
myRespectVersionsForSearchIncludes = theRespectVersionsForSearchIncludes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Should indexing and searching on contained resources be enabled on this server.
|
||||
* This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public boolean isIndexOnContainedResources() {
|
||||
return myIndexOnContainedResources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should indexing and searching on contained resources be enabled on this server.
|
||||
* This may have performance impacts, and should be enabled only if it is needed. Default is <code>false</code>.
|
||||
*
|
||||
* @since 5.4.0
|
||||
*/
|
||||
public void setIndexOnContainedResources(boolean theIndexOnContainedResources) {
|
||||
myIndexOnContainedResources = theIndexOnContainedResources;
|
||||
}
|
||||
|
||||
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
|
||||
Validate.notBlank(theUrl, "Base URL must not be null or empty");
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import ca.uhn.fhir.model.api.IQueryParameterOr;
|
|||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
|
||||
import ca.uhn.fhir.rest.api.SortOrderEnum;
|
||||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
|
@ -79,6 +80,7 @@ public class SearchParameterMap implements Serializable {
|
|||
private boolean myLastN;
|
||||
private Integer myLastNMax;
|
||||
private boolean myDeleteExpunge;
|
||||
private SearchContainedModeEnum mySearchContainedMode = SearchContainedModeEnum.FALSE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -413,10 +415,6 @@ public class SearchParameterMap implements Serializable {
|
|||
IQueryParameterType firstValue = nextValuesAnd.get(0);
|
||||
b.append(UrlUtil.escapeUrlParam(nextKey));
|
||||
|
||||
if (nextKey.equals(Constants.PARAM_HAS)) {
|
||||
b.append(':');
|
||||
}
|
||||
|
||||
if (firstValue.getMissing() != null) {
|
||||
b.append(Constants.PARAMQUALIFIER_MISSING);
|
||||
b.append('=');
|
||||
|
@ -738,4 +736,17 @@ public class SearchParameterMap implements Serializable {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public SearchContainedModeEnum getSearchContainedMode() {
|
||||
return mySearchContainedMode;
|
||||
}
|
||||
|
||||
public void setSearchContainedMode(SearchContainedModeEnum theSearchContainedMode) {
|
||||
if (theSearchContainedMode == null) {
|
||||
mySearchContainedMode = SearchContainedModeEnum.FALSE;
|
||||
} else {
|
||||
this.mySearchContainedMode = theSearchContainedMode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -171,9 +171,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
|
||||
@Override
|
||||
public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource) {
|
||||
public SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences) {
|
||||
IExtractor<PathAndRef> extractor = createReferenceExtractor();
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.REFERENCE, theWantLocalReferences);
|
||||
}
|
||||
|
||||
private IExtractor<PathAndRef> createReferenceExtractor() {
|
||||
|
@ -231,7 +231,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
|
||||
private List<String> extractReferenceParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<PathAndRef> theExtractor) {
|
||||
SearchParamSet<PathAndRef> params = new SearchParamSet<>();
|
||||
extractSearchParam(theSearchParam, theResource, theExtractor, params);
|
||||
extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
|
||||
return refsToStringList(params);
|
||||
}
|
||||
|
||||
|
@ -244,7 +244,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
|
||||
private <T extends BaseResourceIndexedSearchParam> List<String> extractParamsAsQueryTokens(RuntimeSearchParam theSearchParam, IBaseResource theResource, IExtractor<T> theExtractor) {
|
||||
SearchParamSet<T> params = new SearchParamSet<>();
|
||||
extractSearchParam(theSearchParam, theResource, theExtractor, params);
|
||||
extractSearchParam(theSearchParam, theResource, theExtractor, params, false);
|
||||
return toStringList(params);
|
||||
}
|
||||
|
||||
|
@ -257,14 +257,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
@Override
|
||||
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource) {
|
||||
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) {
|
||||
IExtractor<BaseResourceIndexedSearchParam> extractor = createTokenExtractor(theResource);
|
||||
SearchParamSet<BaseResourceIndexedSearchParam> setToPopulate = new SearchParamSet<>();
|
||||
extractSearchParam(theSearchParam, theResource, extractor, setToPopulate);
|
||||
extractSearchParam(theSearchParam, theResource, extractor, setToPopulate, false);
|
||||
return setToPopulate;
|
||||
}
|
||||
|
||||
|
@ -293,11 +293,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
public SearchParamSet<BaseResourceIndexedSearchParam> extractSearchParamSpecial(IBaseResource theResource) {
|
||||
String resourceTypeName = toRootTypeName(theResource);
|
||||
IExtractor<BaseResourceIndexedSearchParam> extractor = createSpecialExtractor(resourceTypeName);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.SPECIAL, false);
|
||||
}
|
||||
|
||||
private IExtractor<BaseResourceIndexedSearchParam> createSpecialExtractor(String theResourceTypeName) {
|
||||
return (params, searchParam, value, path) -> {
|
||||
return (params, searchParam, value, path, theWantLocalReferences) -> {
|
||||
if (COORDS_INDEX_PATHS.contains(path)) {
|
||||
addCoords_Position(theResourceTypeName, params, searchParam, value);
|
||||
}
|
||||
|
@ -311,11 +311,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
@Override
|
||||
public SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource) {
|
||||
IExtractor<ResourceIndexedSearchParamUri> extractor = createUriExtractor(theResource);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.URI, false);
|
||||
}
|
||||
|
||||
private IExtractor<ResourceIndexedSearchParamUri> createUriExtractor(IBaseResource theResource) {
|
||||
return (params, searchParam, value, path) -> {
|
||||
return (params, searchParam, value, path, theWantLocalReferences) -> {
|
||||
String nextType = toRootTypeName(value);
|
||||
String resourceType = toRootTypeName(theResource);
|
||||
switch (nextType) {
|
||||
|
@ -336,7 +336,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
@Override
|
||||
public SearchParamSet<ResourceIndexedSearchParamDate> extractSearchParamDates(IBaseResource theResource) {
|
||||
IExtractor<ResourceIndexedSearchParamDate> extractor = createDateExtractor(theResource);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.DATE, false);
|
||||
}
|
||||
|
||||
private IExtractor<ResourceIndexedSearchParamDate> createDateExtractor(IBaseResource theResource) {
|
||||
|
@ -346,17 +346,17 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
@Override
|
||||
public Date extractDateFromResource(IBase theValue, String thePath) {
|
||||
DateExtractor extractor = new DateExtractor("DateType");
|
||||
return extractor.get(theValue, thePath).getValueHigh();
|
||||
return extractor.get(theValue, thePath, false).getValueHigh();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchParamSet<ResourceIndexedSearchParamNumber> extractSearchParamNumber(IBaseResource theResource) {
|
||||
IExtractor<ResourceIndexedSearchParamNumber> extractor = createNumberExtractor(theResource);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.NUMBER, false);
|
||||
}
|
||||
|
||||
private IExtractor<ResourceIndexedSearchParamNumber> createNumberExtractor(IBaseResource theResource) {
|
||||
return (params, searchParam, value, path) -> {
|
||||
return (params, searchParam, value, path, theWantLocalReferences) -> {
|
||||
String nextType = toRootTypeName(value);
|
||||
String resourceType = toRootTypeName(theResource);
|
||||
switch (nextType) {
|
||||
|
@ -384,18 +384,18 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
@Override
|
||||
public SearchParamSet<ResourceIndexedSearchParamQuantity> extractSearchParamQuantity(IBaseResource theResource) {
|
||||
IExtractor<ResourceIndexedSearchParamQuantity> extractor = createQuantityExtractor(theResource);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public SearchParamSet<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
|
||||
IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY, false);
|
||||
}
|
||||
|
||||
private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) {
|
||||
return (params, searchParam, value, path) -> {
|
||||
return (params, searchParam, value, path, theWantLocalReferences) -> {
|
||||
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
|
||||
return;
|
||||
}
|
||||
|
@ -421,7 +421,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
|
||||
private IExtractor<ResourceIndexedSearchParamQuantityNormalized> createQuantityNormalizedExtractor(IBaseResource theResource) {
|
||||
|
||||
return (params, searchParam, value, path) -> {
|
||||
return (params, searchParam, value, path, theWantLocalReferences) -> {
|
||||
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
|
||||
return;
|
||||
}
|
||||
|
@ -449,11 +449,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
public SearchParamSet<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
|
||||
IExtractor<ResourceIndexedSearchParamString> extractor = createStringExtractor(theResource);
|
||||
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING);
|
||||
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.STRING, false);
|
||||
}
|
||||
|
||||
private IExtractor<ResourceIndexedSearchParamString> createStringExtractor(IBaseResource theResource) {
|
||||
return (params, searchParam, value, path) -> {
|
||||
return (params, searchParam, value, path, theWantLocalReferences) -> {
|
||||
String resourceType = toRootTypeName(theResource);
|
||||
|
||||
if (value instanceof IPrimitiveType) {
|
||||
|
@ -934,7 +934,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
|
||||
}
|
||||
|
||||
private <T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType) {
|
||||
private <T> SearchParamSet<T> extractSearchParams(IBaseResource theResource, IExtractor<T> theExtractor, RestSearchParameterTypeEnum theSearchParamType, boolean theWantLocalReferences) {
|
||||
SearchParamSet<T> retVal = new SearchParamSet<>();
|
||||
|
||||
Collection<RuntimeSearchParam> searchParams = getSearchParams(theResource);
|
||||
|
@ -943,12 +943,12 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
continue;
|
||||
}
|
||||
|
||||
extractSearchParam(nextSpDef, theResource, theExtractor, retVal);
|
||||
extractSearchParam(nextSpDef, theResource, theExtractor, retVal, theWantLocalReferences);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate) {
|
||||
private <T> void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor<T> theExtractor, SearchParamSet<T> theSetToPopulate, boolean theWantLocalReferences) {
|
||||
String nextPathUnsplit = theSearchParameterDef.getPath();
|
||||
if (isBlank(nextPathUnsplit)) {
|
||||
return;
|
||||
|
@ -961,7 +961,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
if (nextObject != null) {
|
||||
String typeName = toRootTypeName(nextObject);
|
||||
if (!myIgnoredForSearchDatatypes.contains(typeName)) {
|
||||
theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath);
|
||||
theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath, theWantLocalReferences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1181,7 +1181,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
@FunctionalInterface
|
||||
private interface IExtractor<T> {
|
||||
|
||||
void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath);
|
||||
void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences);
|
||||
|
||||
}
|
||||
|
||||
|
@ -1196,9 +1196,9 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
|
||||
@Override
|
||||
public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
|
||||
myExtractor0.extract(theParams, theSearchParam, theValue, thePath);
|
||||
myExtractor1.extract(theParams, theSearchParam, theValue, thePath);
|
||||
public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
|
||||
myExtractor0.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
|
||||
myExtractor1.extract(theParams, theSearchParam, theValue, thePath, theWantLocalReferences);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1207,7 +1207,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
private PathAndRef myPathAndRef = null;
|
||||
|
||||
@Override
|
||||
public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
|
||||
public void extract(SearchParamSet<PathAndRef> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
|
||||
if (theValue instanceof IBaseResource) {
|
||||
return;
|
||||
}
|
||||
|
@ -1258,10 +1258,13 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
|
||||
if (nextId == null ||
|
||||
nextId.isEmpty() ||
|
||||
nextId.getValue().startsWith("#") ||
|
||||
nextId.getValue().startsWith("urn:")) {
|
||||
return;
|
||||
}
|
||||
if (!theWantLocalReferences) {
|
||||
if (nextId.getValue().startsWith("#"))
|
||||
return;
|
||||
}
|
||||
|
||||
myPathAndRef = new PathAndRef(theSearchParam.getName(), thePath, valueRef, false);
|
||||
theParams.add(myPathAndRef);
|
||||
|
@ -1275,7 +1278,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
public PathAndRef get(IBase theValue, String thePath) {
|
||||
extract(new SearchParamSet<>(),
|
||||
new RuntimeSearchParam(null, null, "Reference", null, null, null, null, null, null, null),
|
||||
theValue, thePath);
|
||||
theValue, thePath, false);
|
||||
return myPathAndRef;
|
||||
}
|
||||
}
|
||||
|
@ -1294,7 +1297,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
|
||||
@Override
|
||||
public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
|
||||
public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath, boolean theWantLocalReferences) {
|
||||
String nextType = toRootTypeName(theValue);
|
||||
switch (nextType) {
|
||||
case "date":
|
||||
|
@ -1389,10 +1392,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
}
|
||||
|
||||
public ResourceIndexedSearchParamDate get(IBase theValue, String thePath) {
|
||||
public ResourceIndexedSearchParamDate get(IBase theValue, String thePath, boolean theWantLocalReferences) {
|
||||
extract(new SearchParamSet<>(),
|
||||
new RuntimeSearchParam(null, null, "date", null, null, null, null, null, null, null),
|
||||
theValue, thePath);
|
||||
theValue, thePath, theWantLocalReferences);
|
||||
return myIndexedSearchParamDate;
|
||||
}
|
||||
}
|
||||
|
@ -1407,7 +1410,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
|
|||
}
|
||||
|
||||
@Override
|
||||
public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path) {
|
||||
public void extract(SearchParamSet<BaseResourceIndexedSearchParam> params, RuntimeSearchParam searchParam, IBase value, String path, boolean theWantLocalReferences) {
|
||||
|
||||
// DSTU3+
|
||||
if (value instanceof IBaseEnumeration<?>) {
|
||||
|
|
|
@ -61,7 +61,7 @@ public interface ISearchParamExtractor {
|
|||
|
||||
SearchParamSet<ResourceIndexedSearchParamUri> extractSearchParamUri(IBaseResource theResource);
|
||||
|
||||
SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource);
|
||||
SearchParamSet<PathAndRef> extractResourceLinks(IBaseResource theResource, boolean theWantLocalReferences);
|
||||
|
||||
String[] split(String theExpression);
|
||||
|
||||
|
|
|
@ -42,9 +42,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
|
|||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.param.QuantityParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -57,7 +55,7 @@ import java.util.Map;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.compare;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -144,6 +142,17 @@ public final class ResourceIndexedSearchParams {
|
|||
theEntity.setResourceLinks(myLinks);
|
||||
}
|
||||
|
||||
public void updateSpnamePrefixForIndexedOnContainedResource(String theSpnamePrefix) {
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myNumberParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myQuantityParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myQuantityNormalizedParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myDateParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myUriParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myTokenParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myStringParams, theSpnamePrefix);
|
||||
updateSpnamePrefixForIndexedOnContainedResource(myCoordsParams, theSpnamePrefix);
|
||||
}
|
||||
|
||||
void setUpdatedTime(Date theUpdateTime) {
|
||||
setUpdatedTime(myStringParams, theUpdateTime);
|
||||
setUpdatedTime(myNumberParams, theUpdateTime);
|
||||
|
@ -161,6 +170,14 @@ public final class ResourceIndexedSearchParams {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateSpnamePrefixForIndexedOnContainedResource(Collection<? extends BaseResourceIndexedSearchParam> theParams, @Nonnull String theSpnamePrefix) {
|
||||
|
||||
for (BaseResourceIndexedSearchParam param : theParams) {
|
||||
param.setParamName(theSpnamePrefix + "." + param.getParamName());
|
||||
param.calculateHashes(); // re-calculuteHashes
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getPopulatedResourceLinkParameters() {
|
||||
return myPopulatedResourceLinkParameters;
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
|||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||
import ca.uhn.fhir.util.StringUtil;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
|
@ -96,7 +100,18 @@ public class SearchParamExtractorService {
|
|||
IBaseResource resource = normalizeResource(theResource);
|
||||
|
||||
// All search parameter types except Reference
|
||||
extractSearchIndexParameters(theRequestDetails, theParams, resource, theEntity);
|
||||
ResourceIndexedSearchParams normalParams = new ResourceIndexedSearchParams();
|
||||
extractSearchIndexParameters(theRequestDetails, normalParams, resource, theEntity);
|
||||
mergeParams(normalParams, theParams);
|
||||
|
||||
if (myModelConfig.isIndexOnContainedResources()) {
|
||||
ResourceIndexedSearchParams containedParams = new ResourceIndexedSearchParams();
|
||||
extractSearchIndexParametersForContainedResources(theRequestDetails, containedParams, resource, theEntity);
|
||||
mergeParams(containedParams, theParams);
|
||||
}
|
||||
|
||||
// Do this after, because we add to strings during both string and token processing, and contained resource if any
|
||||
populateResourceTables(theParams, theEntity);
|
||||
|
||||
// Reference search parameters
|
||||
extractResourceLinks(theRequestPartitionId, theParams, theEntity, resource, theTransactionDetails, theFailOnInvalidReference, theRequestDetails);
|
||||
|
@ -104,6 +119,68 @@ public class SearchParamExtractorService {
|
|||
theParams.setUpdatedTime(theTransactionDetails.getTransactionDate());
|
||||
}
|
||||
|
||||
private void extractSearchIndexParametersForContainedResources(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
|
||||
|
||||
FhirTerser terser = myContext.newTerser();
|
||||
|
||||
// 1. get all contained resources
|
||||
Collection<IBaseResource> containedResources = terser.getAllEmbeddedResources(theResource, false);
|
||||
|
||||
// 2. Find referenced search parameters
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> referencedSearchParamSet = mySearchParamExtractor.extractResourceLinks(theResource, true);
|
||||
|
||||
String spnamePrefix = null;
|
||||
ResourceIndexedSearchParams currParams;
|
||||
// 3. for each referenced search parameter, create an index
|
||||
for (PathAndRef nextPathAndRef : referencedSearchParamSet) {
|
||||
|
||||
// 3.1 get the search parameter name as spname prefix
|
||||
spnamePrefix = nextPathAndRef.getSearchParamName();
|
||||
|
||||
if (spnamePrefix == null || nextPathAndRef.getRef() == null)
|
||||
continue;
|
||||
|
||||
// 3.2 find the contained resource
|
||||
IBaseResource containedResource = findContainedResource(containedResources, nextPathAndRef.getRef());
|
||||
if (containedResource == null)
|
||||
continue;
|
||||
|
||||
currParams = new ResourceIndexedSearchParams();
|
||||
|
||||
// 3.3 create indexes for the current contained resource
|
||||
extractSearchIndexParameters(theRequestDetails, currParams, containedResource, theEntity);
|
||||
|
||||
// 3.4 added reference name as a prefix for the contained resource if any
|
||||
// e.g. for Observation.subject contained reference
|
||||
// the SP_NAME = subject.family
|
||||
currParams.updateSpnamePrefixForIndexedOnContainedResource(spnamePrefix);
|
||||
|
||||
// 3.5 merge to the mainParams
|
||||
// NOTE: the spname prefix is different
|
||||
mergeParams(currParams, theParams);
|
||||
}
|
||||
}
|
||||
|
||||
private IBaseResource findContainedResource(Collection<IBaseResource> resources, IBaseReference reference) {
|
||||
for (IBaseResource resource : resources) {
|
||||
if (resource.getIdElement().equals(reference.getReferenceElement()))
|
||||
return resource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void mergeParams(ResourceIndexedSearchParams theSrcParams, ResourceIndexedSearchParams theTargetParams) {
|
||||
|
||||
theTargetParams.myNumberParams.addAll(theSrcParams.myNumberParams);
|
||||
theTargetParams.myQuantityParams.addAll(theSrcParams.myQuantityParams);
|
||||
theTargetParams.myQuantityNormalizedParams.addAll(theSrcParams.myQuantityNormalizedParams);
|
||||
theTargetParams.myDateParams.addAll(theSrcParams.myDateParams);
|
||||
theTargetParams.myUriParams.addAll(theSrcParams.myUriParams);
|
||||
theTargetParams.myTokenParams.addAll(theSrcParams.myTokenParams);
|
||||
theTargetParams.myStringParams.addAll(theSrcParams.myStringParams);
|
||||
theTargetParams.myCoordsParams.addAll(theSrcParams.myCoordsParams);
|
||||
}
|
||||
|
||||
private void extractSearchIndexParameters(RequestDetails theRequestDetails, ResourceIndexedSearchParams theParams, IBaseResource theResource, ResourceTable theEntity) {
|
||||
|
||||
// Strings
|
||||
|
@ -156,7 +233,10 @@ public class SearchParamExtractorService {
|
|||
}
|
||||
}
|
||||
|
||||
// Do this after, because we add to strings during both string and token processing
|
||||
}
|
||||
|
||||
private void populateResourceTables(ResourceIndexedSearchParams theParams, ResourceTable theEntity) {
|
||||
|
||||
populateResourceTable(theParams.myNumberParams, theEntity);
|
||||
populateResourceTable(theParams.myQuantityParams, theEntity);
|
||||
populateResourceTable(theParams.myQuantityNormalizedParams, theEntity);
|
||||
|
@ -165,7 +245,6 @@ public class SearchParamExtractorService {
|
|||
populateResourceTable(theParams.myTokenParams, theEntity);
|
||||
populateResourceTable(theParams.myStringParams, theEntity);
|
||||
populateResourceTable(theParams.myCoordsParams, theEntity);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -186,7 +265,7 @@ public class SearchParamExtractorService {
|
|||
private void extractResourceLinks(RequestPartitionId theRequestPartitionId, ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, TransactionDetails theTransactionDetails, boolean theFailOnInvalidReference, RequestDetails theRequest) {
|
||||
String resourceName = myContext.getResourceType(theResource);
|
||||
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource);
|
||||
ISearchParamExtractor.SearchParamSet<PathAndRef> refs = mySearchParamExtractor.extractResourceLinks(theResource, false);
|
||||
SearchParamExtractorService.handleWarnings(theRequest, myInterceptorBroadcaster, refs);
|
||||
|
||||
for (PathAndRef nextPathAndRef : refs) {
|
||||
|
|
|
@ -79,6 +79,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
|
|||
retVal.setWebsocketContextPath("/");
|
||||
retVal.setFilterParameterEnabled(true);
|
||||
retVal.setDefaultSearchParamsCanBeOverridden(false);
|
||||
retVal.getModelConfig().setIndexOnContainedResources(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
retVal.setExpungeEnabled(true);
|
||||
retVal.setFilterParameterEnabled(true);
|
||||
retVal.setDefaultSearchParamsCanBeOverridden(false);
|
||||
retVal.getModelConfig().setIndexOnContainedResources(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
retVal.setExpungeEnabled(true);
|
||||
retVal.setFilterParameterEnabled(true);
|
||||
retVal.setDefaultSearchParamsCanBeOverridden(false);
|
||||
retVal.getModelConfig().setIndexOnContainedResources(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,7 @@ public class TestR5Config extends BaseJavaConfigR5 {
|
|||
retVal.setExpungeEnabled(true);
|
||||
retVal.setFilterParameterEnabled(true);
|
||||
retVal.setDefaultSearchParamsCanBeOverridden(false);
|
||||
retVal.getModelConfig().setIndexOnContainedResources(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
|||
|
||||
public class RuleBuilder implements IAuthRuleBuilder {
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
private static final ConcurrentHashMap<Class<? extends IBaseResource>, String> ourTypeToName = new ConcurrentHashMap<>();
|
||||
private ArrayList<IAuthRule> myRules;
|
||||
private IAuthRuleBuilderRule myAllow;
|
||||
|
@ -144,7 +143,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId forTenantIds(String... theTenantIds) {
|
||||
return forTenantIds(Arrays.asList(defaultIfNull(theTenantIds, EMPTY_STRING_ARRAY)));
|
||||
return forTenantIds(Arrays.asList(defaultIfNull(theTenantIds, Constants.EMPTY_STRING_ARRAY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -162,7 +161,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
|
||||
@Override
|
||||
public IAuthRuleBuilderRuleOpClassifierFinishedWithTenantId notForTenantIds(String... theTenantIds) {
|
||||
return notForTenantIds(Arrays.asList(defaultIfNull(theTenantIds, EMPTY_STRING_ARRAY)));
|
||||
return notForTenantIds(Arrays.asList(defaultIfNull(theTenantIds, Constants.EMPTY_STRING_ARRAY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -131,6 +131,8 @@ public class MethodUtil {
|
|||
param = new SummaryEnumParameter();
|
||||
} else if (parameterType.equals(PatchTypeEnum.class)) {
|
||||
param = new PatchTypeParameter();
|
||||
} else if (parameterType.equals(SearchContainedModeEnum.class)) {
|
||||
param = new SearchContainedModeParameter();
|
||||
} else if (parameterType.equals(SearchTotalModeEnum.class)) {
|
||||
param = new SearchTotalModeParameter();
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package ca.uhn.fhir.rest.server.method;
|
||||
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
class SearchContainedModeParameter implements IParameter {
|
||||
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding<?> theMethodBinding) throws InternalErrorException, InvalidRequestException {
|
||||
return getTypeForRequestOrThrowInvalidRequestException(theRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public static SearchContainedModeEnum getTypeForRequestOrThrowInvalidRequestException(RequestDetails theRequest) {
|
||||
String[] paramValues = theRequest.getParameters().getOrDefault(Constants.PARAM_CONTAINED, Constants.EMPTY_STRING_ARRAY);
|
||||
if (paramValues.length > 0 && isNotBlank(paramValues[0])) {
|
||||
return SearchContainedModeEnum.fromCode(paramValues[0]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,7 @@ import ca.uhn.fhir.rest.param.*;
|
|||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import ca.uhn.fhir.rest.api.SearchTotalModeEnum;
|
||||
import ca.uhn.fhir.rest.api.SearchContainedModeEnum;
|
||||
|
||||
public class ${className}ResourceProvider extends
|
||||
## We have specialized base classes for RPs that handle certain resource types. These
|
||||
|
@ -138,7 +139,9 @@ public class ${className}ResourceProvider extends
|
|||
|
||||
SummaryEnum theSummaryMode,
|
||||
|
||||
SearchTotalModeEnum theSearchTotalMode
|
||||
SearchTotalModeEnum theSearchTotalMode,
|
||||
|
||||
SearchContainedModeEnum theSearchContainedMode
|
||||
|
||||
) {
|
||||
startRequest(theServletRequest);
|
||||
|
@ -165,6 +168,7 @@ public class ${className}ResourceProvider extends
|
|||
paramMap.setOffset(theOffset);
|
||||
paramMap.setSummaryMode(theSummaryMode);
|
||||
paramMap.setSearchTotalMode(theSearchTotalMode);
|
||||
paramMap.setSearchContainedMode(theSearchContainedMode);
|
||||
|
||||
getDao().translateRawParameters(theAdditionalRawParams, paramMap);
|
||||
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -782,7 +782,7 @@
|
|||
<jena_version>3.16.0</jena_version>
|
||||
<jersey_version>3.0.0</jersey_version>
|
||||
<!-- 9.4.17 seems to have issues -->
|
||||
<jetty_version>9.4.35.v20201120</jetty_version>
|
||||
<jetty_version>9.4.38.v20210224</jetty_version>
|
||||
<jsr305_version>3.0.2</jsr305_version>
|
||||
<junit_version>5.7.0</junit_version>
|
||||
<flyway_version>6.5.4</flyway_version>
|
||||
|
|
Loading…
Reference in New Issue