diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml
index 85f29e1e091..2781a057c6f 100644
--- a/hapi-deployable-pom/pom.xml
+++ b/hapi-deployable-pom/pom.xml
@@ -151,6 +151,10 @@
net.bytebuddy
byte-buddy
+
+ com.sun.xml.bind
+ jaxb-impl
+
.*\.txt$
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
index 392f57f5cc3..650b23365e9 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
@@ -1260,10 +1260,12 @@ class ParserState {
String versionId = elem.getMeta().getVersionId();
if (StringUtils.isBlank(elem.getIdElement().getIdPart())) {
// Resource has no ID
- } else if (StringUtils.isNotBlank(versionId)) {
- elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart() + "/_history/" + versionId);
- } else {
- elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart());
+ } else if (!elem.getIdElement().getIdPart().startsWith("urn:")) {
+ if (StringUtils.isNotBlank(versionId)) {
+ elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart() + "/_history/" + versionId);
+ } else {
+ elem.getIdElement().setValue(resourceName + "/" + elem.getIdElement().getIdPart());
+ }
}
}
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
index 3105339a50d..324e8eb32e5 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java
@@ -29,6 +29,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.jena.datatypes.xsd.XSDDatatype;
import org.apache.jena.rdf.model.*;
import org.apache.jena.riot.Lang;
+import org.apache.jena.riot.system.IRIResolver;
import org.apache.jena.vocabulary.RDF;
import org.hl7.fhir.instance.model.api.*;
@@ -171,10 +172,14 @@ public class RDFParser extends BaseParser {
if (!uriBase.endsWith("/")) {
uriBase = uriBase + "/";
}
- String resourceUri = uriBase + resource.getIdElement().toUnqualified();
if (parentResource == null) {
- parentResource = rdfModel.getResource(resourceUri);
+ if (!resource.getIdElement().toUnqualified().hasIdPart()) {
+ parentResource = rdfModel.getResource(null);
+ } else {
+ String resourceUri = IRIResolver.resolve(resource.getIdElement().toUnqualified().toString(), uriBase).toString();
+ parentResource = rdfModel.getResource(resourceUri);
+ }
// If the resource already exists and has statements, return that existing resource.
if (parentResource != null && parentResource.listProperties().toList().size() > 0) {
return parentResource;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QuantityParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QuantityParam.java
index e5dee29bb5c..6a314ddefa5 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QuantityParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/QuantityParam.java
@@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.param;
import java.math.BigDecimal;
import java.util.List;
+import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@@ -301,4 +302,22 @@ public class QuantityParam extends BaseParamWithPrefix implements
return b.toString();
}
+ public static QuantityParam toQuantityParam(IQueryParameterType theParam) {
+ if (theParam instanceof BaseQuantityDt) {
+ BaseQuantityDt param = (BaseQuantityDt) theParam;
+ String systemValue = param.getSystemElement().getValueAsString();
+ String unitsValue = param.getUnitsElement().getValueAsString();
+ ParamPrefixEnum cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
+ BigDecimal valueValue = param.getValueElement().getValue();
+ return new QuantityParam()
+ .setSystem(systemValue)
+ .setUnits(unitsValue)
+ .setPrefix(cmpValue)
+ .setValue(valueValue);
+ } else if (theParam instanceof QuantityParam) {
+ return (QuantityParam) theParam;
+ } else {
+ throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
+ }
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/schematron/SchematronBaseValidator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/schematron/SchematronBaseValidator.java
index 30e0144d109..9bbd1f839fb 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/schematron/SchematronBaseValidator.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/validation/schematron/SchematronBaseValidator.java
@@ -24,15 +24,21 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.BundleUtil;
-import ca.uhn.fhir.validation.*;
+import ca.uhn.fhir.validation.FhirValidator;
+import ca.uhn.fhir.validation.IValidationContext;
+import ca.uhn.fhir.validation.IValidatorModule;
+import ca.uhn.fhir.validation.ResultSeverityEnum;
+import ca.uhn.fhir.validation.SchemaBaseValidator;
+import ca.uhn.fhir.validation.SingleValidationMessage;
+import ca.uhn.fhir.validation.ValidationContext;
import com.helger.commons.error.IError;
import com.helger.commons.error.list.IErrorList;
import com.helger.schematron.ISchematronResource;
import com.helger.schematron.SchematronHelper;
+import com.helger.schematron.svrl.jaxb.SchematronOutputType;
import com.helger.schematron.xslt.SchematronResourceSCH;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.oclc.purl.dsdl.svrl.SchematronOutputType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
index 564dcf6469f..411847f5263 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
@@ -2,6 +2,7 @@
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionRefersToUnknownCs=Unknown CodeSystem URI "{0}" referenced from ValueSet
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetNotYetExpanded=ValueSet "{0}" has not yet been pre-expanded. Performing in-memory expansion without parameters. Current status: {1} | {2}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetNotYetExpanded_OffsetNotAllowed=ValueSet expansion can not combine "offset" with "ValueSet.compose.exclude" unless the ValueSet has been pre-expanded. ValueSet "{0}" must be pre-expanded for this operation to work.
+ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetExpandedUsingPreExpansion=ValueSet was expanded using a pre-calculated expansion
diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java
index 89eb0360318..896f021375e 100644
--- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java
+++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java
@@ -28,6 +28,7 @@ import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.springframework.context.ApplicationContext;
+import javax.annotation.Nonnull;
import java.util.List;
@SuppressWarnings("unused")
@@ -107,11 +108,44 @@ public class RepositoryValidatingInterceptorExamples {
ruleBuilder
.forResourcesOfType("Patient")
.requireValidationToDeclaredProfiles()
- .dontReject()
+ .neverReject()
.tagOnSeverity(ResultSeverityEnum.ERROR, "http://example.com", "validation-failure");
//END SNIPPET: requireValidationToDeclaredProfilesTagOnFailure
}
+ public void requireValidationToDeclaredProfilesAdditionalOptions() {
+ RepositoryValidatingRuleBuilder ruleBuilder = myAppCtx.getBean(RepositoryValidatingRuleBuilder.class);
+
+ //START SNIPPET: requireValidationToDeclaredProfilesAdditionalOptions
+ ruleBuilder
+ .forResourcesOfType("Patient")
+ .requireValidationToDeclaredProfiles()
+
+ // Configure the validator to never reject extensions
+ .allowAnyExtensions()
+
+ // Configure the validator to not perform terminology validation
+ .disableTerminologyChecks()
+
+ // Configure the validator to raise an error if a resource being
+ // validated declares a profile, and the StructureDefinition for
+ // this profile can not be found.
+ .errorOnUnknownProfiles()
+
+ // Configure the validator to suppress the information-level
+ // message that is added to the validation result if a profile
+ // StructureDefinition does not declare a binding for a coded
+ // field.
+ .suppressNoBindingMessage()
+
+ // Configure the validator to suppress the warning-level message
+ // that is added when validating a code that can't be found in a
+ // ValueSet that has an extensible binding.
+ .suppressWarningForExtensibleValueSetValidation();
+ //END SNIPPET: requireValidationToDeclaredProfilesAdditionalOptions
+ }
+
+
public void disallowProfiles() {
RepositoryValidatingRuleBuilder ruleBuilder = myAppCtx.getBean(RepositoryValidatingRuleBuilder.class);
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2054-add-additional-validator-switches.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2054-add-additional-validator-switches.yaml
new file mode 100644
index 00000000000..c302e23f2ef
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2054-add-additional-validator-switches.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 2054
+title: "Two new switches have neen added to FhirInstancdValidator to suppress optional warning messages. Thanks
+ to Anders Havn for the pull request!"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2290-make-quantity-use-double-dt.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2290-make-quantity-use-double-dt.yaml
new file mode 100644
index 00000000000..a59aa98a190
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/2290-make-quantity-use-double-dt.yaml
@@ -0,0 +1,6 @@
+---
+type: change
+issue: 2290
+title: "In the JPA server. the SQL datatype used to index quantities has been changed from `NUMBER(19,2)` to
+ `double precision` (or equivalents depending on platform). This improves the query support for ssearching on
+ very small quantities."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/changes.yaml
index 27be2312139..c7e24274ec3 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_3_0/changes.yaml
@@ -2,18 +2,22 @@
- item:
type: "add"
title: "The version of a few dependencies have been bumped to the latest versions
- (dependent HAPI modules listed in brackets):
-
- - SLF4j (All Modules): 1.7.28 -> 1.7.30
- - Woodstox (XML FHIR Parser): 4.4.1 -> 6.2.3 (Note that the Maven groupId has changed from
org.codehaus.woodstox
to com.fasterxml.woodstox
and the Maven artifactId has changed from woodstox-core-asl
to woodstox-core
for this library)
- - Spring (JPA): 5.2.3.RELEASE -> 5.2.9.RELEASE
- - Datasource-Proxy (JPA): 1.5.1 -> 1.7
- - Jetty (JPA Starter): 9.4.30.v20200611 -> 9.4.35.v20201120
- - Guava (JP): 29.0-jre -> 30.1-jre
- - Hibernate ORM (JPA Server): 5.4.22.FINAL -> 5.4.26.FINAL
- - Spring (JPA Server): 5.2.9.RELEASE -> 5.3.2
- - Spring Data (JPA Server): 2.2.0.RELEASE -> 2.4.2
- - Hibernate Search (JPA Server): 5.11.5.FINAL -> 6.0.0.Final
- - Lucene(HAPI FHIR JPA Server): 5.5.5 -> 8.7.0
- - Spring Boot (JPA Starter): 2.2.6.RELEASE -> 2.4.1
-
"
+(dependent HAPI modules listed in brackets):
+* SLF4j (All Modules): 1.7.28 -> 1.7.30
+* Jackson (All Modules): 2.11.2 -> 2.12.1
+* Woodstox (XML FHIR Parser): 4.4.1 -> 6.2.3 (Note that the Maven groupId has changed from `org.codehaus.woodstox` to `com.fasterxml.woodstox` and the Maven artifactId has changed from `woodstox-core-asl` to `woodstox-core` for this library)
+* Spring (JPA): 5.2.3.RELEASE -> 5.2.9.RELEASE
+* Datasource-Proxy (JPA): 1.5.1 -> 1.7
+* Apache Commons Collections4 (JPA): 4.3 -> 4.4
+* Jetty (JPA Starter): 9.4.30.v20200611 -> 9.4.35.v20201120
+* Guava (JP): 29.0-jre -> 30.1-jre
+* Hibernate ORM (JPA Server): 5.4.22.FINAL -> 5.4.26.FINAL
+* Spring (JPA Server): 5.2.9.RELEASE -> 5.3.3
+* Spring Data (JPA Server): 2.2.0.RELEASE -> 2.4.2
+* Hibernate Search (JPA Server): 5.11.5.FINAL -> 6.0.0.Final
+* Lucene(HAPI FHIR JPA Server): 5.5.5 -> 8.7.0
+* Spring Boot (JPA Starter): 2.2.6.RELEASE -> 2.4.1
+* JANSI (CLI): 1.18 -> 2.1.1
+* PH-Schematron (SCH Validator): 5.2.0 -> 5.6.5
+* PH-Commons (SCH Validator): 5.3.8 -> 9.5.4
+"
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/repository_validating_interceptor.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/repository_validating_interceptor.md
index fa1738382bc..7c26bd43c21 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/repository_validating_interceptor.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/repository_validating_interceptor.md
@@ -64,6 +64,8 @@ Note that this rule alone does not actually enforce validation against the speci
}
```
+
+
# Rules: Require Validation to Declared Profiles
Use the following rule to require that resources of the given type be validated successfully before allowing them to be persisted. For every resource of the given type that is submitted for storage, the `Resource.meta.profile` field will be examined and the resource will be validated against any declarations found there.
@@ -93,6 +95,13 @@ By default, resource updates/changes resulting in failing validation will cause
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java|requireValidationToDeclaredProfilesTagOnFailure}}
```
+## Configuring the Validator
+
+The following snippet shows a number of additional optional settings that can be chained onto the validation rule.
+
+```java
+{{snippet:classpath:/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java|requireValidationToDeclaredProfilesAdditionalOptions}}
+```
# Rules: Disallow Specific Profiles
@@ -101,3 +110,7 @@ Rules can declare that a specific profile is not allowed.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/RepositoryValidatingInterceptorExamples.java|disallowProfiles}}
```
+
+# Adding Validation Outcome to HTTP Response
+
+If you have included a [Require Validation](#require-validation) rule to your chain, you can add the `ValidationResultEnrichingInterceptor` to your server if you wish to have validation results added to and OperationOutcome objects that are returned by the server.
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamQuantityNormalizedDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamQuantityNormalizedDao.java
index 65fdaeadf87..a26b226cbb5 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamQuantityNormalizedDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceIndexedSearchParamQuantityNormalizedDao.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.data;
* #L%
*/
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
import org.springframework.data.jpa.repository.JpaRepository;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@@ -27,7 +28,7 @@ import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
-public interface IResourceIndexedSearchParamQuantityNormalizedDao extends JpaRepository {
+public interface IResourceIndexedSearchParamQuantityNormalizedDao extends JpaRepository {
@Modifying
@Query("delete from ResourceIndexedSearchParamQuantityNormalized t WHERE t.myResourcePid = :resid")
void deleteByResourceId(@Param("resid") Long theResourcePid);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/IRepositoryValidatingRule.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/IRepositoryValidatingRule.java
index 59bf694ad13..267494170e1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/IRepositoryValidatingRule.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/IRepositoryValidatingRule.java
@@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.interceptor.validation;
* #L%
*/
+import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -35,7 +36,7 @@ public interface IRepositoryValidatingRule {
String getResourceType();
@Nonnull
- RuleEvaluation evaluate(@Nonnull IBaseResource theResource);
+ RuleEvaluation evaluate(RequestDetails theRequestDetails, @Nonnull IBaseResource theResource);
class RuleEvaluation {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptor.java
index fc503f65d3c..b2c5cb50b50 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptor.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptor.java
@@ -24,6 +24,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import com.google.common.collect.ArrayListMultimap;
@@ -47,7 +48,7 @@ import java.util.stream.Collectors;
public class RepositoryValidatingInterceptor {
private static final Logger ourLog = LoggerFactory.getLogger(RepositoryValidatingInterceptor.class);
- private Multimap myRules = ArrayListMultimap.create();
+ private final Multimap myRules = ArrayListMultimap.create();
private FhirContext myFhirContext;
/**
@@ -113,25 +114,25 @@ public class RepositoryValidatingInterceptor {
* Interceptor hook method. This method should not be called directly.
*/
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
- void create(IBaseResource theResource) {
- handle(theResource);
+ void create(RequestDetails theRequestDetails, IBaseResource theResource) {
+ handle(theRequestDetails, theResource);
}
/**
* Interceptor hook method. This method should not be called directly.
*/
@Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
- void update(IBaseResource theOldResource, IBaseResource theNewResource) {
- handle(theNewResource);
+ void update(RequestDetails theRequestDetails, IBaseResource theOldResource, IBaseResource theNewResource) {
+ handle(theRequestDetails, theNewResource);
}
- private void handle(IBaseResource theNewResource) {
+ private void handle(RequestDetails theRequestDetails, IBaseResource theNewResource) {
Validate.notNull(myFhirContext, "No FhirContext has been set for this interceptor of type: %s", getClass());
String resourceType = myFhirContext.getResourceType(theNewResource);
Collection rules = myRules.get(resourceType);
for (IRepositoryValidatingRule nextRule : rules) {
- IRepositoryValidatingRule.RuleEvaluation outcome = nextRule.evaluate(theNewResource);
+ IRepositoryValidatingRule.RuleEvaluation outcome = nextRule.evaluate(theRequestDetails, theNewResource);
if (!outcome.isPasses()) {
handleFailure(outcome);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingRuleBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingRuleBuilder.java
index 8ef1b30d6a3..fbe10366af2 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingRuleBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingRuleBuilder.java
@@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.interceptor.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.validation.ValidatorResourceFetcher;
+import ca.uhn.fhir.rest.server.interceptor.ValidationResultEnrichingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.apache.commons.lang3.Validate;
import org.apache.commons.text.WordUtils;
@@ -148,13 +149,27 @@ public final class RepositoryValidatingRuleBuilder implements IRuleRoot {
}
/**
- * @param theProfileUrl
- * @return
+ * If set, any resources that contain a profile declaration in Resource.meta.profile
+ * matching {@literal theProfileUrl} will be rejected.
+ *
+ * @param theProfileUrl The profile canonical URL
*/
public FinalizedTypedRule disallowProfile(String theProfileUrl) {
return disallowProfiles(theProfileUrl);
}
+ /**
+ * Perform a resource validation step using the FHIR Instance Validator and reject the
+ * storage if the validation fails.
+ *
+ *
+ * If the {@link ValidationResultEnrichingInterceptor} is registered against the
+ * {@link ca.uhn.fhir.rest.server.RestfulServer} interceptor registry, the validation results
+ * will be appended to any OperationOutcome
resource returned by the server.
+ *
+ *
+ * @see ValidationResultEnrichingInterceptor
+ */
public FinalizedRequireValidationRule requireValidationToDeclaredProfiles() {
RequireValidationRule rule = new RequireValidationRule(myFhirContext, myType, myValidationSupport, myValidatorResourceFetcher);
myRules.add(rule);
@@ -211,7 +226,7 @@ public final class RepositoryValidatingRuleBuilder implements IRuleRoot {
* Specifies that the resource should not be rejected from storage even if it does not pass validation.
*/
@Nonnull
- public FinalizedRequireValidationRule dontReject() {
+ public FinalizedRequireValidationRule neverReject() {
myRule.dontReject();
return this;
}
@@ -251,13 +266,13 @@ public final class RepositoryValidatingRuleBuilder implements IRuleRoot {
* Specifies that if the validation results in any results with a severity of theSeverity
or
* greater, the resource will be tagged with the given tag when it is saved.
*
- * @param theSeverity The minimum severity. Must be drawn from values in {@link ResultSeverityEnum} and must not be null
+ * @param theSeverity The minimum severity. Must be drawn from values in {@link ResultSeverityEnum} and must not be null
* @param theTagSystem The system for the tag to add. Must not be null
- * @param theTagCode The code for the tag to add. Must not be null
+ * @param theTagCode The code for the tag to add. Must not be null
* @return
*/
@Nonnull
- public FinalizedRequireValidationRule tagOnSeverity(@Nonnull String theSeverity,@Nonnull String theTagSystem,@Nonnull String theTagCode) {
+ public FinalizedRequireValidationRule tagOnSeverity(@Nonnull String theSeverity, @Nonnull String theTagSystem, @Nonnull String theTagCode) {
ResultSeverityEnum severity = ResultSeverityEnum.fromCode(toLowerCase(theSeverity));
return tagOnSeverity(severity, theTagSystem, theTagCode);
}
@@ -266,17 +281,68 @@ public final class RepositoryValidatingRuleBuilder implements IRuleRoot {
* Specifies that if the validation results in any results with a severity of theSeverity
or
* greater, the resource will be tagged with the given tag when it is saved.
*
- * @param theSeverity The minimum severity. Must be drawn from values in {@link ResultSeverityEnum} and must not be null
+ * @param theSeverity The minimum severity. Must be drawn from values in {@link ResultSeverityEnum} and must not be null
* @param theTagSystem The system for the tag to add. Must not be null
- * @param theTagCode The code for the tag to add. Must not be null
+ * @param theTagCode The code for the tag to add. Must not be null
* @return
*/
@Nonnull
- public FinalizedRequireValidationRule tagOnSeverity(@Nonnull ResultSeverityEnum theSeverity,@Nonnull String theTagSystem,@Nonnull String theTagCode) {
+ public FinalizedRequireValidationRule tagOnSeverity(@Nonnull ResultSeverityEnum theSeverity, @Nonnull String theTagSystem, @Nonnull String theTagCode) {
myRule.tagOnSeverity(theSeverity, theTagSystem, theTagCode);
return this;
}
+ /**
+ * Configure the validator to never reject extensions
+ */
+ @Nonnull
+ public FinalizedRequireValidationRule allowAnyExtensions() {
+ myRule.getValidator().setAnyExtensionsAllowed(true);
+ return this;
+ }
+
+ /**
+ * Configure the validator to not perform terminology validation
+ */
+ @Nonnull
+ public FinalizedRequireValidationRule disableTerminologyChecks() {
+ myRule.getValidator().setNoTerminologyChecks(true);
+ return this;
+ }
+
+ /**
+ * Configure the validator to raise an error if a resource being validated
+ * declares a profile, and the StructureDefinition for this profile
+ * can not be found.
+ */
+ @Nonnull
+ public FinalizedRequireValidationRule errorOnUnknownProfiles() {
+ myRule.getValidator().setErrorForUnknownProfiles(true);
+ return this;
+ }
+
+ /**
+ * Configure the validator to suppress the information-level message that
+ * is added to the validation result if a profile StructureDefinition does
+ * not declare a binding for a coded field.
+ */
+ @Nonnull
+ public FinalizedRequireValidationRule suppressNoBindingMessage() {
+ myRule.getValidator().setNoBindingMsgSuppressed(true);
+ return this;
+ }
+
+ /**
+ * Configure the validator to suppress the warning-level message that
+ * is added when validating a code that can't be found in an ValueSet that
+ * has an extensible binding.
+ */
+ @Nonnull
+ public FinalizedRequireValidationRule suppressWarningForExtensibleValueSetValidation() {
+ myRule.getValidator().setNoExtensibleWarnings(true);
+ return this;
+ }
+
}
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RequireValidationRule.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RequireValidationRule.java
index f682f1c9590..2d4857332be 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RequireValidationRule.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RequireValidationRule.java
@@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.interceptor.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.validation.ValidatorResourceFetcher;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.interceptor.ValidationResultEnrichingInterceptor;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
@@ -58,7 +60,7 @@ class RequireValidationRule extends BaseTypedRule {
@Nonnull
@Override
- public RuleEvaluation evaluate(@Nonnull IBaseResource theResource) {
+ public RuleEvaluation evaluate(RequestDetails theRequestDetails, @Nonnull IBaseResource theResource) {
FhirValidator validator = getFhirContext().newValidator();
validator.registerValidatorModule(myValidator);
@@ -83,6 +85,8 @@ class RequireValidationRule extends BaseTypedRule {
}
+ ValidationResultEnrichingInterceptor.addValidationResultToRequestDetails(theRequestDetails, outcome);
+
return RuleEvaluation.forSuccess(this);
}
@@ -104,6 +108,22 @@ class RequireValidationRule extends BaseTypedRule {
myRejectOnSeverity = null;
}
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("resourceType", getResourceType())
+ .append("rejectOnSeverity", myRejectOnSeverity)
+ .append("tagOnSeverity", myTagOnSeverity)
+ .toString();
+ }
+
+ public FhirInstanceValidator getValidator() {
+ return myValidator;
+ }
+
+ public void setAllowAnyExtensions() {
+ myValidator.setAnyExtensionsAllowed(true);
+ }
private static class TagOnSeverity {
private final int mySeverity;
@@ -133,13 +153,4 @@ class RequireValidationRule extends BaseTypedRule {
return ResultSeverityEnum.values()[mySeverity].name() + "/" + myTagSystem + "/" + myTagCode;
}
}
-
- @Override
- public String toString() {
- return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
- .append("resourceType", getResourceType())
- .append("rejectOnSeverity", myRejectOnSeverity)
- .append("tagOnSeverity", myTagOnSeverity)
- .toString();
- }
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleDisallowProfile.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleDisallowProfile.java
index f6f499770ef..cc620e6add5 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleDisallowProfile.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleDisallowProfile.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.interceptor.validation;
*/
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -47,7 +48,7 @@ class RuleDisallowProfile extends BaseTypedRule {
@Nonnull
@Override
- public RuleEvaluation evaluate(@Nonnull IBaseResource theResource) {
+ public RuleEvaluation evaluate(RequestDetails theRequestDetails, @Nonnull IBaseResource theResource) {
for (IPrimitiveType next : theResource.getMeta().getProfile()) {
String nextUrl = next.getValueAsString();
String nextUrlNormalized = UrlUtil.normalizeCanonicalUrlForComparison(nextUrl);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleRequireProfileDeclaration.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleRequireProfileDeclaration.java
index 8688a19e668..5ad81127ecc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleRequireProfileDeclaration.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/validation/RuleRequireProfileDeclaration.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.interceptor.validation;
*/
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -39,7 +40,7 @@ class RuleRequireProfileDeclaration extends BaseTypedRule {
@Nonnull
@Override
- public RuleEvaluation evaluate(@Nonnull IBaseResource theResource) {
+ public RuleEvaluation evaluate(RequestDetails theRequestDetails, @Nonnull IBaseResource theResource) {
Optional matchingProfile = theResource
.getMeta()
.getProfile()
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java
index a52a65a02b1..13d853aa356 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java
@@ -31,7 +31,9 @@ import ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderToken;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.TagTypeEnum;
+import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.search.builder.predicate.BaseJoiningPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CompositeUniqueSearchParameterPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.CoordsPredicateBuilder;
@@ -43,12 +45,12 @@ import ca.uhn.fhir.jpa.search.builder.predicate.ResourceIdPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceLinkPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.ResourceTablePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SearchParamPresentPredicateBuilder;
-import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.SourcePredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.StringPredicateBuilder;
import ca.uhn.fhir.jpa.search.builder.predicate.TagPredicateBuilder;
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.jpa.searchparam.extractor.BaseSearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@@ -185,14 +187,9 @@ public class QueryStack {
public void addSortOnQuantity(String theResourceName, String theParamName, boolean theAscending) {
BaseJoiningPredicateBuilder firstPredicateBuilder = mySqlBuilder.getOrCreateFirstPredicateBuilder();
-
- QuantityBasePredicateBuilder sortPredicateBuilder = null;
- if (myModelConfig.isNormalizedQuantitySearchSupported()) {
- sortPredicateBuilder = mySqlBuilder.addQuantityNormalizedPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
- } else {
- sortPredicateBuilder = mySqlBuilder.addQuantityPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
- }
+ QuantityBasePredicateBuilder sortPredicateBuilder;
+ sortPredicateBuilder = mySqlBuilder.addQuantityPredicateBuilder(firstPredicateBuilder.getResourceIdColumn());
Condition hashIdentityPredicate = sortPredicateBuilder.createHashIdentityPredicate(theResourceName, theParamName);
mySqlBuilder.addPredicate(hashIdentityPredicate);
@@ -272,33 +269,33 @@ 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, RuntimeSearchParam theParamDef, List extends IQueryParameterType> theNextAnd, RequestPartitionId theRequestPartitionId) {
- Condition orCondidtion = null;
+ Condition orCondidtion = null;
for (IQueryParameterType next : theNextAnd) {
-
+
if (!(next instanceof CompositeParam, ?>)) {
throw new InvalidRequestException("Invalid type for composite param (must be " + CompositeParam.class.getSimpleName() + ": " + next.getClass());
}
CompositeParam, ?> cp = (CompositeParam, ?>) next;
-
+
RuntimeSearchParam left = theParamDef.getCompositeOf().get(0);
IQueryParameterType leftValue = cp.getLeftValue();
Condition leftPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, left, leftValue, theRequestPartitionId);
-
+
RuntimeSearchParam right = theParamDef.getCompositeOf().get(1);
IQueryParameterType rightValue = cp.getRightValue();
Condition rightPredicate = createPredicateCompositePart(theSourceJoinColumn, theResourceName, right, rightValue, theRequestPartitionId);
-
+
Condition andCondition = toAndPredicate(leftPredicate, rightPredicate);
-
+
if (orCondidtion == null) {
orCondidtion = toOrPredicate(andCondition);
} else {
orCondidtion = toOrPredicate(orCondidtion, andCondition);
}
}
-
+
return orCondidtion;
}
@@ -414,11 +411,11 @@ public class QueryStack {
param.setValueAsQueryToken(null, null, null, theFilter.getValue());
return theQueryStack3.createPredicateResourceId(null, Collections.singletonList(Collections.singletonList(param)), theResourceName, theFilter.getOperation(), theRequestPartitionId);
} else if (paramName.equals(IAnyResource.SP_RES_LANGUAGE)) {
- return theQueryStack3.createPredicateLanguage(Collections.singletonList(Collections.singletonList(new StringParam(theFilter.getValue()))), theFilter.getOperation());
+ return theQueryStack3.createPredicateLanguage(Collections.singletonList(Collections.singletonList(new StringParam(theFilter.getValue()))), theFilter.getOperation());
} else if (paramName.equals(Constants.PARAM_SOURCE)) {
- TokenParam param = new TokenParam();
- param.setValueAsQueryToken(null, null, null, theFilter.getValue());
- return createPredicateSource(null, Collections.singletonList(param));
+ TokenParam param = new TokenParam();
+ param.setValueAsQueryToken(null, null, null, theFilter.getValue());
+ return createPredicateSource(null, Collections.singletonList(param));
} else {
RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(theResourceName, paramName);
if (searchParam == null) {
@@ -642,20 +639,38 @@ public class QueryStack {
List extends IQueryParameterType> theList,
SearchFilterParser.CompareOperation theOperation,
RequestPartitionId theRequestPartitionId) {
-
- QuantityBasePredicateBuilder join = null;
-
- if (myModelConfig.isNormalizedQuantitySearchSupported()) {
- join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
- } else {
- join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
- }
+
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);
}
+ List quantityParams = theList
+ .stream()
+ .map(t -> QuantityParam.toQuantityParam(t))
+ .collect(Collectors.toList());
+
+ QuantityBasePredicateBuilder join = null;
+ boolean normalizedSearchEnabled = myModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+ if (normalizedSearchEnabled) {
+ List normalizedQuantityParams = quantityParams
+ .stream()
+ .map(t -> UcumServiceUtil.toCanonicalQuantityOrNull(t))
+ .filter(t -> t != null)
+ .collect(Collectors.toList());
+
+ if (normalizedQuantityParams.size() == quantityParams.size()) {
+ join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityNormalizedPredicateBuilder(theSourceJoinColumn)).getResult();
+ quantityParams = normalizedQuantityParams;
+ }
+ }
+
+ if (join == null) {
+ join = createOrReusePredicateBuilder(PredicateBuilderTypeEnum.QUANTITY, theSourceJoinColumn, theSearchParam.getName(), () -> mySqlBuilder.addQuantityPredicateBuilder(theSourceJoinColumn)).getResult();
+ }
+
List codePredicates = new ArrayList<>();
- for (IQueryParameterType nextOr : theList) {
+ for (QuantityParam nextOr : quantityParams) {
Condition singleCode = join.createPredicateQuantity(nextOr, theResourceName, theSearchParam.getName(), null, join, theOperation, theRequestPartitionId);
codePredicates.add(singleCode);
}
@@ -923,7 +938,7 @@ public class QueryStack {
@Nullable
public Condition searchForIdsWithAndOr(@Nullable DbColumn theSourceJoinColumn, String theResourceName, String theParamName, List> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) {
-
+
if (theAndOrParams.isEmpty()) {
return null;
}
@@ -1054,7 +1069,7 @@ public class QueryStack {
return toAndPredicate(andPredicates);
}
-
+
}
public void addPredicateCompositeUnique(String theIndexString, RequestPartitionId theRequestPartitionId) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/QuantityBasePredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/QuantityBasePredicateBuilder.java
index 7b610a72dae..bd71d3a1327 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/QuantityBasePredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/QuantityBasePredicateBuilder.java
@@ -20,27 +20,10 @@ package ca.uhn.fhir.jpa.search.builder.predicate;
* #L%
*/
-import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
-import static org.apache.commons.lang3.StringUtils.isBlank;
-
-import java.math.BigDecimal;
-
-import javax.persistence.criteria.CriteriaBuilder;
-
-import org.fhir.ucum.Pair;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import com.healthmarketscience.sqlbuilder.BinaryCondition;
-import com.healthmarketscience.sqlbuilder.ComboCondition;
-import com.healthmarketscience.sqlbuilder.Condition;
-import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
-import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.dao.predicate.SearchFilterParser;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
-import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamBaseQuantity;
import ca.uhn.fhir.jpa.search.builder.QueryStack;
import ca.uhn.fhir.jpa.search.builder.sql.SearchQueryBuilder;
@@ -48,7 +31,18 @@ import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.model.base.composite.BaseQuantityDt;
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
import ca.uhn.fhir.rest.param.QuantityParam;
-import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
+import com.healthmarketscience.sqlbuilder.BinaryCondition;
+import com.healthmarketscience.sqlbuilder.ComboCondition;
+import com.healthmarketscience.sqlbuilder.Condition;
+import com.healthmarketscience.sqlbuilder.dbspec.basic.DbColumn;
+import com.healthmarketscience.sqlbuilder.dbspec.basic.DbTable;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import java.math.BigDecimal;
+
+import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
+import static org.apache.commons.lang3.StringUtils.isBlank;
public abstract class QuantityBasePredicateBuilder extends BaseSearchParamPredicateBuilder {
@@ -59,11 +53,7 @@ public abstract class QuantityBasePredicateBuilder extends BaseSearchParamPredic
@Autowired
private FhirContext myFhirContext;
-
- @Autowired
- private ModelConfig myModelConfig;
-
-
+
/**
* Constructor
*/
@@ -71,38 +61,13 @@ public abstract class QuantityBasePredicateBuilder extends BaseSearchParamPredic
super(theSearchSqlBuilder, theTable);
}
- public Condition createPredicateQuantity(IQueryParameterType theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, QuantityBasePredicateBuilder theFrom, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
-
- String systemValue;
- String unitsValue;
- ParamPrefixEnum cmpValue;
- BigDecimal valueValue;
+ public Condition createPredicateQuantity(QuantityParam theParam, String theResourceName, String theParamName, CriteriaBuilder theBuilder, QuantityBasePredicateBuilder theFrom, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) {
- if (theParam instanceof BaseQuantityDt) {
- BaseQuantityDt param = (BaseQuantityDt) theParam;
- systemValue = param.getSystemElement().getValueAsString();
- unitsValue = param.getUnitsElement().getValueAsString();
- cmpValue = ParamPrefixEnum.forValue(param.getComparatorElement().getValueAsString());
- valueValue = param.getValueElement().getValue();
- } else if (theParam instanceof QuantityParam) {
- QuantityParam param = (QuantityParam) theParam;
- systemValue = param.getSystem();
- unitsValue = param.getUnits();
- cmpValue = param.getPrefix();
- valueValue = param.getValue();
- } else {
- throw new IllegalArgumentException("Invalid quantity type: " + theParam.getClass());
- }
+ String systemValue = theParam.getSystem();
+ String unitsValue = theParam.getUnits();
+ ParamPrefixEnum cmpValue = theParam.getPrefix();
+ BigDecimal valueValue = theParam.getValue();
- if (myModelConfig.isNormalizedQuantitySearchSupported()) {
- //-- convert the value/unit to the canonical form if any to use by the search
- Pair canonicalForm = UcumServiceUtil.getCanonicalForm(systemValue, valueValue, unitsValue);
- if (canonicalForm != null) {
- valueValue = new BigDecimal(canonicalForm.getValue().asDecimal());
- unitsValue = canonicalForm.getCode();
- }
- }
-
Condition hashPredicate;
if (!isBlank(systemValue) && !isBlank(unitsValue)) {
long hash = ResourceIndexedSearchParamBaseQuantity.calculateHashSystemAndUnits(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, systemValue, unitsValue);
@@ -127,5 +92,6 @@ public abstract class QuantityBasePredicateBuilder extends BaseSearchParamPredic
public DbColumn getColumnValue() {
return myColumnValue;
- }
+ }
+
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java
index fc894e424a4..f8fcedd3669 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/BaseTermReadSvcImpl.java
@@ -521,6 +521,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
/*
* ValueSet is pre-expanded in database so let's use that
*/
+ String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "valueSetExpandedUsingPreExpansion");
+ theAccumulator.addMessage(msg);
expandConcepts(theAccumulator, termValueSet, theFilter, theAdd);
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java
index efa965e4f84..a8c6fab7341 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/BaseJpaR4Test.java
@@ -64,7 +64,9 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc;
+import ca.uhn.fhir.jpa.provider.r4.BaseJpaResourceProviderObservationR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
+import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
@@ -810,7 +812,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
Optional first = stream.findFirst();
if (!first.isPresent()) {
- String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount);
+ String expandedValueSetString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theValueSet);
+ String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations. Outcome:\n%s", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount, expandedValueSetString);
fail(failureMessage);
return null;
} else {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java
index 16197adb501..c72cdea95df 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java
@@ -1,18 +1,17 @@
package ca.uhn.fhir.jpa.dao.r4;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.matchesPattern;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.IOException;
-import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-
+import ca.uhn.fhir.jpa.api.config.DaoConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
+import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.QuantityParam;
+import ca.uhn.fhir.rest.param.StringParam;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@@ -33,15 +32,20 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.PageRequest;
-import ca.uhn.fhir.jpa.api.config.DaoConfig;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
-import ca.uhn.fhir.rest.param.QuantityParam;
-import ca.uhn.fhir.rest.param.StringParam;
-import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
-import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
-import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4CreateTest.class);
@@ -51,7 +55,7 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
myDaoConfig.setDefaultSearchParamsCanBeOverridden(new DaoConfig().isDefaultSearchParamsCanBeOverridden());
- myModelConfig.setNormalizedQuantitySearchNotSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
}
@Test
@@ -337,9 +341,9 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
}
@Test
- public void testCreateWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+ public void testCreateWithNormalizedQuantitySearchSupported_AlreadyCanonicalUnit() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
@@ -348,34 +352,38 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
q.setSystem("http://unitsofmeasure.org");
q.setCode("cm");
obs.setValue(q);
-
+
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
-
+
assertTrue(myObservationDao.create(obs).getCreated());
-
- SearchParameterMap map = new SearchParameterMap();
- map.setLoadSynchronous(true);
- QuantityParam qp = new QuantityParam();
- qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
- qp.setValue(new BigDecimal("0.012"));
- qp.setUnits("m");
-
- map.add(Observation.SP_VALUE_QUANTITY, qp);
-
- IBundleProvider found = myObservationDao.search(map);
- List ids = toUnqualifiedVersionlessIdValues(found);
-
- List resources = found.getResources(0, found.size());
-
- assertEquals(1, ids.size());
-
- ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
+
+ // Same value should be placed in both quantity tables
+ runInTransaction(()->{
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ assertEquals("1.2", Double.toString(quantityIndexes.get(0).getValue().doubleValue()));
+ assertEquals("http://unitsofmeasure.org", quantityIndexes.get(0).getSystem());
+ assertEquals("cm", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, normalizedQuantityIndexes.size());
+ assertEquals("0.012", Double.toString(normalizedQuantityIndexes.get(0).getValue()));
+ assertEquals("http://unitsofmeasure.org", normalizedQuantityIndexes.get(0).getSystem());
+ assertEquals("m", normalizedQuantityIndexes.get(0).getUnits());
+ });
+
+ SearchParameterMap map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.012"))
+ .setUnits("m")
+ );
+ assertEquals(1, toUnqualifiedVersionlessIdValues(myObservationDao.search(map)).size());
}
@Test
- public void testCreateWithNormalizedQuantitySearchSupportedWithVerySmallNumber() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+ public void testCreateWithNormalizedQuantitySearchSupported_SmallerThanCanonicalUnit() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
@@ -384,102 +392,358 @@ public class FhirResourceDaoR4CreateTest extends BaseJpaR4Test {
q.setSystem("http://unitsofmeasure.org");
q.setCode("mm");
obs.setValue(q);
-
+
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
-
+
+ myCaptureQueriesListener.clear();
assertTrue(myObservationDao.create(obs).getCreated());
-
- SearchParameterMap map = new SearchParameterMap();
- map.setLoadSynchronous(true);
- QuantityParam qp = new QuantityParam();
- qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
- qp.setValue(new BigDecimal("0.0000000012"));
- qp.setUnits("m");
-
- map.add(Observation.SP_VALUE_QUANTITY, qp);
-
- IBundleProvider found = myObservationDao.search(map);
- List ids = toUnqualifiedVersionlessIdValues(found);
-
- List resources = found.getResources(0, found.size());
-
+ myCaptureQueriesListener.logInsertQueries();
+
+ // Original value should be in Quantity index, normalized should be in normalized table
+ runInTransaction(()->{
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ double d = quantityIndexes.get(0).getValue().doubleValue();
+ assertEquals("1.2E-6", Double.toString(d));
+ assertEquals("http://unitsofmeasure.org", quantityIndexes.get(0).getSystem());
+ assertEquals("mm", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, normalizedQuantityIndexes.size());
+ assertEquals("1.2E-9", Double.toString(normalizedQuantityIndexes.get(0).getValue()));
+ assertEquals("http://unitsofmeasure.org", normalizedQuantityIndexes.get(0).getSystem());
+ assertEquals("m", normalizedQuantityIndexes.get(0).getUnits());
+ });
+
+ String searchSql;
+ SearchParameterMap map;
+ List ids;
+
+ // Try with normalized value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.0000000012"))
+ .setUnits("m")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY_NRML t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '1.2E-9'"));
+ assertEquals(1, ids.size());
+
+ // Try with non-normalized value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.0000012"))
+ .setUnits("mm")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY_NRML t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '1.2E-9'"));
+ assertEquals(1, ids.size());
+
+ // Try with no units value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setValue(new BigDecimal("0.0000012"))
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '0.0000012'"));
assertEquals(1, ids.size());
-
- ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
-
}
@Test
- public void testCreateWithNormalizedQuantitySearchSupportedWithVerySmallNumber2() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+ public void testCreateWithNormalizedQuantitySearchSupported_SmallerThanCanonicalUnit2() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
- q.setValueElement(new DecimalType(149597.870691));
+ q.setValueElement(new DecimalType("149597.870691"));
q.setUnit("MM");
q.setSystem("http://unitsofmeasure.org");
q.setCode("mm");
obs.setValue(q);
-
+
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
-
+
assertTrue(myObservationDao.create(obs).getCreated());
-
+
+ // Original value should be in Quantity index, normalized should be in normalized table
+ runInTransaction(()->{
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ assertEquals("149597.870691", Double.toString(quantityIndexes.get(0).getValue().doubleValue()));
+ assertEquals("http://unitsofmeasure.org", quantityIndexes.get(0).getSystem());
+ assertEquals("mm", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, normalizedQuantityIndexes.size());
+ assertEquals("149.597870691", Double.toString(normalizedQuantityIndexes.get(0).getValue()));
+ assertEquals("http://unitsofmeasure.org", normalizedQuantityIndexes.get(0).getSystem());
+ assertEquals("m", normalizedQuantityIndexes.get(0).getUnits());
+ });
+
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
QuantityParam qp = new QuantityParam();
qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
qp.setValue(new BigDecimal("149.597870691"));
qp.setUnits("m");
-
+
map.add(Observation.SP_VALUE_QUANTITY, qp);
-
+
IBundleProvider found = myObservationDao.search(map);
List ids = toUnqualifiedVersionlessIdValues(found);
-
- List resources = found.getResources(0, found.size());
-
+
+ List resources = found.getResources(0, found.sizeOrThrowNpe());
+
assertEquals(1, ids.size());
-
+
ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
-
+
}
@Test
- public void testCreateWithNormalizedQuantitySearchSupportedWithLargeNumber() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+ public void testCreateWithNormalizedQuantitySearchSupported_LargerThanCanonicalUnit() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+ Observation obs = new Observation();
+ obs.setStatus(Observation.ObservationStatus.FINAL);
+ Quantity q = new Quantity();
+ q.setValueElement(new DecimalType("95.7412345"));
+ q.setUnit("kg/dL");
+ q.setSystem("http://unitsofmeasure.org");
+ q.setCode("kg/dL");
+ obs.setValue(q);
+
+ ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
+
+ assertTrue(myObservationDao.create(obs).getCreated());
+
+ // Original value should be in Quantity index, normalized should be in normalized table
+ runInTransaction(()->{
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ assertEquals("95.7412345", Double.toString(quantityIndexes.get(0).getValue().doubleValue()));
+ assertEquals("http://unitsofmeasure.org", quantityIndexes.get(0).getSystem());
+ assertEquals("kg/dL", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, normalizedQuantityIndexes.size());
+ assertEquals("9.57412345E8", Double.toString(normalizedQuantityIndexes.get(0).getValue()));
+ assertEquals("http://unitsofmeasure.org", normalizedQuantityIndexes.get(0).getSystem());
+ assertEquals("g.m-3", normalizedQuantityIndexes.get(0).getUnits());
+ });
+
+ SearchParameterMap map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("957412345"))
+ .setUnits("g.m-3")
+ );
+ assertEquals(1, toUnqualifiedVersionlessIdValues(myObservationDao.search(map)).size());
+ }
+
+ @Test
+ public void testCreateWithNormalizedQuantitySearchSupported_NonCanonicalUnit() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL);
Quantity q = new Quantity();
q.setValueElement(new DecimalType(95.7412345));
q.setUnit("kg/dL");
- q.setSystem("http://unitsofmeasure.org");
+ q.setSystem("http://example.com");
q.setCode("kg/dL");
obs.setValue(q);
-
+
ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
-
+
assertTrue(myObservationDao.create(obs).getCreated());
-
- SearchParameterMap map = new SearchParameterMap();
- map.setLoadSynchronous(true);
- QuantityParam qp = new QuantityParam();
- qp.setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL);
- qp.setValue(new BigDecimal("957412345"));
- qp.setUnits("g.m-3");
-
- map.add(Observation.SP_VALUE_QUANTITY, qp);
-
- IBundleProvider found = myObservationDao.search(map);
- List ids = toUnqualifiedVersionlessIdValues(found);
-
- List resources = found.getResources(0, found.size());
-
+
+ // Original value should be in Quantity index, normalized should be in normalized table
+ runInTransaction(() -> {
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t -> t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ assertEquals("95.7412345", Double.toString(quantityIndexes.get(0).getValue().doubleValue()));
+ assertEquals("http://example.com", quantityIndexes.get(0).getSystem());
+ assertEquals("kg/dL", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t -> t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(0, normalizedQuantityIndexes.size());
+ });
+
+ List ids;
+
+ // Search should succeed using non-normalized table
+ myCaptureQueriesListener.clear();
+ SearchParameterMap map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem("http://example.com")
+ .setValue(95.7412345)
+ .setUnits("kg/dL")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '95.7412345'"));
+ assertEquals(1, ids.size());
+
+ }
+
+
+ @Test
+ public void testCreateWithNormalizedQuantityStorageSupported_SmallerThanCanonicalUnit() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
+ Observation obs = new Observation();
+ obs.setStatus(Observation.ObservationStatus.FINAL);
+ Quantity q = new Quantity();
+ q.setValueElement(new DecimalType(0.0000012));
+ q.setUnit("MM");
+ q.setSystem("http://unitsofmeasure.org");
+ q.setCode("mm");
+ obs.setValue(q);
+
+ ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
+
+ myCaptureQueriesListener.clear();
+ assertTrue(myObservationDao.create(obs).getCreated());
+ myCaptureQueriesListener.logInsertQueries();
+
+ // Original value should be in Quantity index, normalized should be in normalized table
+ runInTransaction(()->{
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ double d = quantityIndexes.get(0).getValue().doubleValue();
+ assertEquals("1.2E-6", Double.toString(d));
+ assertEquals("http://unitsofmeasure.org", quantityIndexes.get(0).getSystem());
+ assertEquals("mm", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, normalizedQuantityIndexes.size());
+ assertEquals("1.2E-9", Double.toString(normalizedQuantityIndexes.get(0).getValue()));
+ assertEquals("http://unitsofmeasure.org", normalizedQuantityIndexes.get(0).getSystem());
+ assertEquals("m", normalizedQuantityIndexes.get(0).getUnits());
+ });
+
+ String searchSql;
+ SearchParameterMap map;
+ List ids;
+
+ // Try with normalized value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.0000000012"))
+ .setUnits("m")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '1.2E-9'"));
+ assertEquals(0, ids.size());
+
+ // Try with non-normalized value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.0000012"))
+ .setUnits("mm")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '0.0000012'"));
+ assertEquals(1, ids.size());
+
+ // Try with no units value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setValue(new BigDecimal("0.0000012"))
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '0.0000012'"));
+ assertEquals(1, ids.size());
+ }
+
+ @Test
+ public void testCreateWithNormalizedQuantitySearchNotSupported_SmallerThanCanonicalUnit() {
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ Observation obs = new Observation();
+ obs.setStatus(Observation.ObservationStatus.FINAL);
+ Quantity q = new Quantity();
+ q.setValueElement(new DecimalType(0.0000012));
+ q.setUnit("MM");
+ q.setSystem("http://unitsofmeasure.org");
+ q.setCode("mm");
+ obs.setValue(q);
+
+ ourLog.info("Observation1: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
+
+ myCaptureQueriesListener.clear();
+ assertTrue(myObservationDao.create(obs).getCreated());
+ myCaptureQueriesListener.logInsertQueries();
+
+ // Original value should be in Quantity index, no normalized should be in normalized table
+ runInTransaction(()->{
+ List quantityIndexes = myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(1, quantityIndexes.size());
+ double d = quantityIndexes.get(0).getValue().doubleValue();
+ assertEquals("1.2E-6", Double.toString(d));
+ assertEquals("http://unitsofmeasure.org", quantityIndexes.get(0).getSystem());
+ assertEquals("mm", quantityIndexes.get(0).getUnits());
+
+ List normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
+ assertEquals(0, normalizedQuantityIndexes.size());
+ });
+
+ String searchSql;
+ SearchParameterMap map;
+ List ids;
+
+ // Try with normalized value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.0000000012"))
+ .setUnits("m")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '1.2E-9'"));
+ assertEquals(0, ids.size());
+
+ // Try with non-normalized value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL)
+ .setValue(new BigDecimal("0.0000012"))
+ .setUnits("mm")
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '0.0000012'"));
+ assertEquals(1, ids.size());
+
+ // Try with no units value
+ myCaptureQueriesListener.clear();
+ map = SearchParameterMap.newSynchronous(Observation.SP_VALUE_QUANTITY, new QuantityParam()
+ .setValue(new BigDecimal("0.0000012"))
+ );
+ ids = toUnqualifiedVersionlessIdValues(myObservationDao.search(map));
+ searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true,true);
+ assertThat(searchSql, containsString("HFJ_SPIDX_QUANTITY t0"));
+ assertThat(searchSql, containsString("t0.SP_VALUE = '0.0000012'"));
assertEquals(1, ids.size());
-
- ourLog.info("Observation2: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(resources.get(0)));
-
}
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java
index 8d828d8ceb8..5e56111fa2d 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java
@@ -6,6 +6,7 @@ import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
@@ -86,8 +87,8 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
@AfterEach
public void after() {
myDaoConfig.setValidateSearchParameterExpressionsOnSave(new DaoConfig().isValidateSearchParameterExpressionsOnSave());
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
@BeforeEach
public void beforeDisableResultReuse() {
@@ -117,8 +118,8 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
@Test
public void testStoreSearchParamWithBracketsInExpressionNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
myDaoConfig.setMarkResourcesForReindexingUponSearchParameterChange(true);
SearchParameter fooSp = new SearchParameter();
@@ -138,8 +139,8 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
@Test
public void testStoreSearchParamWithBracketsInExpressionNormalizedQuantityStorageSupported() {
-
- myModelConfig.setNormalizedQuantityStorageSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
myDaoConfig.setMarkResourcesForReindexingUponSearchParameterChange(true);
SearchParameter fooSp = new SearchParameter();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java
index 49484ef60be..f77b6d07025 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchMissingTest.java
@@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.DateParam;
@@ -10,7 +11,16 @@ import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.DateType;
+import org.hl7.fhir.r4.model.DecimalType;
+import org.hl7.fhir.r4.model.Location;
+import org.hl7.fhir.r4.model.MedicationRequest;
+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.Quantity;
+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.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -21,8 +31,12 @@ import java.util.List;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.*;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInRelativeOrder;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.not;
+import static org.junit.jupiter.api.Assertions.assertEquals;
public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoR4SearchMissingTest.class);
@@ -31,10 +45,10 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
public void beforeResetMissing() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
}
-
+
@AfterEach
public void afterResetSearch() {
- myModelConfig.setNormalizedQuantitySearchNotSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
}
@Test
@@ -80,9 +94,9 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
@Test
public void testIndexMissingFieldsDisabledDontCreateIndexesWithNormalizedQuantitySearchSupported() {
-
+
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
- myModelConfig.setNormalizedQuantitySearchSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Organization org = new Organization();
org.setActive(true);
myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
@@ -94,12 +108,12 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
assertThat(myResourceIndexedSearchParamQuantityDao.findAll(), empty());
}
-
+
@Test
public void testIndexMissingFieldsDisabledDontCreateIndexesWithNormalizedQuantityStorageSupported() {
-
+
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);
- myModelConfig.setNormalizedQuantityStorageSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
Organization org = new Organization();
org.setActive(true);
myOrganizationDao.create(org, mySrd).getId().toUnqualifiedVersionless();
@@ -111,7 +125,7 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
assertThat(myResourceIndexedSearchParamQuantityDao.findAll(), empty());
}
-
+
@SuppressWarnings("unused")
@Test
public void testSearchResourceReferenceMissingChain() {
@@ -221,8 +235,8 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
String locId = myLocationDao.create(new Location(), mySrd).getId().toUnqualifiedVersionless().getValue();
String locId2 = myLocationDao.create(new Location().setPosition(new Location.LocationPositionComponent(new DecimalType(10), new DecimalType(10))), mySrd).getId().toUnqualifiedVersionless().getValue();
- runInTransaction(()->{
- ourLog.info("Coords:\n * {}", myResourceIndexedSearchParamCoordsDao.findAll().stream().map(t->t.toString()).collect(Collectors.joining("\n * ")));
+ runInTransaction(() -> {
+ ourLog.info("Coords:\n * {}", myResourceIndexedSearchParamCoordsDao.findAll().stream().map(t -> t.toString()).collect(Collectors.joining("\n * ")));
});
{
@@ -309,8 +323,8 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
@Test
public void testSearchWithMissingQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
IIdType notMissing;
IIdType missing;
{
@@ -324,6 +338,12 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
obs.setValue(new Quantity(123));
notMissing = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless();
}
+
+ runInTransaction(() -> {
+ ourLog.info("Quantity Indexes:\n * {}", myResourceIndexedSearchParamQuantityDao.findAll().stream().filter(t -> t.getParamName().equals("value-quantity")).map(t -> t.toString()).collect(Collectors.joining("\n * ")));
+ ourLog.info("Normalized Quantity Indexes:\n * {}", myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t -> t.getParamName().equals("value-quantity")).map(t -> t.toString()).collect(Collectors.joining("\n * ")));
+ });
+
// Quantity Param
{
SearchParameterMap params = new SearchParameterMap();
@@ -341,17 +361,19 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
QuantityParam param = new QuantityParam();
param.setMissing(true);
params.add(Observation.SP_VALUE_QUANTITY, param);
+ myCaptureQueriesListener.clear();
List patients = toUnqualifiedVersionlessIds(myObservationDao.search(params));
+ myCaptureQueriesListener.logSelectQueries();
assertThat(patients, containsInRelativeOrder(missing));
assertThat(patients, not(containsInRelativeOrder(notMissing)));
}
-
+
}
-
+
@Test
public void testSearchWithMissingQuantityWithNormalizedQuantityStorageSupported() {
-
- myModelConfig.setNormalizedQuantityStorageSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
IIdType notMissing;
IIdType missing;
{
@@ -386,10 +408,10 @@ public class FhirResourceDaoR4SearchMissingTest extends BaseJpaR4Test {
assertThat(patients, containsInRelativeOrder(missing));
assertThat(patients, not(containsInRelativeOrder(notMissing)));
}
-
+
}
-
-
+
+
@Test
public void testSearchWithMissingReference() {
IIdType orgId = myOrganizationDao.create(new Organization(), mySrd).getId().toUnqualifiedVersionless();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
index b1c66e1ad73..411ae3d4b37 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
@@ -34,6 +34,7 @@ import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
@@ -185,8 +186,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
@BeforeEach
public void beforeDisableCacheReuse() {
@@ -1236,8 +1237,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test
public void testIndexNoDuplicatesQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Substance res = new Substance();
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
@@ -1246,20 +1247,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantity.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(2, results.size());
- });
-
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantityNormalized.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(2, results.size());
- });
-
List actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 12300, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm"))));
assertThat(actual, contains(id));
@@ -1268,26 +1255,12 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test
public void testQuantityWithNormalizedQuantitySearchSupported_InvalidUCUMCode() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Substance res = new Substance();
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("FOO").setValue(123);
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
-
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantity.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(1, results.size());
- });
-
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantityNormalized.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(1, results.size());
- });
List actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, UcumServiceUtil.UCUM_CODESYSTEM_URL, "FOO"))));
@@ -1297,27 +1270,13 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test
public void testQuantityWithNormalizedQuantitySearchSupported_NotUCUM() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Substance res = new Substance();
res.addInstance().getQuantity().setSystem("http://bar").setCode("FOO").setValue(123);
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantity.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(1, results.size());
- });
-
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantityNormalized.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(1, results.size());
- });
-
List actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, "http://bar", "FOO"))));
assertThat(actual, contains(id));
@@ -1326,8 +1285,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test
public void testIndexNoDuplicatesQuantityWithNormalizedQuantityStorageSupported() {
-
- myModelConfig.setNormalizedQuantityStorageSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
Substance res = new Substance();
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
res.addInstance().getQuantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("m").setValue(123);
@@ -1336,20 +1295,6 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
IIdType id = mySubstanceDao.create(res, mySrd).getId().toUnqualifiedVersionless();
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantity.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(2, results.size());
- });
-
- runInTransaction(() -> {
- Class type = ResourceIndexedSearchParamQuantityNormalized.class;
- List> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
- ourLog.info(toStringMultiline(results));
- assertEquals(2, results.size());
- });
-
List actual = toUnqualifiedVersionlessIds(
mySubstanceDao.search(new SearchParameterMap().setLoadSynchronous(true).add(Substance.SP_QUANTITY, new QuantityParam(null, 123, UcumServiceUtil.UCUM_CODESYSTEM_URL, "m"))));
assertThat(actual, contains(id));
@@ -2753,8 +2698,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test
public void testSearchByMoneyParamWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
ChargeItem ci = new ChargeItem();
ci.getPriceOverride().setValue(123).setCurrency("$");
@@ -3119,8 +3064,8 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
@Test
public void testSearchQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Condition c1 = new Condition();
c1.setAbatement(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(new SimpleQuantity().setValue(1L)));
String id1 = myConditionDao.create(c1).getId().toUnqualifiedVersionless().getValue();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
index 7e6d9f6ede2..fe878a5f23a 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoHashesTest.java
@@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
@@ -143,8 +144,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
myDaoConfig.setDisableHashBasedSearches(false);
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
@BeforeEach
public void beforeInitialize() {
@@ -1193,8 +1194,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
@Test
public void testComponentQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm")))
@@ -1216,8 +1217,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
@Test
public void testComponentQuantityWithNormalizedQuantityStorageSupported() {
-
- myModelConfig.setNormalizedQuantityStorageSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm")))
@@ -1292,8 +1293,8 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
@Test
public void testSearchCompositeParamQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
index 109cc815914..c68a9a705a3 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4Test.java
@@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.JpaResourceDao;
import ca.uhn.fhir.jpa.entity.TermConcept;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
@@ -161,8 +162,8 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete());
myDaoConfig.setEnforceReferenceTargetTypes(new DaoConfig().isEnforceReferenceTargetTypes());
myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields());
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
@BeforeEach
public void before() {
@@ -581,8 +582,8 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
@Test
public void testChoiceParamQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation o3 = new Observation();
o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03");
o3.setValue(new Quantity(QuantityComparator.GREATER_THAN, 123.0, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm", "cm")); // 0.0123m
@@ -682,8 +683,8 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
@Test
public void testChoiceParamQuantityPrecisionWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation o3 = new Observation();
o3.getCode().addCoding().setSystem("foo").setCode("testChoiceParam03");
o3.setValue(new Quantity(null, 123.01, UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm", "cm")); // 0.012301 m
@@ -3500,9 +3501,10 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
}
@Test
- public void testSortByQuantityWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+ @Disabled
+ public void testSortByQuantityWithNormalizedQuantitySearchFullSupported() {
+
+// myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_FULL_SUPPORTED);
Observation res;
res = new Observation();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java
index c7cd84e9db3..24951ce3060 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java
@@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
@@ -23,7 +24,6 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IAnyResource;
@@ -61,7 +61,6 @@ import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
@@ -110,8 +109,8 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
public void after() {
myDaoConfig.setAllowInlineMatchUrlReferences(false);
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
@BeforeEach
public void beforeDisableResultReuse() {
@@ -3131,7 +3130,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest {
@Test
public void testTransactionWithConditionalUpdateDoesntUpdateIfNoChangeWithNormalizedQuantitySearchSupported() {
- myModelConfig.setNormalizedQuantitySearchSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation obs = new Observation();
obs.addIdentifier()
.setSystem("http://acme.org")
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
index 02655f22356..a680c5a27db 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
@@ -1,20 +1,29 @@
package ca.uhn.fhir.jpa.dao.r4;
-import static java.util.Comparator.comparing;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.hl7.fhir.dstu2.model.SimpleQuantity;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
+import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
+import ca.uhn.fhir.jpa.model.config.PartitionSettings;
+import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
+import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
+import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
+import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
+import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
+import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
+import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
+import ca.uhn.fhir.jpa.searchparam.registry.ReadOnlySearchParamCache;
+import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
+import ca.uhn.fhir.util.HapiExtensions;
+import com.google.common.collect.Sets;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
@@ -24,47 +33,30 @@ import org.hl7.fhir.r4.model.Extension;
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.Range;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.SearchParameter;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.FhirVersionEnum;
-import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.context.RuntimeSearchParam;
-import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
-import ca.uhn.fhir.context.support.IValidationSupport;
-import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
-import ca.uhn.fhir.jpa.model.config.PartitionSettings;
-import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
-import ca.uhn.fhir.jpa.model.entity.ModelConfig;
-import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
-import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized;
-import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
-import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
-import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
-import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
-import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
-import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
-import ca.uhn.fhir.jpa.searchparam.registry.ReadOnlySearchParamCache;
-import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
-import ca.uhn.fhir.util.HapiExtensions;
-import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
+import static java.util.Comparator.comparing;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
public class SearchParamExtractorR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(SearchParamExtractorR4Test.class);
private static FhirContext ourCtx = FhirContext.forCached(FhirVersionEnum.R4);
- private static IValidationSupport ourValidationSupport;
private MySearchParamRegistry mySearchParamRegistry;
private PartitionSettings myPartitionSettings;
@@ -81,7 +73,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.addCategory().addCoding().setSystem("SYSTEM").setCode("CODE");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
Set tokens = extractor.extractSearchParamTokens(obs);
assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();
@@ -95,7 +87,7 @@ public class SearchParamExtractorR4Test {
SearchParameter sp = new SearchParameter();
sp.addUseContext().setCode(new Coding().setSystem("http://system").setCode("code"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
Set tokens = extractor.extractSearchParamTokens(sp);
assertEquals(1, tokens.size());
ResourceIndexedSearchParamToken token = (ResourceIndexedSearchParamToken) tokens.iterator().next();
@@ -109,7 +101,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, mySearchParamRegistry);
List tokens = extractor.extractSearchParamTokens(obs)
.stream()
@@ -138,7 +130,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, mySearchParamRegistry);
List tokens = extractor.extractSearchParamTokens(obs)
.stream()
@@ -162,7 +154,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, myPartitionSettings, ourCtx, mySearchParamRegistry);
List tokens = extractor.extractSearchParamTokens(obs)
.stream()
@@ -191,7 +183,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.getCode().addCoding().setSystem("http://system").setCode("code").setDisplay("Help Im a Bug");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, myPartitionSettings, ourCtx, mySearchParamRegistry);
List tokens = extractor.extractSearchParamTokens(obs)
.stream()
@@ -216,7 +208,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.addIdentifier().setSystem("sys").setValue("val").getType().setText("Help Im a Bug");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, mySearchParamRegistry);
List tokens = extractor.extractSearchParamTokens(obs)
.stream()
@@ -246,7 +238,7 @@ public class SearchParamExtractorR4Test {
Observation obs = new Observation();
obs.addIdentifier().setSystem("sys").setValue("val").getType().setText("Help Im a Bug");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), myPartitionSettings, ourCtx, mySearchParamRegistry);
List tokens = extractor.extractSearchParamTokens(obs)
.stream()
@@ -267,7 +259,7 @@ public class SearchParamExtractorR4Test {
Encounter enc = new Encounter();
enc.addLocation().setLocation(new Reference("Location/123"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Encounter", "location");
assertNotNull(param);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(enc);
@@ -282,7 +274,7 @@ public class SearchParamExtractorR4Test {
Consent consent = new Consent();
consent.setSource(new Reference().setReference("Consent/999"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Consent", Consent.SP_SOURCE_REFERENCE);
assertNotNull(param);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(consent);
@@ -297,7 +289,7 @@ public class SearchParamExtractorR4Test {
Patient p = new Patient();
p.addIdentifier().setSystem("sys").setValue("val");
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Patient", Patient.SP_IDENTIFIER);
assertNotNull(param);
ISearchParamExtractor.SearchParamSet params = extractor.extractSearchParamTokens(p, param);
@@ -319,7 +311,7 @@ public class SearchParamExtractorR4Test {
Patient patient = new Patient();
patient.addExtension("http://patext", new Reference("Organization/AAA"));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
ISearchParamExtractor.SearchParamSet links = extractor.extractResourceLinks(patient);
assertEquals(1, links.size());
@@ -335,7 +327,7 @@ public class SearchParamExtractorR4Test {
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code2")))
.setValue(new Quantity().setSystem("http://bar").setCode("code2").setValue(200));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), new PartitionSettings(), ourCtx, mySearchParamRegistry);
Set links = extractor.extractSearchParamQuantity(o1);
ourLog.info("Links:\n {}", links.stream().map(t -> t.toString()).collect(Collectors.joining("\n ")));
assertEquals(4, links.size());
@@ -343,44 +335,44 @@ public class SearchParamExtractorR4Test {
@Test
public void testExtractComponentQuantityWithNormalizedQuantitySearchSupported() {
-
+
ModelConfig modelConfig = new ModelConfig();
-
- modelConfig.setNormalizedQuantitySearchSupported();
-
+
+ modelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(200));
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, mySearchParamRegistry);
Set links = extractor.extractSearchParamQuantityNormalized(o1);
ourLog.info("Links:\n {}", links.stream().map(t -> t.toString()).collect(Collectors.joining("\n ")));
assertEquals(2, links.size());
-
+
}
-
+
@Test
public void testExtractComponentQuantityValueWithNormalizedQuantitySearchSupported() {
-
+
ModelConfig modelConfig = new ModelConfig();
-
- modelConfig.setNormalizedQuantitySearchSupported();
-
+
+ modelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+
Observation o1 = new Observation();
-
+
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("code1")))
.setValue(new Quantity().setSystem(UcumServiceUtil.UCUM_CODESYSTEM_URL).setCode("cm").setValue(200));
RuntimeSearchParam existingCodeSp = mySearchParamRegistry.getActiveSearchParams("Observation").get("component-value-quantity");
-
- SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(modelConfig, new PartitionSettings(), ourCtx, mySearchParamRegistry);
List list = extractor.extractParamValuesAsStrings(existingCodeSp, o1);
-
- assertEquals(1, list.size());
+
+ assertEquals(2, list.size());
}
-
+
private static class MySearchParamRegistry implements ISearchParamRegistry {
@@ -457,9 +449,4 @@ public class SearchParamExtractorR4Test {
}
}
- @BeforeAll
- public static void beforeClass() {
- ourValidationSupport = new DefaultProfileValidationSupport(ourCtx);
- }
-
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorHttpR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorHttpR4Test.java
new file mode 100644
index 00000000000..cc5c9bc8795
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorHttpR4Test.java
@@ -0,0 +1,82 @@
+package ca.uhn.fhir.jpa.interceptor.validation;
+
+import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.jpa.config.BaseConfig;
+import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
+import ca.uhn.fhir.jpa.rp.r4.ObservationResourceProvider;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.PreferReturnEnum;
+import ca.uhn.fhir.rest.server.interceptor.ValidationResultEnrichingInterceptor;
+import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
+import org.hl7.fhir.r4.model.Observation;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+
+import java.util.List;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+
+public class RepositoryValidatingInterceptorHttpR4Test extends BaseJpaR4Test {
+
+ private static final Logger ourLog = LoggerFactory.getLogger(RepositoryValidatingInterceptorHttpR4Test.class);
+ @Autowired
+ protected ObservationResourceProvider myObservationResourceProvider;
+ private RepositoryValidatingInterceptor myValInterceptor;
+ @RegisterExtension
+ protected RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(FhirVersionEnum.R4);
+ @Autowired
+ private ApplicationContext myApplicationContext;
+
+ @BeforeEach
+ public void before() {
+ myValInterceptor = new RepositoryValidatingInterceptor();
+ myValInterceptor.setFhirContext(myFhirCtx);
+ myInterceptorRegistry.registerInterceptor(myValInterceptor);
+
+ myRestfulServerExtension.getRestfulServer().registerProvider(myObservationResourceProvider);
+ myRestfulServerExtension.getRestfulServer().getInterceptorService().registerInterceptor(new ValidationResultEnrichingInterceptor());
+ }
+
+ @AfterEach
+ public void after() {
+ myInterceptorRegistry.unregisterInterceptorsIf(t -> t instanceof RepositoryValidatingInterceptor);
+ }
+
+ @Test
+ public void testValidationOutcomeAddedToRequestResponse() {
+ List rules = newRuleBuilder()
+ .forResourcesOfType("Observation")
+ .requireValidationToDeclaredProfiles()
+ .withBestPracticeWarningLevel("WARNING")
+ .build();
+ myValInterceptor.setRules(rules);
+
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://foo").setCode("123").setDisplay("help im a bug");
+ obs.setStatus(Observation.ObservationStatus.AMENDED);
+
+ MethodOutcome outcome = myRestfulServerExtension
+ .getFhirClient()
+ .create()
+ .resource(obs)
+ .prefer(PreferReturnEnum.OPERATION_OUTCOME)
+ .execute();
+
+ String operationOutcomeEncoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
+ ourLog.info("Outcome: {}", operationOutcomeEncoded);
+ assertThat(operationOutcomeEncoded, containsString("All observations should have a subject"));
+
+ }
+
+ private RepositoryValidatingRuleBuilder newRuleBuilder() {
+ return myApplicationContext.getBean(BaseConfig.REPOSITORY_VALIDATING_RULE_BUILDER, RepositoryValidatingRuleBuilder.class);
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorR4Test.java
index a60be973f43..d48e9cbe61f 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/interceptor/validation/RepositoryValidatingInterceptorR4Test.java
@@ -15,7 +15,6 @@ import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.StringType;
-import org.hl7.fhir.r4.model.UrlType;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -44,6 +43,7 @@ public class RepositoryValidatingInterceptorR4Test extends BaseJpaR4Test {
myValInterceptor = new RepositoryValidatingInterceptor();
myValInterceptor.setFhirContext(myFhirCtx);
myInterceptorRegistry.registerInterceptor(myValInterceptor);
+
}
@AfterEach
@@ -264,13 +264,40 @@ public class RepositoryValidatingInterceptorR4Test extends BaseJpaR4Test {
}
}
+ @Test
+ public void testRequireValidation_AdditionalOptions() {
+ List rules = newRuleBuilder()
+ .forResourcesOfType("Observation")
+ .requireValidationToDeclaredProfiles()
+ .withBestPracticeWarningLevel("IGNORE")
+ .allowAnyExtensions()
+ .disableTerminologyChecks()
+ .errorOnUnknownProfiles()
+ .suppressNoBindingMessage()
+ .suppressWarningForExtensibleValueSetValidation()
+ .build();
+
+ myValInterceptor.setRules(rules);
+
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://foo").setCode("123").setDisplay("help im a bug");
+ obs.setStatus(Observation.ObservationStatus.AMENDED);
+ try {
+ IIdType id = myObservationDao.create(obs).getId();
+ assertEquals("1", id.getVersionIdPart());
+ } catch (PreconditionFailedException e) {
+ // should not happen
+ fail(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
+ }
+ }
+
@Test
public void testRequireValidation_FailNoRejectAndTag() {
List rules = newRuleBuilder()
.forResourcesOfType("Observation")
.requireValidationToDeclaredProfiles()
.withBestPracticeWarningLevel("IGNORE")
- .dontReject()
+ .neverReject()
.tagOnSeverity(ResultSeverityEnum.ERROR, "http://foo", "validation-error")
.build();
myValInterceptor.setRules(rules);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java
index c5054bf7ffa..adedaa93ad3 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java
@@ -35,6 +35,7 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.validation.IValidatorModule;
+import ca.uhn.fhir.validation.ResultSeverityEnum;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
@@ -65,6 +66,7 @@ import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
import org.hl7.fhir.dstu3.model.Bundle.SearchEntryMode;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
+import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Condition;
import org.hl7.fhir.dstu3.model.DateTimeType;
@@ -79,6 +81,7 @@ import org.hl7.fhir.dstu3.model.Enumerations;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.IdType;
+import org.hl7.fhir.dstu3.model.Identifier;
import org.hl7.fhir.dstu3.model.ImagingStudy;
import org.hl7.fhir.dstu3.model.InstantType;
import org.hl7.fhir.dstu3.model.IntegerType;
@@ -110,8 +113,10 @@ import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
+import org.hl7.fhir.dstu3.model.Task;
import org.hl7.fhir.dstu3.model.UnsignedIntType;
import org.hl7.fhir.dstu3.model.ValueSet;
+import org.hl7.fhir.dstu3.model.codesystems.DeviceStatus;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterEach;
@@ -235,6 +240,56 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(0, returnedBundle.getEntry().size());
}
+ @Test
+ public void testSuppressNoExtensibleWarnings() {
+ RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
+ interceptor.setFailOnSeverity(ResultSeverityEnum.INFORMATION);
+ FhirInstanceValidator val = new FhirInstanceValidator(myValidationSupport);
+ val.setNoExtensibleWarnings(true);
+ interceptor.addValidatorModule(val);
+
+ ourRestServer.registerInterceptor(interceptor);
+ try {
+ CodeableConcept codeableConcept = new CodeableConcept();
+ Coding codingCode = codeableConcept.addCoding();
+ codingCode.setCode(DeviceStatus.ACTIVE.toCode());
+ codingCode.setSystem(DeviceStatus.ACTIVE.getSystem());
+
+ Device device = new Device();
+ Identifier identifier = device.addIdentifier();
+ identifier.setType(codeableConcept); // Not valid against valueset with 'Extensible' binding strength
+ ourClient.create().resource(device).execute().getId();
+ } finally {
+ ourRestServer.unregisterInterceptor(interceptor);
+ }
+ }
+
+ @Test
+ public void testSuppressNoBindingMessage() {
+ RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
+ interceptor.setFailOnSeverity(ResultSeverityEnum.INFORMATION);
+ FhirInstanceValidator val = new FhirInstanceValidator(myValidationSupport);
+ val.setNoBindingMsgSuppressed(true);
+ interceptor.addValidatorModule(val);
+
+ ourRestServer.registerInterceptor(interceptor);
+ try {
+ CodeableConcept codeableConcept = new CodeableConcept();
+ Coding codingCode = codeableConcept.addCoding();
+ codingCode.setSystem(DeviceStatus.ACTIVE.toCode());
+ codingCode.setSystem(DeviceStatus.ACTIVE.getSystem());
+
+ Task task = new Task();
+ task.setStatus(Task.TaskStatus.DRAFT);
+ task.setIntent(Task.TaskIntent.FILLERORDER);
+ task.setCode(codeableConcept); // Task.code has no source/binding
+
+ ourClient.create().resource(task).execute().getId();
+ } finally {
+ ourRestServer.unregisterInterceptor(interceptor);
+ }
+ }
+
/**
* See #872
*/
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java
index 56aa943a3a1..2c1f632e331 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ExpungeR4Test.java
@@ -5,6 +5,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@@ -62,8 +63,8 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
@AfterEach
public void afterDisableExpunge() {
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
@BeforeEach
public void beforeEnableExpunge() {
@@ -402,7 +403,7 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
@Test
public void testExpungeSystemEverythingWithNormalizedQuantitySearchSupported() {
- myModelConfig.setNormalizedQuantitySearchSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
createStandardPatients();
mySystemDao.expunge(new ExpungeOptions()
@@ -424,7 +425,7 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
@Test
public void testExpungeSystemEverythingWithNormalizedQuantityStorageSupported() {
- myModelConfig.setNormalizedQuantityStorageSupported();
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
createStandardPatients();
mySystemDao.expunge(new ExpungeOptions()
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
index 62576b232cc..cb2f52d307c 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
@@ -47,6 +47,7 @@ import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
@@ -217,9 +218,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE);
mySearchCoordinatorSvcRaw.setNeverUseLocalSearchForUnitTests(false);
mySearchCoordinatorSvcRaw.cancelAllActiveSearches();
- myDaoConfig.getModelConfig().setNormalizedQuantitySearchNotSupported();
+ myDaoConfig.getModelConfig().setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
- myClient.unregisterInterceptor(myCapturingInterceptor);
+ myClient.unregisterInterceptor(myCapturingInterceptor);
}
@BeforeEach
@@ -4085,8 +4086,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
@Test
public void testSearchWithNormalizedQuantitySearchSupported() throws Exception {
-
- myDaoConfig.getModelConfig().setNormalizedQuantitySearchSupported();
+
+ myDaoConfig.getModelConfig().setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
IIdType pid0;
{
Patient patient = new Patient();
@@ -4167,8 +4168,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
@Test
public void testSearchWithNormalizedQuantitySearchSupported_CombineUCUMOrNonUCUM() throws Exception {
-
- myDaoConfig.getModelConfig().setNormalizedQuantitySearchSupported();
+
+ myDaoConfig.getModelConfig().setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
IIdType pid0;
{
Patient patient = new Patient();
@@ -4222,11 +4223,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
ourLog.info("Observation: \n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs));
}
- // > 1m
- String uri = ourServerBase + "/Observation?value-quantity=" + UrlUtil.escapeUrlParam("100|http://unitsofmeasure.org|cm,100|http://foo|cm");
-
- ourLog.info("uri = " + uri);
- List ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
+ String uri;
+ List ids;
+
+ // With non-normalized
+ uri = ourServerBase + "/Observation?value-quantity=" + UrlUtil.escapeUrlParam("100|http://unitsofmeasure.org|cm,100|http://foo|cm");
+ ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
+ assertEquals(1, ids.size());
+
+ // With normalized
+ uri = ourServerBase + "/Observation?value-quantity=" + UrlUtil.escapeUrlParam("1|http://unitsofmeasure.org|m,100|http://foo|cm");
+ ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
assertEquals(2, ids.size());
}
@@ -6044,8 +6051,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
@Test
public void testUpdateWithNormalizedQuantitySearchSupported() throws Exception {
-
- myDaoConfig.getModelConfig().setNormalizedQuantitySearchSupported();
+
+ myDaoConfig.getModelConfig().setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
IIdType pid0;
{
Patient patient = new Patient();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
index e3764d82969..7830b2b682f 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherR4Test.java
@@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
@@ -103,8 +104,8 @@ public class InMemorySubscriptionMatcherR4Test {
@AfterEach
public void after() throws Exception {
- myModelConfig.setNormalizedQuantitySearchNotSupported();
- }
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED);
+ }
private void assertMatched(Resource resource, SearchParameterMap params) {
InMemoryMatchResult result = match(resource, params);
@@ -249,8 +250,8 @@ public class InMemorySubscriptionMatcherR4Test {
@Test
public void testSearchWithNormalizedQuantitySearchSupported() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
Observation o1 = new Observation();
o1.addComponent()
@@ -282,8 +283,8 @@ public class InMemorySubscriptionMatcherR4Test {
@Test
public void testSearchWithNormalizedQuantitySearchSupported_InvalidUCUMUnit() {
- myModelConfig.setNormalizedQuantitySearchSupported();
-
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://bar").setCode("foo")))
@@ -298,8 +299,8 @@ public class InMemorySubscriptionMatcherR4Test {
@Test
public void testSearchWithNormalizedQuantitySearchSupported_NoSystem() {
- myModelConfig.setNormalizedQuantitySearchSupported();
-
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://bar").setCode("foo")))
@@ -314,9 +315,9 @@ public class InMemorySubscriptionMatcherR4Test {
@Test
public void testSearchWithNormalizedQuantitySearchSupported_NotUcumSystem() {
-
- myModelConfig.setNormalizedQuantitySearchSupported();
-
+
+ myModelConfig.setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
+
Observation o1 = new Observation();
o1.addComponent()
.setCode(new CodeableConcept().addCoding(new Coding().setSystem("http://foo").setCode("cm")))
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java
index 67d04ee1b6c..58634856eb6 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/ValueSetExpansionR4Test.java
@@ -655,6 +655,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
CodeSystem codeSystem = myCodeSystemDao.read(myExtensionalCsId);
ourLog.info("CodeSystem:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(codeSystem));
+ // If this ever fails, it just means that new codes have been added to the
+ // code system used by this test, so the numbers below may also need to be
+ // updated
+ assertEquals(24, codeSystem.getConcept().size());
+
ValueSet valueSet = myValueSetDao.read(myExtensionalVsId);
ourLog.info("ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(valueSet));
@@ -662,9 +667,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSetExpansionOptions options = new ValueSetExpansionOptions()
.setOffset(0)
- .setCount(23);
+ .setCount(24);
ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet);
- ourLog.info("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet));
+ String expandedValueSetString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet);
+ ourLog.info("Expanded ValueSet:\n" + expandedValueSetString);
+ assertThat(expandedValueSetString, containsString("ValueSet was expanded using a pre-calculated expansion"));
assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal());
assertEquals(myDaoConfig.getPreExpandValueSetsDefaultOffset(), expandedValueSet.getExpansion().getOffset());
@@ -672,9 +679,9 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals("offset", expandedValueSet.getExpansion().getParameter().get(0).getName());
assertEquals(0, expandedValueSet.getExpansion().getParameter().get(0).getValueIntegerType().getValue().intValue());
assertEquals("count", expandedValueSet.getExpansion().getParameter().get(1).getName());
- assertEquals(23, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
+ assertEquals(24, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
- assertEquals(23, expandedValueSet.getExpansion().getContains().size());
+ assertEquals(24, expandedValueSet.getExpansion().getContains().size());
ValueSet.ValueSetExpansionContainsComponent concept = assertExpandedValueSetContainsConcept(expandedValueSet, "http://acme.org", "8450-9", "Systolic blood pressure--expiration", 2);
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
index 7736ca31df6..48e1e210066 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
@@ -256,6 +256,8 @@ public class JdbcUtils {
return new ColumnType(ColumnTypeEnum.BLOB, length);
case Types.CLOB:
return new ColumnType(ColumnTypeEnum.CLOB, length);
+ case Types.DOUBLE:
+ return new ColumnType(ColumnTypeEnum.DOUBLE, length);
default:
throw new IllegalArgumentException("Don't know how to handle datatype " + dataType + " for column " + theColumnName + " on table " + theTableName);
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java
index 76ed7fd1974..7a552491560 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeEnum.java
@@ -22,14 +22,14 @@ package ca.uhn.fhir.jpa.migrate.taskdef;
public enum ColumnTypeEnum {
- LONG,
- STRING,
- DATE_ONLY,
- DATE_TIMESTAMP,
- BOOLEAN,
- FLOAT,
- INT,
- BLOB,
- CLOB
-
+ LONG,
+ STRING,
+ DATE_ONLY,
+ DATE_TIMESTAMP,
+ BOOLEAN,
+ FLOAT,
+ INT,
+ BLOB,
+ CLOB,
+ DOUBLE;
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java
index a2043a0b012..0523592f4d2 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ColumnTypeToDriverTypeToSqlType.java
@@ -49,6 +49,14 @@ public class ColumnTypeToDriverTypeToSqlType {
setColumnType(ColumnTypeEnum.FLOAT, DriverTypeEnum.ORACLE_12C, "float");
setColumnType(ColumnTypeEnum.FLOAT, DriverTypeEnum.POSTGRES_9_4, "float");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.H2_EMBEDDED, "double");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.DERBY_EMBEDDED, "double");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.MARIADB_10_1, "double precision");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.MYSQL_5_7, "double precision");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.MSSQL_2012, "double precision");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.ORACLE_12C, "double precision");
+ setColumnType(ColumnTypeEnum.DOUBLE, DriverTypeEnum.POSTGRES_9_4, "float8");
+
setColumnType(ColumnTypeEnum.LONG, DriverTypeEnum.H2_EMBEDDED, "bigint");
setColumnType(ColumnTypeEnum.LONG, DriverTypeEnum.DERBY_EMBEDDED, "bigint");
setColumnType(ColumnTypeEnum.LONG, DriverTypeEnum.MARIADB_10_1, "bigint");
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
index 8cc9d077f6d..04f725cc42e 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java
@@ -119,6 +119,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks {
.addForeignKey("20210111.3", "FKRCJOVMUH5KC0O6FVBLE319PYV")
.toColumn("RES_ID")
.references("HFJ_RESOURCE", "RES_ID");
+
+ Builder.BuilderWithTableName quantityTable = version.onTable("HFJ_SPIDX_QUANTITY");
+ quantityTable.modifyColumn("20210116.1", "SP_VALUE").nullable().failureAllowed().withType(ColumnTypeEnum.DOUBLE);
+
}
protected void init520() {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
index 35402230b88..cb2a4d65466 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
@@ -90,7 +90,7 @@ public class ModelConfig {
private IPrimitiveType myPeriodIndexEndOfTime;
private NormalizedQuantitySearchLevel myNormalizedQuantitySearchLevel;
-
+
/**
* Constructor
*/
@@ -576,6 +576,47 @@ public class ModelConfig {
myPeriodIndexEndOfTime = thePeriodIndexEndOfTime;
}
+ /**
+ * Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
+ *
+ *
+ * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
+ *
+ *
+ * Here is the UCUM service support level
+ *
+ * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.
+ * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.
+ * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.
+ *
+ *
+ *
+ * @since 5.3.0
+ */
+ public NormalizedQuantitySearchLevel getNormalizedQuantitySearchLevel() {
+ return myNormalizedQuantitySearchLevel;
+ }
+
+ /**
+ * Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
+ *
+ *
+ * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
+ *
+ *
+ * Here is the UCUM service support level
+ *
+ * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.
+ * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.
+ * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.
+ *
+ *
+ *
+ * @since 5.3.0
+ */
+ public void setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel theNormalizedQuantitySearchLevel) {
+ myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
+ }
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");
@@ -589,43 +630,4 @@ public class ModelConfig {
}
- /**
- * Set the UCUM service support level
- *
- *
- * The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
- *
- *
- * Here is the UCUM service support level
- *
- * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.
- * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.
- * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.
- * - {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_FULL_SUPPORTED}, Quantity is stored in only in {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching. NOTE: this option is not supported yet.
- *
- *
- *
- * @since 5.3.0
- */
- public NormalizedQuantitySearchLevel getNormalizedQuantitySearchLevel() {
- return myNormalizedQuantitySearchLevel;
- }
- public void setNormalizedQuantitySearchLevel(NormalizedQuantitySearchLevel theNormalizedQuantitySearchLevel) {
- myNormalizedQuantitySearchLevel = theNormalizedQuantitySearchLevel;
- }
- public boolean isNormalizedQuantitySearchSupported() {
- return myNormalizedQuantitySearchLevel.equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED);
- }
- public boolean isNormalizedQuantityStorageSupported() {
- return myNormalizedQuantitySearchLevel.equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED);
- }
- public void setNormalizedQuantitySearchNotSupported() {
- myNormalizedQuantitySearchLevel = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED;
- }
- public void setNormalizedQuantityStorageSupported() {
- myNormalizedQuantitySearchLevel = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED;
- }
- public void setNormalizedQuantitySearchSupported() {
- myNormalizedQuantitySearchLevel = NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED;
- }
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/NormalizedQuantitySearchLevel.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/NormalizedQuantitySearchLevel.java
index f7c819b5a92..56b9dee8b31 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/NormalizedQuantitySearchLevel.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/NormalizedQuantitySearchLevel.java
@@ -53,5 +53,6 @@ public enum NormalizedQuantitySearchLevel {
* The existing non normalized quantity will be not supported
* NOTE: this option is not supported in this release
*/
+ // When this is enabled, we can enable testSortByQuantityWithNormalizedQuantitySearchFullSupported()
//NORMALIZED_QUANTITY_SEARCH_FULL_SUPPORTED,
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
index 75e99a94150..51e587fa29d 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantity.java
@@ -69,7 +69,7 @@ public class ResourceIndexedSearchParamQuantity extends ResourceIndexedSearchPar
@Column(name = "SP_VALUE", nullable = true)
@ScaledNumberField
- public BigDecimal myValue;
+ public Double myValue;
public ResourceIndexedSearchParamQuantity() {
super();
@@ -99,11 +99,11 @@ public class ResourceIndexedSearchParamQuantity extends ResourceIndexedSearchPar
}
public BigDecimal getValue() {
- return myValue;
+ return myValue != null ? new BigDecimal(myValue) : null;
}
public ResourceIndexedSearchParamQuantity setValue(BigDecimal theValue) {
- myValue = theValue;
+ myValue = theValue != null ? theValue.doubleValue() : null;
return this;
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java
index 0234d0fa4a2..03a89138db1 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalized.java
@@ -40,7 +40,6 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.fhir.ucum.Pair;
-import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField;
import org.hibernate.search.mapper.pojo.mapping.definition.annotation.ScaledNumberField;
@@ -84,23 +83,14 @@ public class ResourceIndexedSearchParamQuantityNormalized extends ResourceIndexe
super();
}
- public ResourceIndexedSearchParamQuantityNormalized(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, BigDecimal theValue, String theSystem, String theUnits) {
+ public ResourceIndexedSearchParamQuantityNormalized(PartitionSettings thePartitionSettings, String theResourceType, String theParamName, double theValue, String theSystem, String theUnits) {
this();
setPartitionSettings(thePartitionSettings);
setResourceType(theResourceType);
setParamName(theParamName);
setSystem(theSystem);
-
- //-- convert the value/unit to the canonical form if any, otherwise store the original value/units pair
- Pair canonicalForm = UcumServiceUtil.getCanonicalForm(theSystem, theValue, theUnits);
- if (canonicalForm != null) {
- setValue(Double.parseDouble(canonicalForm.getValue().asDecimal()));
- setUnits(canonicalForm.getCode());
- } else {
- setValue(theValue);
- setUnits(theUnits);
- }
-
+ setValue(theValue);
+ setUnits(theUnits);
calculateHashes();
}
@@ -123,17 +113,13 @@ public class ResourceIndexedSearchParamQuantityNormalized extends ResourceIndexe
public ResourceIndexedSearchParamQuantityNormalized setValue(Double theValue) {
myValue = theValue;
return this;
- }
- public void setValue(BigDecimal theValue) {
- if (theValue != null)
- myValue = theValue.doubleValue();
}
- public BigDecimal getValueBigDecimal() {
- if (myValue == null)
- return null;
- return new BigDecimal(myValue);
+
+ public ResourceIndexedSearchParamQuantityNormalized setValue(double theValue) {
+ myValue = theValue;
+ return this;
}
-
+
//-- myId
@Override
public Long getId() {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/UcumServiceUtil.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/UcumServiceUtil.java
index 3224067fbc0..d718eaaedb6 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/UcumServiceUtil.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/UcumServiceUtil.java
@@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.model.util;
import java.io.InputStream;
import java.math.BigDecimal;
+import ca.uhn.fhir.rest.param.QuantityParam;
import org.fhir.ucum.Decimal;
import org.fhir.ucum.Pair;
import org.fhir.ucum.UcumEssenceService;
@@ -32,6 +33,8 @@ import org.slf4j.LoggerFactory;
import ca.uhn.fhir.util.ClasspathUtil;
+import javax.annotation.Nullable;
+
/**
* It's a wrapper of UcumEssenceService
*
@@ -85,7 +88,7 @@ public class UcumServiceUtil {
return null;
init();
- Pair theCanonicalPair = null;
+ Pair theCanonicalPair;
try {
Decimal theDecimal = new Decimal(theValue.toPlainString(), theValue.precision());
@@ -97,4 +100,19 @@ public class UcumServiceUtil {
return theCanonicalPair;
}
+ @Nullable
+ public static QuantityParam toCanonicalQuantityOrNull(QuantityParam theQuantityParam) {
+ Pair canonicalForm = getCanonicalForm(theQuantityParam.getSystem(), theQuantityParam.getValue(), theQuantityParam.getUnits());
+ if (canonicalForm != null) {
+ BigDecimal valueValue = new BigDecimal(canonicalForm.getValue().asDecimal());
+ String unitsValue = canonicalForm.getCode();
+ return new QuantityParam()
+ .setSystem(theQuantityParam.getSystem())
+ .setValue(valueValue)
+ .setUnits(unitsValue)
+ .setPrefix(theQuantityParam.getPrefix());
+ } else {
+ return null;
+ }
+ }
}
diff --git a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java
index 4affba1865c..1e3b5d94727 100644
--- a/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java
+++ b/hapi-fhir-jpaserver-model/src/test/java/ca/uhn/fhir/jpa/model/entity/ResourceIndexedSearchParamQuantityNormalizedTest.java
@@ -1,34 +1,13 @@
package ca.uhn.fhir.jpa.model.entity;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
-import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
-
import org.junit.jupiter.api.Test;
-import java.math.BigDecimal;
-
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class ResourceIndexedSearchParamQuantityNormalizedTest {
- private ResourceIndexedSearchParamQuantityNormalized createParam(String theParamName, String theValue, String theSystem, String theUnits) {
- ResourceIndexedSearchParamQuantityNormalized token = new ResourceIndexedSearchParamQuantityNormalized(new PartitionSettings(), "Observation", theParamName, new BigDecimal(theValue), theSystem, theUnits);
- token.setResource(new ResourceTable().setResourceType("Patient"));
- return token;
- }
-
- @Test
- public void testHashFunctions() {
- ResourceIndexedSearchParamQuantityNormalized token = createParam("Quanity", "123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm");
- token.calculateHashes();
-
- // Make sure our hashing function gives consistent results
- assertEquals(5219730978980909111L, token.getHashIdentity().longValue());
- assertEquals(-2454931617586657338L, token.getHashIdentityAndUnits().longValue());
- assertEquals(878263047209296227L, token.getHashIdentitySystemAndUnits().longValue());
- }
-
@Test
public void testEquals() {
@@ -46,37 +25,5 @@ public class ResourceIndexedSearchParamQuantityNormalizedTest {
assertNotEquals(val1, "");
}
- @Test
- public void testUcum() {
-
- //-- system is ucum
- ResourceIndexedSearchParamQuantityNormalized token1 = createParam("Quanity", "123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "cm");
- token1.calculateHashes();
-
- assertEquals("m", token1.getUnits());
- assertEquals(Double.parseDouble("1.23001"), token1.getValue());
-
- //-- small number
- token1 = createParam("Quanity", "0.000001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "mm");
- token1.calculateHashes();
-
- assertEquals("m", token1.getUnits());
- assertEquals(Double.parseDouble("0.000000001"), token1.getValue());
-
-
- // -- non ucum system
- ResourceIndexedSearchParamQuantityNormalized token2 = createParam("Quanity", "123.001", "http://abc.org", "cm");
- token2.calculateHashes();
-
- assertEquals("cm", token2.getUnits());
- assertEquals(Double.parseDouble("123.001"), token2.getValue());
-
- // -- unsupported ucum code
- ResourceIndexedSearchParamQuantityNormalized token3 = createParam("Quanity", "123.001", UcumServiceUtil.UCUM_CODESYSTEM_URL, "unknown");
- token3.calculateHashes();
-
- assertEquals("unknown", token3.getUnits());
- assertEquals(Double.parseDouble("123.001"), token3.getValue());
- }
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
index 25af4ddb6c4..d2324ef102f 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
@@ -30,6 +30,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@@ -38,6 +39,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantityNormalized
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
+import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
@@ -49,7 +51,7 @@ import com.google.common.collect.Sets;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
-import org.hibernate.search.engine.spatial.GeoPoint;
+import org.fhir.ucum.Pair;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
@@ -78,7 +80,6 @@ import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
-import static ca.uhn.fhir.jpa.searchparam.extractor.GeopointNormalizer.normalizeLongitude;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
@@ -201,10 +202,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
extractor = createReferenceExtractor();
return extractReferenceParamsAsQueryTokens(theSearchParam, theResource, extractor);
case QUANTITY:
- if (myModelConfig.isNormalizedQuantitySearchSupported())
- extractor = createQuantityNormalizedExtractor(theResource);
- else
+ if (myModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) {
+ extractor = new CompositeExtractor(
+ createQuantityExtractor(theResource),
+ createQuantityNormalizedExtractor(theResource)
+ );
+ } else {
extractor = createQuantityExtractor(theResource);
+ }
break;
case URI:
extractor = createUriExtractor(theResource);
@@ -216,6 +221,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
default:
throw new UnsupportedOperationException("Type " + theSearchParam.getParamType() + " not supported for extraction");
}
+
return extractParamsAsQueryTokens(theSearchParam, theResource, extractor);
}
@@ -377,14 +383,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
}
-
+
@Override
public SearchParamSet extractSearchParamQuantityNormalized(IBaseResource theResource) {
IExtractor extractor = createQuantityNormalizedExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
}
-
- private IExtractor createQuantityExtractor(IBaseResource theResource) {
+
+ private IExtractor createQuantityExtractor(IBaseResource theResource) {
return (params, searchParam, value, path) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
@@ -394,7 +400,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String resourceType = toRootTypeName(theResource);
switch (nextType) {
case "Quantity":
- addQuantity_Quantity(resourceType, params, searchParam, value);
+ addQuantity_Quantity(resourceType, params, searchParam, value);
break;
case "Money":
addQuantity_Money(resourceType, params, searchParam, value);
@@ -406,11 +412,11 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
addUnexpectedDatatypeWarning(params, searchParam, value);
break;
}
- };
+ };
}
-
+
private IExtractor createQuantityNormalizedExtractor(IBaseResource theResource) {
-
+
return (params, searchParam, value, path) -> {
if (value.getClass().equals(myLocationPositionDefinition.getImplementingClass())) {
return;
@@ -434,7 +440,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
};
}
-
+
@Override
public SearchParamSet extractSearchParamStrings(IBaseResource theResource) {
IExtractor extractor = createStringExtractor(theResource);
@@ -546,24 +552,31 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
-
- theParams.add(nextEntity);
+
+ theParams.add(nextEntity);
}
}
-
-
- private void addQuantity_QuantityNormalized(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
+
+
+ private void addQuantity_QuantityNormalized(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional> valueField = myQuantityValueValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
String system = extractValueAsString(myQuantitySystemValueChild, theValue);
String code = extractValueAsString(myQuantityCodeValueChild, theValue);
- ResourceIndexedSearchParamQuantityNormalized nextEntity = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, theSearchParam.getName(), nextValueValue, system, code);
- theParams.add(nextEntity);
+ //-- convert the value/unit to the canonical form if any
+ Pair canonicalForm = UcumServiceUtil.getCanonicalForm(system, nextValueValue, code);
+ if (canonicalForm != null) {
+ double canonicalValue = Double.parseDouble(canonicalForm.getValue().asDecimal());
+ String canonicalUnits = canonicalForm.getCode();
+ ResourceIndexedSearchParamQuantityNormalized nextEntity = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, theSearchParam.getName(), canonicalValue, system, canonicalUnits);
+ theParams.add(nextEntity);
+ }
+
}
}
-
+
private void addQuantity_Money(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
@@ -572,14 +585,14 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
-
+
ResourceIndexedSearchParamQuantity nextEntity = new ResourceIndexedSearchParamQuantity(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
theParams.add(nextEntity);
- }
+ }
}
-
- private void addQuantity_MoneyNormalized(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
+
+ private void addQuantity_MoneyNormalized(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional> valueField = myMoneyValueChild.getAccessor().getFirstValueOrNull(theValue);
if (valueField.isPresent() && valueField.get().getValue() != null) {
BigDecimal nextValueValue = valueField.get().getValue();
@@ -587,22 +600,22 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
String nextValueString = "urn:iso:std:iso:4217";
String nextValueCode = extractValueAsString(myMoneyCurrencyChild, theValue);
String searchParamName = theSearchParam.getName();
-
- ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, searchParamName, nextValueValue, nextValueString, nextValueCode);
- theParams.add(nextEntityNormalized);
+
+ ResourceIndexedSearchParamQuantityNormalized nextEntityNormalized = new ResourceIndexedSearchParamQuantityNormalized(myPartitionSettings, theResourceType, searchParamName, nextValueValue.doubleValue(), nextValueString, nextValueCode);
+ theParams.add(nextEntityNormalized);
}
}
-
-
+
+
private void addQuantity_Range(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
Optional high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
- high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
+ high.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
}
-
+
private void addQuantity_RangeNormalized(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
@@ -610,7 +623,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
Optional high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
}
-
+
private void addToken_Identifier(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
String system = extractValueAsString(myIdentifierSystemValueChild, theValue);
String value = extractValueAsString(myIdentifierValueValueChild, theValue);
@@ -1163,11 +1176,27 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@FunctionalInterface
private interface IExtractor {
-
void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath);
}
+ private static class CompositeExtractor implements IExtractor {
+
+ private final IExtractor myExtractor0;
+ private final IExtractor myExtractor1;
+
+ private CompositeExtractor(IExtractor theExtractor0, IExtractor theExtractor1) {
+ myExtractor0 = theExtractor0;
+ myExtractor1 = theExtractor1;
+ }
+
+ @Override
+ public void extract(SearchParamSet theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
+ myExtractor0.extract(theParams, theSearchParam, theValue, thePath);
+ myExtractor1.extract(theParams, theSearchParam, theValue, thePath);
+ }
+ }
+
private class ResourceLinkExtractor implements IExtractor {
private PathAndRef myPathAndRef = null;
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
index 3006df3ddec..656a3cbb50a 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceIndexedSearchParams.java
@@ -25,6 +25,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
@@ -36,9 +37,14 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamUri;
import ca.uhn.fhir.jpa.model.entity.ResourceLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.model.util.UcumServiceUtil;
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;
@@ -51,6 +57,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 static org.apache.commons.lang3.StringUtils.compare;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@@ -158,21 +165,30 @@ public final class ResourceIndexedSearchParams {
return myPopulatedResourceLinkParameters;
}
- public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theParam) {
+ public boolean matchParam(ModelConfig theModelConfig, String theResourceName, String theParamName, RuntimeSearchParam theParamDef, IQueryParameterType theValue) {
if (theParamDef == null) {
return false;
}
- Collection extends BaseResourceIndexedSearchParam> resourceParams;
+ Collection extends BaseResourceIndexedSearchParam> resourceParams = null;
+ IQueryParameterType value = theValue;
switch (theParamDef.getParamType()) {
case TOKEN:
resourceParams = myTokenParams;
break;
case QUANTITY:
- if (theModelConfig.isNormalizedQuantitySearchSupported())
- resourceParams = myQuantityNormalizedParams;
- else
- resourceParams = myQuantityParams;
+ if (theModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) {
+ QuantityParam quantity = QuantityParam.toQuantityParam(theValue);
+ QuantityParam normalized = UcumServiceUtil.toCanonicalQuantityOrNull(quantity);
+ if (normalized != null) {
+ resourceParams = myQuantityNormalizedParams;
+ value = normalized;
+ }
+ }
+
+ if (resourceParams == null) {
+ resourceParams = myQuantityParams;
+ }
break;
case STRING:
resourceParams = myStringParams;
@@ -187,7 +203,7 @@ public final class ResourceIndexedSearchParams {
resourceParams = myDateParams;
break;
case REFERENCE:
- return matchResourceLinks(theModelConfig, theResourceName, theParamName, theParam, theParamDef.getPath());
+ return matchResourceLinks(theModelConfig, theResourceName, theParamName, value, theParamDef.getPath());
case COMPOSITE:
case HAS:
case SPECIAL:
@@ -197,11 +213,16 @@ public final class ResourceIndexedSearchParams {
if (resourceParams == null) {
return false;
}
- Predicate namedParamPredicate = param ->
- param.getParamName().equalsIgnoreCase(theParamName) &&
- param.matches(theParam);
- return resourceParams.stream().anyMatch(namedParamPredicate);
+ for (BaseResourceIndexedSearchParam nextParam : resourceParams) {
+ if (nextParam.getParamName().equalsIgnoreCase(theParamName)) {
+ if (nextParam.matches(value)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
}
/**
@@ -275,10 +296,7 @@ public final class ResourceIndexedSearchParams {
public void findMissingSearchParams(PartitionSettings thePartitionSettings, ModelConfig theModelConfig, ResourceTable theEntity, Set> theActiveSearchParams) {
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.STRING, myStringParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.NUMBER, myNumberParams);
- if (theModelConfig.isNormalizedQuantitySearchSupported())
- findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityNormalizedParams);
- else
- findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
+ findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.QUANTITY, myQuantityParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.DATE, myDateParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.URI, myUriParams);
findMissingSearchParams(thePartitionSettings, theModelConfig, theEntity, theActiveSearchParams, RestSearchParameterTypeEnum.TOKEN, myTokenParams);
@@ -309,10 +327,7 @@ public final class ResourceIndexedSearchParams {
param = new ResourceIndexedSearchParamNumber();
break;
case QUANTITY:
- if (theModelConfig.isNormalizedQuantitySearchSupported())
- param = new ResourceIndexedSearchParamQuantityNormalized();
- else
- param = new ResourceIndexedSearchParamQuantity();
+ param = new ResourceIndexedSearchParamQuantity();
break;
case STRING:
param = new ResourceIndexedSearchParamString()
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
index 89074ba36ad..e92e62dc9d9 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3.java
@@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@@ -50,9 +49,9 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
// This constructor is used by tests
@VisibleForTesting
- public SearchParamExtractorDstu3(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
+ public SearchParamExtractorDstu3(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
- initFhirPathEngine(theValidationSupport);
+ initFhirPathEngine();
start();
}
@@ -75,13 +74,12 @@ public class SearchParamExtractorDstu3 extends BaseSearchParamExtractor implemen
public void start() {
super.start();
if (myFhirPathEngine == null) {
- IValidationSupport support = myApplicationContext.getBean(IValidationSupport.class);
- initFhirPathEngine(support);
+ initFhirPathEngine();
}
}
- public void initFhirPathEngine(IValidationSupport theSupport) {
- IWorkerContext worker = new HapiWorkerContext(getContext(), theSupport);
+ public void initFhirPathEngine() {
+ IWorkerContext worker = new HapiWorkerContext(getContext(), getContext().getValidationSupport());
myFhirPathEngine = new FHIRPathEngine(worker);
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
index 60701fb23b3..5e61663ab01 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java
@@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@@ -62,9 +61,9 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
// This constructor is used by tests
@VisibleForTesting
- public SearchParamExtractorR4(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, IValidationSupport theValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
+ public SearchParamExtractorR4(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
- initFhirPath(theValidationSupport);
+ initFhirPath();
start();
}
@@ -82,13 +81,12 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
public void start() {
super.start();
if (myFhirPathEngine == null) {
- IValidationSupport support = myApplicationContext.getBean(IValidationSupport.class);
- initFhirPath(support);
+ initFhirPath();
}
}
- public void initFhirPath(IValidationSupport theSupport) {
- IWorkerContext worker = new HapiWorkerContext(getContext(), theSupport);
+ public void initFhirPath() {
+ IWorkerContext worker = new HapiWorkerContext(getContext(), getContext().getValidationSupport());
myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR4HostServices());
}
@@ -96,7 +94,7 @@ public class SearchParamExtractorR4 extends BaseSearchParamExtractor implements
private static class SearchParamExtractorR4HostServices implements FHIRPathEngine.IEvaluationContext {
- private Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
+ private final Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
index af1e43bd107..38e6b082f12 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR5.java
@@ -21,8 +21,6 @@ package ca.uhn.fhir.jpa.searchparam.extractor;
*/
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
-import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@@ -58,9 +56,9 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
/**
* Constructor for unit tests
*/
- public SearchParamExtractorR5(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, DefaultProfileValidationSupport theDefaultProfileValidationSupport, ISearchParamRegistry theSearchParamRegistry) {
+ public SearchParamExtractorR5(ModelConfig theModelConfig, PartitionSettings thePartitionSettings, FhirContext theCtx, ISearchParamRegistry theSearchParamRegistry) {
super(theModelConfig, thePartitionSettings, theCtx, theSearchParamRegistry);
- initFhirPath(theDefaultProfileValidationSupport);
+ initFhirPath();
start();
}
@@ -69,13 +67,12 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
public void start() {
super.start();
if (myFhirPathEngine == null) {
- IValidationSupport support = myApplicationContext.getBean(IValidationSupport.class);
- initFhirPath(support);
+ initFhirPath();
}
}
- public void initFhirPath(IValidationSupport theSupport) {
- IWorkerContext worker = new HapiWorkerContext(getContext(), theSupport);
+ public void initFhirPath() {
+ IWorkerContext worker = new HapiWorkerContext(getContext(), getContext().getValidationSupport());
myFhirPathEngine = new FHIRPathEngine(worker);
myFhirPathEngine.setHostServices(new SearchParamExtractorR5HostServices());
}
@@ -88,7 +85,7 @@ public class SearchParamExtractorR5 extends BaseSearchParamExtractor implements
private static class SearchParamExtractorR5HostServices implements FHIRPathEngine.IEvaluationContext {
- private Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
+ private final Map myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java
index 1727f6a1ba4..43d0945b341 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java
@@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.cross.IResourceLookup;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber;
@@ -119,8 +120,8 @@ public class SearchParamExtractorService {
ISearchParamExtractor.SearchParamSet quantities = extractSearchParamQuantity(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantities);
theParams.myQuantityParams.addAll(quantities);
-
- if (myModelConfig.isNormalizedQuantityStorageSupported()|| myModelConfig.isNormalizedQuantitySearchSupported()) {
+
+ if (myModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_STORAGE_SUPPORTED) || myModelConfig.getNormalizedQuantitySearchLevel().equals(NormalizedQuantitySearchLevel.NORMALIZED_QUANTITY_SEARCH_SUPPORTED)) {
ISearchParamExtractor.SearchParamSet quantitiesNormalized = extractSearchParamQuantityNormalized(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized);
theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized);
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
index cd6ffa86f18..bbb7ef23b6b 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/IndexStressTest.java
@@ -9,9 +9,6 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.util.StopWatch;
-import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
-import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -23,7 +20,6 @@ import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -43,9 +39,8 @@ public class IndexStressTest {
FhirContext ctx = FhirContext.forDstu3();
IValidationSupport mockValidationSupport = mock(IValidationSupport.class);
when(mockValidationSupport.getFhirContext()).thenReturn(ctx);
- IValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(new DefaultProfileValidationSupport(ctx), mockValidationSupport));
ISearchParamRegistry searchParamRegistry = mock(ISearchParamRegistry.class);
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ctx, validationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ctx, searchParamRegistry);
extractor.start();
Map spMap = ctx
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
index 11bd9c98f6d..4a6106d9fea 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java
@@ -1,11 +1,10 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
-import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam;
@@ -33,7 +32,6 @@ import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.text.Normalizer;
@@ -51,8 +49,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class SearchParamExtractorDstu3Test {
- private static FhirContext ourCtx = FhirContext.forDstu3();
- private static IValidationSupport ourValidationSupport;
+ private static FhirContext ourCtx = FhirContext.forCached(FhirVersionEnum.DSTU3);
@Test
public void testParamWithOrInPath() {
@@ -61,7 +58,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
Set tokens = extractor.extractSearchParamTokens(obs);
assertEquals(1, tokens.size());
@@ -84,7 +81,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
Set params = extractor.extractSearchParamStrings(questionnaire);
assertEquals(1, params.size());
@@ -102,7 +99,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
Set params = extractor.extractSearchParamNumber(enc);
assertEquals(1, params.size());
@@ -120,7 +117,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
Set params = extractor.extractSearchParamNumber(enc);
assertEquals(1, params.size());
@@ -132,7 +129,7 @@ public class SearchParamExtractorDstu3Test {
public void testEmptyPath() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE));
@@ -148,7 +145,7 @@ public class SearchParamExtractorDstu3Test {
public void testStringMissingResourceType() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
searchParamRegistry.addSearchParam(new RuntimeSearchParam("foo", "foo", "communication.language.coding.system | communication.language.coding.code", RestSearchParameterTypeEnum.STRING, Sets.newHashSet(), Sets.newHashSet(), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE));
@@ -165,7 +162,7 @@ public class SearchParamExtractorDstu3Test {
public void testInvalidType() {
MySearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
{
@@ -216,7 +213,7 @@ public class SearchParamExtractorDstu3Test {
ISearchParamRegistry searchParamRegistry = new MySearchParamRegistry();
- SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, ourValidationSupport, searchParamRegistry);
+ SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), new PartitionSettings(), ourCtx, searchParamRegistry);
extractor.start();
ISearchParamExtractor.SearchParamSet coords = extractor.extractSearchParamTokens(loc);
assertEquals(1, coords.size());
@@ -227,7 +224,7 @@ public class SearchParamExtractorDstu3Test {
private static class MySearchParamRegistry implements ISearchParamRegistry {
- private List myAddedSearchParams = new ArrayList<>();
+ private final List myAddedSearchParams = new ArrayList<>();
public void addSearchParam(RuntimeSearchParam... theSearchParam) {
myAddedSearchParams.clear();
@@ -306,9 +303,4 @@ public class SearchParamExtractorDstu3Test {
TestUtil.clearAllStaticFieldsForUnitTest();
}
- @BeforeAll
- public static void beforeClass() {
- ourValidationSupport = new DefaultProfileValidationSupport(ourCtx);
- }
-
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
index bd1744df856..97185778d9a 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java
@@ -17,7 +17,6 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
-import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.jpa.cache.ResourceChangeResult;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@@ -66,15 +65,15 @@ public class SearchParamExtractorMegaTest {
ctx = FhirContext.forDstu3();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorDstu3(new ModelConfig(), partitionSettings, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry));
+ process(ctx, new SearchParamExtractorDstu3(new ModelConfig(), partitionSettings, ctx, searchParamRegistry));
ctx = FhirContext.forR4();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorR4(new ModelConfig(), partitionSettings, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry));
+ process(ctx, new SearchParamExtractorR4(new ModelConfig(), partitionSettings, ctx, searchParamRegistry));
ctx = FhirContext.forR5();
searchParamRegistry = new MySearchParamRegistry(ctx);
- process(ctx, new SearchParamExtractorR5(new ModelConfig(), partitionSettings, ctx, new DefaultProfileValidationSupport(ctx), searchParamRegistry));
+ process(ctx, new SearchParamExtractorR5(new ModelConfig(), partitionSettings, ctx, searchParamRegistry));
}
private void process(FhirContext theCtx, BaseSearchParamExtractor theExtractor) throws Exception {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BaseValidatingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BaseValidatingInterceptor.java
index c352102c51f..b84e30a6490 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BaseValidatingInterceptor.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/BaseValidatingInterceptor.java
@@ -45,7 +45,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* request with an {@link UnprocessableEntityException HTTP 422 Unprocessable Entity}.
*/
@Interceptor
-public abstract class BaseValidatingInterceptor {
+public abstract class BaseValidatingInterceptor extends ValidationResultEnrichingInterceptor {
/**
* Default value:
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.java
index a046dbf7304..25c035c8692 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/RequestValidatingInterceptor.java
@@ -31,8 +31,6 @@ import ca.uhn.fhir.rest.server.method.ResourceParameter;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.ValidationResult;
-import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
-import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -51,11 +49,6 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor validationResult = (List) theRequestDetails.getUserData().remove(REQUEST_VALIDATION_RESULT);
+ if (validationResult != null) {
+ for (ValidationResult next : validationResult) {
+ next.populateOperationOutcome(oo);
+ }
+ }
+ }
+
+ }
+
+ return true;
+ }
+
+
+ @SuppressWarnings("unchecked")
+ public static void addValidationResultToRequestDetails(@Nullable RequestDetails theRequestDetails, @Nonnull ValidationResult theValidationResult) {
+ if (theRequestDetails != null) {
+ List results = (List) theRequestDetails.getUserData().computeIfAbsent(REQUEST_VALIDATION_RESULT, t -> new ArrayList<>(2));
+ results.add(theValidationResult);
+ }
+ }
+}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java
index 60c32608290..32d6ab8b196 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java
@@ -9,15 +9,14 @@ import fr.inria.lille.shexjava.schema.parsing.GenParser;
import fr.inria.lille.shexjava.validation.RecursiveValidation;
import fr.inria.lille.shexjava.validation.ValidationAlgorithm;
import org.apache.commons.rdf.api.Graph;
-import org.apache.commons.rdf.api.IRI;
+import org.apache.commons.rdf.api.RDFTerm;
import org.apache.commons.rdf.rdf4j.RDF4J;
import org.eclipse.rdf4j.model.Model;
-import org.eclipse.rdf4j.model.impl.SimpleIRI;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Base;
-import org.hl7.fhir.r4.model.DomainResource;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
@@ -25,8 +24,10 @@ import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringReader;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -53,6 +54,15 @@ public class RDFParserTest extends BaseTest {
fhirSchema = GenParser.parseSchema(schemaFile, Collections.emptyList());
}
+ // If we can't round-trip JSON, we skip the Turtle round-trip test.
+ private static ArrayList jsonRoundTripErrors = new ArrayList();
+ @AfterAll
+ static void reportJsonRoundTripErrors() {
+ System.out.println(jsonRoundTripErrors.size() + " tests disqualified because of JSON round-trip errors");
+ for (String e : jsonRoundTripErrors)
+ System.out.println(e);
+ }
+
/**
* This test method has a method source for each JSON file in the resources/rdf-test-input directory (see #getInputFiles).
* Each input file is expected to be a JSON representation of an R4 FHIR resource.
@@ -62,71 +72,48 @@ public class RDFParserTest extends BaseTest {
* 3. Perform a graph validation on the resulting RDF using ShEx and ShEx-java -- ensure validation passed
* 4. Parse the RDF string into the HAPI object model -- ensure resource instance is not null
* 5. Perform deep equals comparison of JSON-originated instance and RDF-originated instance -- ensure equality
- * @param inputFile -- path to resource file to be tested
+ * @param referenceFilePath -- path to resource file to be tested
* @throws IOException -- thrown when parsing RDF string into graph model
*/
@ParameterizedTest
@MethodSource("getInputFiles")
- public void testRDFRoundTrip(String inputFile) throws IOException {
- FileInputStream inputStream = new FileInputStream(inputFile);
- IBaseResource resource;
- String resourceType;
- // Parse JSON input as Resource
- resource = ourCtx.newJsonParser().parseResource(inputStream);
- assertNotNull(resource);
- resourceType = resource.fhirType();
-
- // Write the resource out to an RDF String
- String rdfContent = ourCtx.newRDFParser().encodeResourceToString(resource);
- assertNotNull(rdfContent);
+ public void testRDFRoundTrip(String referenceFilePath) throws IOException {
+ String referenceFileName = referenceFilePath.substring(referenceFilePath.lastIndexOf("/")+1);
+ IBaseResource referenceResource = parseJson(new FileInputStream(referenceFilePath));
+ String referenceJson = serializeJson(ourCtx, referenceResource);
// Perform ShEx validation on RDF
- RDF4J factory = new RDF4J();
- GlobalFactory.RDFFactory = factory; //set the global factory used in shexjava
+ String turtleString = serializeRdf(ourCtx, referenceResource);
+ validateRdf(turtleString, referenceFileName, referenceResource);
- // load the model
- String baseIRI = "http://a.example.shex/";
- Model data = Rio.parse(new StringReader(rdfContent), baseIRI, RDFFormat.TURTLE);
+ // If we can round-trip JSON
+ IBaseResource viaJsonResource = parseJson(new ByteArrayInputStream(referenceJson.getBytes()));
+ if (((Base)viaJsonResource).equalsDeep((Base)referenceResource)) {
- String rootSubjectIri = null;
- for (org.eclipse.rdf4j.model.Resource resourceStream : data.subjects()) {
- if (resourceStream instanceof SimpleIRI) {
- Model filteredModel = data.filter(resourceStream, factory.getValueFactory().createIRI(NODE_ROLE_IRI), factory.getValueFactory().createIRI(TREE_ROOT_IRI), (org.eclipse.rdf4j.model.Resource)null);
- if (filteredModel != null && filteredModel.subjects().size() == 1) {
- Optional rootResource = filteredModel.subjects().stream().findFirst();
- if (rootResource.isPresent()) {
- rootSubjectIri = rootResource.get().stringValue();
- break;
- }
+ // Parse RDF content as resource
+ IBaseResource viaTurtleResource = parseRdf(ourCtx, new StringReader(turtleString));
+ assertNotNull(viaTurtleResource);
- }
+ // Compare original JSON-based resource against RDF-based resource
+ String viaTurtleJson = serializeJson(ourCtx, viaTurtleResource);
+ if (!((Base)viaTurtleResource).equalsDeep((Base)referenceResource)) {
+ String failMessage = referenceFileName + ": failed to round-trip Turtle ";
+ if (referenceJson.equals(viaTurtleJson))
+ throw new Error(failMessage
+ + "\nttl: " + turtleString
+ + "\nexp: " + referenceJson);
+ else
+ assertEquals(referenceJson, viaTurtleJson, failMessage + "\nttl: " + turtleString);
}
- }
-
- // create the graph
- Graph dataGraph = factory.asGraph(data);
-
- // choose focus node and shapelabel
- IRI focusNode = factory.createIRI(rootSubjectIri);
- Label shapeLabel = new Label(factory.createIRI(FHIR_SHAPE_PREFIX + resourceType));
-
- ValidationAlgorithm validation = new RecursiveValidation(fhirSchema, dataGraph);
- validation.validate(focusNode, shapeLabel);
- boolean result = validation.getTyping().isConformant(focusNode, shapeLabel);
- assertTrue(result);
-
- // Parse RDF content as resource
- IBaseResource parsedResource = ourCtx.newRDFParser().parseResource(new StringReader(rdfContent));
- assertNotNull(parsedResource);
-
- // Compare original JSON-based resource against RDF-based resource
- if (parsedResource instanceof DomainResource) {
- // This is a hack because this initializes the collection if it is empty
- ((DomainResource) parsedResource).getContained();
- boolean deepEquals = ((Base)parsedResource).equalsDeep((Base)resource);
- assertTrue(deepEquals);
} else {
- ourLog.warn("Input JSON did not yield a DomainResource");
+ String gotString = serializeJson(ourCtx, viaJsonResource);
+ String skipMessage = referenceFileName + ": failed to round-trip JSON" +
+ (referenceJson.equals(gotString)
+ ? "\ngot: " + gotString + "\nexp: " + referenceJson
+ : "\nsome inequality not visible in: " + referenceJson);
+ System.out.println(referenceFileName + " skipped");
+ // Specific messages are printed at end of run.
+ jsonRoundTripErrors.add(skipMessage);
}
}
@@ -135,11 +122,98 @@ public class RDFParserTest extends BaseTest {
List resourceList = new ArrayList<>();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
Resource[] resources = resolver.getResources("classpath:rdf-test-input/*.json") ;
- for (Resource resource: resources){
+ for (Resource resource: resources)
resourceList.add(resource.getFile().getPath());
- }
return resourceList.stream();
}
+ // JSON functions
+ public IBaseResource parseJson(InputStream inputStream) {
+ IParser refParser = ourCtx.newJsonParser();
+ refParser.setStripVersionsFromReferences(false);
+ // parser.setDontStripVersionsFromReferencesAtPaths();
+ IBaseResource ret = refParser.parseResource(inputStream);
+ assertNotNull(ret);
+ return ret;
+ }
+
+ public String serializeJson(FhirContext ctx, IBaseResource resource) {
+ IParser jsonParser = ctx.newJsonParser();
+ jsonParser.setStripVersionsFromReferences(false);
+ String ret = jsonParser.encodeResourceToString(resource);
+ assertNotNull(ret);
+ return ret;
+ }
+
+ // Rdf (Turtle) functions
+ public IBaseResource parseRdf(FhirContext ctx, StringReader inputStream) {
+ IParser refParser = ctx.newRDFParser();
+ IBaseResource ret = refParser.parseResource(inputStream);
+ assertNotNull(ret);
+ return ret;
+ }
+
+ public String serializeRdf(FhirContext ctx, IBaseResource resource) {
+ IParser rdfParser = ourCtx.newRDFParser();
+ rdfParser.setStripVersionsFromReferences(false);
+ rdfParser.setServerBaseUrl("http://a.example/fhir/");
+ String ret = rdfParser.encodeResourceToString(resource);
+ assertNotNull(ret);
+ return ret;
+ }
+
+ public void validateRdf(String rdfContent, String referenceFileName, IBaseResource referenceResource) throws IOException {
+ String baseIRI = "http://a.example/shex/";
+ RDF4J factory = new RDF4J();
+ GlobalFactory.RDFFactory = factory; //set the global factory used in shexjava
+ Model data = Rio.parse(new StringReader(rdfContent), baseIRI, RDFFormat.TURTLE);
+ FixedShapeMapEntry fixedMapEntry = new FixedShapeMapEntry(factory, data, referenceResource.fhirType(), baseIRI);
+ Graph dataGraph = factory.asGraph(data); // create the graph
+ ValidationAlgorithm validation = new RecursiveValidation(fhirSchema, dataGraph);
+ validation.validate(fixedMapEntry.node, fixedMapEntry.shape);
+ boolean result = validation.getTyping().isConformant(fixedMapEntry.node, fixedMapEntry.shape);
+ assertTrue(result,
+ referenceFileName + ": failed to validate " + fixedMapEntry
+ + "\n" + referenceFileName
+ + "\n" + rdfContent
+ );
+ }
+
+ // Shape Expressions functions
+ class FixedShapeMapEntry {
+ RDFTerm node;
+ Label shape;
+
+ FixedShapeMapEntry(RDF4J factory, Model data, String resourceType, String baseIRI) {
+ String rootSubjectIri = null;
+ // StmtIterator i = data.listStatements();
+ for (org.eclipse.rdf4j.model.Resource resourceStream : data.subjects()) {
+// if (resourceStream instanceof SimpleIRI) {
+ Model filteredModel = data.filter(resourceStream, factory.getValueFactory().createIRI(NODE_ROLE_IRI), factory.getValueFactory().createIRI(TREE_ROOT_IRI), (org.eclipse.rdf4j.model.Resource)null);
+ if (filteredModel != null && filteredModel.subjects().size() == 1) {
+ Optional rootResource = filteredModel.subjects().stream().findFirst();
+ if (rootResource.isPresent()) {
+ rootSubjectIri = rootResource.get().stringValue();
+ break;
+ }
+
+ }
+// }
+ }
+
+ // choose focus node and shapelabel
+ this.node = rootSubjectIri.indexOf(":") == -1
+ ? factory.createBlankNode(rootSubjectIri)
+ : factory.createIRI(rootSubjectIri);
+ Label shapeLabel = new Label(factory.createIRI(FHIR_SHAPE_PREFIX + resourceType));
+// this.node = focusNode;
+ this.shape = shapeLabel;
+ }
+
+ public String toString() {
+ return "<" + node.toString() + ">@" + shape.toPrettyString();
+ }
+ }
+
}
diff --git a/hapi-fhir-structures-r4/src/test/resources/rdf-test-input/DISABLED/README.md b/hapi-fhir-structures-r4/src/test/resources/rdf-test-input/DISABLED/README.md
new file mode 100644
index 00000000000..43a69e1cb67
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/resources/rdf-test-input/DISABLED/README.md
@@ -0,0 +1,5 @@
+# Why is this disabled?
+
+| file | reason |
+| - | - |
+| [bundle-response](bundle-response.json) | [entry\[0\].response.outcome.issue](bundle-response.json#L50-L61) is not in [R4 Resource](http://hl7.org/fhir/resource.html#Resource) |
diff --git a/hapi-fhir-structures-r4/src/test/resources/rdf-test-input/bundle-response.json b/hapi-fhir-structures-r4/src/test/resources/rdf-test-input/DISABLED/bundle-response.json
similarity index 100%
rename from hapi-fhir-structures-r4/src/test/resources/rdf-test-input/bundle-response.json
rename to hapi-fhir-structures-r4/src/test/resources/rdf-test-input/DISABLED/bundle-response.json
diff --git a/hapi-fhir-structures-r4/src/test/resources/rdf-validation/fhir-r4.shex b/hapi-fhir-structures-r4/src/test/resources/rdf-validation/fhir-r4.shex
index 040ca2b9c67..ec75815f9e1 100644
--- a/hapi-fhir-structures-r4/src/test/resources/rdf-validation/fhir-r4.shex
+++ b/hapi-fhir-structures-r4/src/test/resources/rdf-validation/fhir-r4.shex
@@ -13267,7 +13267,7 @@ fhirvs:contact-point-use ["home" "work" "temp" "old" "mobile"]
fhirvs:immunization-status ["completed" "entered-in-error" "not-done"]
# This value set includes all possible codes from BCP-13 (http://tools.ietf.org/html/bcp13)
-fhirvs:mimetypes EXTERNAL
+fhirvs:mimetypes .
# The use of an address.
fhirvs:address-use ["home" "work" "temp" "old" "billing"]
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java
index 5a5354aa8ca..8b8175683d9 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirInstanceValidator.java
@@ -29,6 +29,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
+ private boolean noExtensibleWarnings = false;
+ private boolean noBindingMsgSuppressed = false;
private volatile VersionSpecificWorkerContextWrapper myWrappedWorkerContext;
private boolean errorForUnknownProfiles;
private boolean assumeValidRestReferences;
@@ -184,6 +186,34 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
noTerminologyChecks = theNoTerminologyChecks;
}
+ /**
+ * If set to {@literal true} (default is false) no extensible warnings suppressed
+ */
+ public boolean isNoExtensibleWarnings() {
+ return noExtensibleWarnings;
+ }
+
+ /**
+ * If set to {@literal true} (default is false) no extensible warnings is suppressed
+ */
+ public void setNoExtensibleWarnings(final boolean theNoExtensibleWarnings) {
+ noExtensibleWarnings = theNoExtensibleWarnings;
+ }
+
+ /**
+ * If set to {@literal true} (default is false) no binding message is suppressed
+ */
+ public boolean isNoBindingMsgSuppressed() {
+ return noBindingMsgSuppressed;
+ }
+
+ /**
+ * If set to {@literal true} (default is false) no binding message is suppressed
+ */
+ public void setNoBindingMsgSuppressed(final boolean theNoBindingMsgSuppressed) {
+ noBindingMsgSuppressed = theNoBindingMsgSuppressed;
+ }
+
public List getExtensionDomains() {
return myExtensionDomains;
}
@@ -198,6 +228,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IInsta
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
+ .setNoExtensibleWarnings(isNoExtensibleWarnings())
+ .setNoBindingMsgSuppressed(isNoBindingMsgSuppressed())
.setValidatorResourceFetcher(getValidatorResourceFetcher())
.setAssumeValidRestReferences(isAssumeValidRestReferences())
.validate(wrappedWorkerContext, theValidationCtx);
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java
index 9452feb433a..33817480b03 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java
@@ -38,6 +38,8 @@ class ValidatorWrapper {
private boolean myErrorForUnknownProfiles;
private boolean myNoTerminologyChecks;
private boolean myAssumeValidRestReferences;
+ private boolean myNoExtensibleWarnings;
+ private boolean myNoBindingMsgSuppressed;
private Collection extends String> myExtensionDomains;
private IResourceValidator.IValidatorResourceFetcher myValidatorResourceFetcher;
@@ -77,6 +79,16 @@ class ValidatorWrapper {
return this;
}
+ public ValidatorWrapper setNoExtensibleWarnings(boolean theNoExtensibleWarnings) {
+ myNoExtensibleWarnings = theNoExtensibleWarnings;
+ return this;
+ }
+
+ public ValidatorWrapper setNoBindingMsgSuppressed(boolean theNoBindingMsgSuppressed) {
+ myNoBindingMsgSuppressed = theNoBindingMsgSuppressed;
+ return this;
+ }
+
public ValidatorWrapper setExtensionDomains(Collection extends String> theExtensionDomains) {
myExtensionDomains = theExtensionDomains;
return this;
@@ -105,6 +117,8 @@ class ValidatorWrapper {
v.setErrorForUnknownProfiles(myErrorForUnknownProfiles);
v.getExtensionDomains().addAll(myExtensionDomains);
v.setFetcher(myValidatorResourceFetcher);
+ v.setNoExtensibleWarnings(myNoExtensibleWarnings);
+ v.setNoBindingMsgSuppressed(myNoBindingMsgSuppressed);
v.setAllowXsiLocation(true);
List messages = new ArrayList<>();
diff --git a/pom.xml b/pom.xml
index d94a6d732f6..142adf8cf1f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -531,6 +531,10 @@
basecade
Anders Havn
+
+ vedion
+ Anders Havn
+
zaewonyx
@@ -744,7 +748,7 @@
2.3.0.1
2.3.1
3.16.0
- 2.25.1
+ 3.0.0
9.4.35.v20201120
3.0.2
@@ -758,19 +762,19 @@
6.1.5.Final
4.4.13
4.5.13
- 2.11.2
+ 2.12.1
2.11.2
3.1.0
1.8
4.0.0.Beta3
- 5.2.0
- 9.3.8
+ 5.6.5
+ 9.5.4
2.8.8
9.8.0-15
1.2_5
1.7.30
2.11.1
- 5.3.2
+ 5.3.3
2.4.2
4.2.3.RELEASE
@@ -888,6 +892,12 @@
com.helger
ph-schematron
${ph_schematron_version}
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+
+
com.helger
@@ -927,7 +937,7 @@
org.apache.commons
commons-collections4
- 4.3
+ 4.4
org.apache.commons
@@ -1351,7 +1361,7 @@
org.fusesource.jansi
jansi
- 1.18
+ 2.1.1
org.glassfish
@@ -1956,7 +1966,7 @@
org.jacoco
jacoco-maven-plugin
- 0.8.5
+ 0.8.6
ca/uhn/fhir/model/dstu2/**/*.class