Merge remote-tracking branch 'origin/master' into ng_20201218_survivorship_poc

This commit is contained in:
Nick 2021-01-18 09:25:58 -05:00
commit b00f47ef81
66 changed files with 1556 additions and 813 deletions

View File

@ -151,6 +151,10 @@
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
</ignoredDependencies>
<ignoredResourcePatterns>
<ignoredResourcePattern>.*\.txt$</ignoredResourcePattern>

View File

@ -1260,10 +1260,12 @@ class ParserState<T> {
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());
}
}
}
}

View File

@ -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;

View File

@ -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<QuantityParam> 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());
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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!"

View File

@ -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."

View File

@ -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):
<ul>
<li>SLF4j (All Modules): 1.7.28 -&gt; 1.7.30</li>
<li>Woodstox (XML FHIR Parser): 4.4.1 -&gt; 6.2.3 (Note that the Maven groupId has changed from <code>org.codehaus.woodstox</code> to <code>com.fasterxml.woodstox</code> and the Maven artifactId has changed from <code>woodstox-core-asl</code> to <code>woodstox-core</code> for this library)</li>
<li>Spring (JPA): 5.2.3.RELEASE -&gt; 5.2.9.RELEASE</li>
<li>Datasource-Proxy (JPA): 1.5.1 -&gt; 1.7</li>
<li>Jetty (JPA Starter): 9.4.30.v20200611 -&gt; 9.4.35.v20201120</li>
<li>Guava (JP): 29.0-jre -&gt; 30.1-jre</li>
<li>Hibernate ORM (JPA Server): 5.4.22.FINAL -&gt; 5.4.26.FINAL</li>
<li>Spring (JPA Server): 5.2.9.RELEASE -&gt; 5.3.2</li>
<li>Spring Data (JPA Server): 2.2.0.RELEASE -&gt; 2.4.2</li>
<li>Hibernate Search (JPA Server): 5.11.5.FINAL -&gt; 6.0.0.Final</li>
<li>Lucene(HAPI FHIR JPA Server): 5.5.5 -&gt; 8.7.0</li>
<li>Spring Boot (JPA Starter): 2.2.6.RELEASE -&gt; 2.4.1</li>
</ul>"
(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
"

View File

@ -64,6 +64,8 @@ Note that this rule alone does not actually enforce validation against the speci
}
```
<a name="require-validation"/>
# 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.

View File

@ -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<ResourceIndexedSearchParamQuantity, Long> {
public interface IResourceIndexedSearchParamQuantityNormalizedDao extends JpaRepository<ResourceIndexedSearchParamQuantityNormalized, Long> {
@Modifying
@Query("delete from ResourceIndexedSearchParamQuantityNormalized t WHERE t.myResourcePid = :resid")
void deleteByResourceId(@Param("resid") Long theResourcePid);

View File

@ -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 {

View File

@ -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<String, IRepositoryValidatingRule> myRules = ArrayListMultimap.create();
private final Multimap<String, IRepositoryValidatingRule> 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<IRepositoryValidatingRule> 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);
}

View File

@ -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 <code>Resource.meta.profile</code>
* 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.
*
* <p>
* 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 <code>OperationOutcome</code> resource returned by the server.
* </p>
*
* @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 <code>theSeverity</code> 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 <code>null</code>
* @param theSeverity The minimum severity. Must be drawn from values in {@link ResultSeverityEnum} and must not be <code>null</code>
* @param theTagSystem The system for the tag to add. Must not be <code>null</code>
* @param theTagCode The code for the tag to add. Must not be <code>null</code>
* @param theTagCode The code for the tag to add. Must not be <code>null</code>
* @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 <code>theSeverity</code> 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 <code>null</code>
* @param theSeverity The minimum severity. Must be drawn from values in {@link ResultSeverityEnum} and must not be <code>null</code>
* @param theTagSystem The system for the tag to add. Must not be <code>null</code>
* @param theTagCode The code for the tag to add. Must not be <code>null</code>
* @param theTagCode The code for the tag to add. Must not be <code>null</code>
* @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;
}
}
}

View File

@ -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();
}
}

View File

@ -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<String> next : theResource.getMeta().getProfile()) {
String nextUrl = next.getValueAsString();
String nextUrlNormalized = UrlUtil.normalizeCanonicalUrlForComparison(nextUrl);

View File

@ -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<String> matchingProfile = theResource
.getMeta()
.getProfile()

View File

@ -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<QuantityParam> 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<QuantityParam> 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<Condition> 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<List<IQueryParameterType>> 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) {

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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<ValueSet.ValueSetExpansionContainsComponent> 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 {

View File

@ -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<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
myCaptureQueriesListener.logInsertQueries();
// Original value should be in Quantity index, normalized should be in normalized table
runInTransaction(()->{
List<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<String> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
List<IBaseResource> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<String> ids = toUnqualifiedVersionlessIdValues(found);
List<IBaseResource> resources = found.getResources(0, found.size());
// Original value should be in Quantity index, normalized should be in normalized table
runInTransaction(() -> {
List<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t -> t.getParamName().equals("value-quantity")).collect(Collectors.toList());
assertEquals(0, normalizedQuantityIndexes.size());
});
List<String> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<String> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> normalizedQuantityIndexes = myResourceIndexedSearchParamQuantityNormalizedDao.findAll().stream().filter(t->t.getParamName().equals("value-quantity")).collect(Collectors.toList());
assertEquals(0, normalizedQuantityIndexes.size());
});
String searchSql;
SearchParameterMap map;
List<String> 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)));
}
}

View File

@ -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();

View File

@ -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<IIdType> 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();

View File

@ -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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(2, results.size());
});
List<IIdType> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(1, results.size());
});
List<IIdType> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(1, results.size());
});
List<IIdType> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> type = ResourceIndexedSearchParamQuantityNormalized.class;
List<?> results = myEntityManager.createQuery("SELECT i FROM " + type.getSimpleName() + " i", type).getResultList();
ourLog.info(toStringMultiline(results));
assertEquals(2, results.size());
});
List<IIdType> 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();

View File

@ -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")))

View File

@ -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();

View File

@ -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")

View File

@ -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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<BaseResourceIndexedSearchParam> 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<PathAndRef> 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<PathAndRef> 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<BaseResourceIndexedSearchParam> 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<PathAndRef> 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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<String> 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);
}
}

View File

@ -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<IRepositoryValidatingRule> 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);
}
}

View File

@ -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<IRepositoryValidatingRule> 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<IRepositoryValidatingRule> rules = newRuleBuilder()
.forResourcesOfType("Observation")
.requireValidationToDeclaredProfiles()
.withBestPracticeWarningLevel("IGNORE")
.dontReject()
.neverReject()
.tagOnSeverity(ResultSeverityEnum.ERROR, "http://foo", "validation-error")
.build();
myValInterceptor.setRules(rules);

View File

@ -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
*/

View File

@ -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()

View File

@ -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<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(uri);
String uri;
List<String> 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();

View File

@ -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")))

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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");

View File

@ -119,6 +119,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
.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() {

View File

@ -90,7 +90,7 @@ public class ModelConfig {
private IPrimitiveType<Date> 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.
*
* <p>
* The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
* </p>
* <p>
* Here is the UCUM service support level
* <ul>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
* </ul>
* </p>
*
* @since 5.3.0
*/
public NormalizedQuantitySearchLevel getNormalizedQuantitySearchLevel() {
return myNormalizedQuantitySearchLevel;
}
/**
* Toggles whether Quantity searches support value normalization when using valid UCUM coded values.
*
* <p>
* The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
* </p>
* <p>
* Here is the UCUM service support level
* <ul>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
* </ul>
* </p>
*
* @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
*
* <p>
* The default value is {@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED} which is current behavior.
* </p>
* <p>
* Here is the UCUM service support level
* <ul>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_NOT_SUPPORTED}, default, Quantity is stored in {@link ResourceIndexedSearchParamQuantity} only and it is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_STORAGE_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, but {@link ResourceIndexedSearchParamQuantity} is used by searching.</li>
* <li>{@link NormalizedQuantitySearchLevel#NORMALIZED_QUANTITY_SEARCH_SUPPORTED}, Quantity is stored in both {@link ResourceIndexedSearchParamQuantity} and {@link ResourceIndexedSearchParamQuantityNormalized}, {@link ResourceIndexedSearchParamQuantityNormalized} is used by searching.</li>
* <li>{@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.</li>
* </ul>
* </p>
*
* @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;
}
}

View File

@ -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,
}

View File

@ -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;
}

View File

@ -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() {

View File

@ -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;
}
}
}

View File

@ -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());
}
}

View File

@ -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<ResourceIndexedSearchParamQuantityNormalized> extractSearchParamQuantityNormalized(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamQuantityNormalized> extractor = createQuantityNormalizedExtractor(theResource);
return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.QUANTITY);
}
private IExtractor<ResourceIndexedSearchParamQuantity> createQuantityExtractor(IBaseResource theResource) {
private IExtractor<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> 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<ResourceIndexedSearchParamString> extractSearchParamStrings(IBaseResource theResource) {
IExtractor<ResourceIndexedSearchParamString> 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<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
private void addQuantity_QuantityNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> 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<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> 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<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
private void addQuantity_MoneyNormalized(String theResourceType, Set<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IPrimitiveType<BigDecimal>> 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<ResourceIndexedSearchParamQuantity> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IBase> low = myRangeLowValueChild.getAccessor().getFirstValueOrNull(theValue);
low.ifPresent(theIBase -> addQuantity_Quantity(theResourceType, theParams, theSearchParam, theIBase));
Optional<IBase> 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<ResourceIndexedSearchParamQuantityNormalized> theParams, RuntimeSearchParam theSearchParam, IBase theValue) {
Optional<IBase> 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<IBase> high = myRangeHighValueChild.getAccessor().getFirstValueOrNull(theValue);
high.ifPresent(theIBase -> addQuantity_QuantityNormalized(theResourceType, theParams, theSearchParam, theIBase));
}
private void addToken_Identifier(String theResourceType, Set<BaseResourceIndexedSearchParam> 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<T> {
void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath);
}
private static class CompositeExtractor<T> implements IExtractor<T> {
private final IExtractor<T> myExtractor0;
private final IExtractor<T> myExtractor1;
private CompositeExtractor(IExtractor<T> theExtractor0, IExtractor<T> theExtractor1) {
myExtractor0 = theExtractor0;
myExtractor1 = theExtractor1;
}
@Override
public void extract(SearchParamSet<T> theParams, RuntimeSearchParam theSearchParam, IBase theValue, String thePath) {
myExtractor0.extract(theParams, theSearchParam, theValue, thePath);
myExtractor1.extract(theParams, theSearchParam, theValue, thePath);
}
}
private class ResourceLinkExtractor implements IExtractor<PathAndRef> {
private PathAndRef myPathAndRef = null;

View File

@ -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<BaseResourceIndexedSearchParam> 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<Entry<String, RuntimeSearchParam>> 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()

View File

@ -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);
}

View File

@ -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<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
private final Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {

View File

@ -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<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
private final Map<String, Base> myResourceTypeToStub = Collections.synchronizedMap(new HashMap<>());
@Override
public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {

View File

@ -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<ResourceIndexedSearchParamQuantity> 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<ResourceIndexedSearchParamQuantityNormalized> quantitiesNormalized = extractSearchParamQuantityNormalized(theResource);
handleWarnings(theRequestDetails, myInterceptorBroadcaster, quantitiesNormalized);
theParams.myQuantityNormalizedParams.addAll(quantitiesNormalized);

View File

@ -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<String, RuntimeSearchParam> spMap = ctx

View File

@ -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<BaseResourceIndexedSearchParam> 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<ResourceIndexedSearchParamString> 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<ResourceIndexedSearchParamNumber> 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<ResourceIndexedSearchParamNumber> 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<BaseResourceIndexedSearchParam> coords = extractor.extractSearchParamTokens(loc);
assertEquals(1, coords.size());
@ -227,7 +224,7 @@ public class SearchParamExtractorDstu3Test {
private static class MySearchParamRegistry implements ISearchParamRegistry {
private List<RuntimeSearchParam> myAddedSearchParams = new ArrayList<>();
private final List<RuntimeSearchParam> 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);
}
}

View File

@ -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 {

View File

@ -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<T> {
public abstract class BaseValidatingInterceptor<T> extends ValidationResultEnrichingInterceptor {
/**
* Default value:<br/>

View File

@ -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<Stri
* X-HAPI-Request-Validation
*/
public static final String DEFAULT_RESPONSE_HEADER_NAME = "X-FHIR-Request-Validation";
/**
* A {@link RequestDetails#getUserData() user data} entry will be created with this
* key which contains the {@link ValidationResult} from validating the request.
*/
public static final String REQUEST_VALIDATION_RESULT = RequestValidatingInterceptor.class.getName() + "_REQUEST_VALIDATION_RESULT";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RequestValidatingInterceptor.class);
private boolean myAddValidationResultsToResponseOperationOutcome = true;
@ -82,8 +75,9 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
ValidationResult validationResult = validate(requestText, theRequestDetails);
// The JPA server will use this
theRequestDetails.getUserData().put(REQUEST_VALIDATION_RESULT, validationResult);
if (myAddValidationResultsToResponseOperationOutcome) {
addValidationResultToRequestDetails(theRequestDetails, validationResult);
}
return true;
}
@ -110,25 +104,6 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
myAddValidationResultsToResponseOperationOutcome = theAddValidationResultsToResponseOperationOutcome;
}
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
if (myAddValidationResultsToResponseOperationOutcome) {
if (theResponseObject instanceof IBaseOperationOutcome) {
IBaseOperationOutcome oo = (IBaseOperationOutcome) theResponseObject;
if (theRequestDetails != null) {
ValidationResult validationResult = (ValidationResult) theRequestDetails.getUserData().get(RequestValidatingInterceptor.REQUEST_VALIDATION_RESULT);
if (validationResult != null) {
validationResult.populateOperationOutcome(oo);
}
}
}
}
return true;
}
@Override
String provideDefaultResponseHeaderName() {
return DEFAULT_RESPONSE_HEADER_NAME;

View File

@ -0,0 +1,73 @@
package ca.uhn.fhir.rest.server.interceptor;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.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.validation.ValidationResult;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
@Interceptor
public class ValidationResultEnrichingInterceptor {
/**
* A {@link RequestDetails#getUserData() user data} entry will be created with this
* key which contains the {@link ValidationResult} from validating the request.
*/
public static final String REQUEST_VALIDATION_RESULT = ValidationResultEnrichingInterceptor.class.getName() + "_REQUEST_VALIDATION_RESULT";
@SuppressWarnings("unchecked")
@Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
public boolean addValidationResultsToOperationOutcome(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
if (theResponseObject instanceof IBaseOperationOutcome) {
IBaseOperationOutcome oo = (IBaseOperationOutcome) theResponseObject;
if (theRequestDetails != null) {
List<ValidationResult> validationResult = (List<ValidationResult>) 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<ValidationResult> results = (List<ValidationResult>) theRequestDetails.getUserData().computeIfAbsent(REQUEST_VALIDATION_RESULT, t -> new ArrayList<>(2));
results.add(theValidationResult);
}
}
}

View File

@ -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<String> jsonRoundTripErrors = new ArrayList<String>();
@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<org.eclipse.rdf4j.model.Resource> 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<String> 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<org.eclipse.rdf4j.model.Resource> 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();
}
}
}

View File

@ -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) |

View File

@ -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"]

View File

@ -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<String> 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);

View File

@ -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<ValidationMessage> messages = new ArrayList<>();

26
pom.xml
View File

@ -531,6 +531,10 @@
<id>basecade</id>
<name>Anders Havn</name>
</developer>
<developer>
<id>vedion</id>
<name>Anders Havn</name>
</developer>
<developer>
<id>zaewonyx</id>
</developer>
@ -744,7 +748,7 @@
<jaxb_core_version>2.3.0.1</jaxb_core_version>
<jaxb_runtime_version>2.3.1</jaxb_runtime_version>
<jena_version>3.16.0</jena_version>
<jersey_version>2.25.1</jersey_version>
<jersey_version>3.0.0</jersey_version>
<!-- 9.4.17 seems to have issues -->
<jetty_version>9.4.35.v20201120</jetty_version>
<jsr305_version>3.0.2</jsr305_version>
@ -758,19 +762,19 @@
<hibernate_validator_version>6.1.5.Final</hibernate_validator_version>
<httpcore_version>4.4.13</httpcore_version>
<httpclient_version>4.5.13</httpclient_version>
<jackson_version>2.11.2</jackson_version>
<jackson_version>2.12.1</jackson_version>
<jackson_databind_version>2.11.2</jackson_databind_version>
<maven_assembly_plugin_version>3.1.0</maven_assembly_plugin_version>
<maven_license_plugin_version>1.8</maven_license_plugin_version>
<resteasy_version>4.0.0.Beta3</resteasy_version>
<ph_schematron_version>5.2.0</ph_schematron_version>
<ph_commons_version>9.3.8</ph_commons_version>
<ph_schematron_version>5.6.5</ph_schematron_version>
<ph_commons_version>9.5.4</ph_commons_version>
<plexus_compiler_api_version>2.8.8</plexus_compiler_api_version>
<servicemix_saxon_version>9.8.0-15</servicemix_saxon_version>
<servicemix_xmlresolver_version>1.2_5</servicemix_xmlresolver_version>
<slf4j_version>1.7.30</slf4j_version>
<log4j_to_slf4j_version>2.11.1</log4j_to_slf4j_version>
<spring_version>5.3.2</spring_version>
<spring_version>5.3.3</spring_version>
<!-- FYI: Spring Data JPA 2.1.9 causes test failures due to unexpected cascading deletes -->
<spring_data_version>2.4.2</spring_data_version>
<spring_batch_version>4.2.3.RELEASE</spring_batch_version>
@ -888,6 +892,12 @@
<groupId>com.helger</groupId>
<artifactId>ph-schematron</artifactId>
<version>${ph_schematron_version}</version>
<exclusions>
<exclusion>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.helger</groupId>
@ -927,7 +937,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.3</version>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@ -1351,7 +1361,7 @@
<dependency>
<groupId>org.fusesource.jansi</groupId>
<artifactId>jansi</artifactId>
<version>1.18</version>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
@ -1956,7 +1966,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.5</version>
<version>0.8.6</version>
<configuration>
<excludes>
<exclude>ca/uhn/fhir/model/dstu2/**/*.class</exclude>