Improve versioned URL support for ValueSet expansion (#2994)

* Add test

* Test fixes

* Work on expansion errors

* Fixes

* Test fixes

* Test fixes

* Test fixes

* Test fixes

* Test fixes

* Test fixes

* Test fixes

* Work on fix

* Test fixes

* Test fixes

* Test fix

* Cleanup

* Test fixes

* Remove accidental commit

* Test fixes

* Version bump

* Add changelog

* Fixes

* Test fix

* Fix conflicts
This commit is contained in:
James Agnew 2021-10-03 19:47:23 -04:00 committed by GitHub
parent 95e853bdf7
commit eca725afa4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2126 additions and 1001 deletions

View File

@ -45,6 +45,16 @@ import java.util.Properties;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/**
* This class returns the vocabulary that is shipped with the base FHIR
* specification.
*
* Note that this class is version aware. For example, a request for
* <code>http://foo-codesystem|123</code> will only return a value if
* the built in resource if the version matches. Unversioned URLs
* should generally be used, and will return whatever version is
* present.
*/
public class DefaultProfileValidationSupport implements IValidationSupport {
private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/";
@ -179,18 +189,27 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
// System can take the form "http://url|version"
String system = theSystem;
if (system.contains("|")) {
String version = system.substring(system.indexOf('|') + 1);
if (version.matches("^[0-9.]+$")) {
system = system.substring(0, system.indexOf('|'));
String version = null;
int pipeIdx = system.indexOf('|');
if (pipeIdx > 0) {
version = system.substring(pipeIdx + 1);
system = system.substring(0, pipeIdx);
}
IBaseResource candidate;
if (codeSystem) {
candidate = codeSystems.get(system);
} else {
candidate = valueSets.get(system);
}
if (candidate != null && isNotBlank(version) && !system.startsWith("http://hl7.org") && !system.startsWith("http://terminology.hl7.org")) {
if (!StringUtils.equals(version, myCtx.newTerser().getSinglePrimitiveValueOrNull(candidate, "version"))) {
candidate = null;
}
}
if (codeSystem) {
return codeSystems.get(system);
} else {
return valueSets.get(system);
}
return candidate;
}
}
@ -205,8 +224,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
}
Map<String, IBaseResource> structureDefinitionMap = provideStructureDefinitionMap();
IBaseResource retVal = structureDefinitionMap.get(url);
return retVal;
return structureDefinitionMap.get(url);
}
@Override

View File

@ -586,8 +586,8 @@ public interface IValidationSupport {
private final IBaseResource myValueSet;
private final String myError;
public ValueSetExpansionOutcome(IBaseResource theValueSet, String theError) {
myValueSet = theValueSet;
public ValueSetExpansionOutcome(String theError) {
myValueSet = null;
myError = theError;
}

View File

@ -2,8 +2,12 @@
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
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetExpandedUsingPreExpansion=ValueSet was expanded using an expansion that was pre-calculated at {0}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetExpandedUsingInMemoryExpansion=ValueSet with URL "{0}" was expanded using an in-memory expansion
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.validationPerformedAgainstPreExpansion=Code validation occurred using a ValueSet expansion that was pre-calculated at {0}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetNotFoundInTerminologyDatabase=ValueSet can not be found in terminology database: {0}
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetPreExpansionInvalidated=ValueSet with URL "{0}" precaluclated expansion with {1} concept(s) has been invalidated
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.valueSetCantInvalidateNotYetPrecalculated=ValueSet with URL "{0}" already has status: {1}
# Core Library Messages

View File

@ -0,0 +1,8 @@
---
type: add
issue: 2994
title: "When performing ValueSet expansions and ValueSet-based code validations, the HAPI FHIR terminology service will allow
ValueSets to be expanded if they contain an enumeration of codes, even if the corresponding CodeSystem resource can not
be found. Response messages have also been improved to give better insight into whether a precalculated expansion
was used. In addition, a new operation called `$invalidate-expansion` has been added that allows for manual invalidation
of previously calculated ValueSet expansions."

View File

@ -85,6 +85,7 @@ import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.reindex.ReindexJobSubmitterImpl;
import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory;
import ca.uhn.fhir.jpa.sched.HapiSchedulerServiceImpl;
@ -318,6 +319,12 @@ public abstract class BaseConfig {
return new SubscriptionTriggeringProvider();
}
@Bean
@Lazy
public ValueSetOperationProvider valueSetOperationProvider() {
return new ValueSetOperationProvider();
}
@Bean
public TransactionProcessor transactionProcessor() {
return new TransactionProcessor();

View File

@ -88,7 +88,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
private Class<? extends IBaseResource> myValueSetType;
private Class<? extends IBaseResource> myQuestionnaireType;
private Class<? extends IBaseResource> myImplementationGuideType;
private Cache<String, IBaseResource> myLoadCache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).build();
private Cache<String, IBaseResource> myLoadCache = Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(1, TimeUnit.MINUTES).build();
/**
* Constructor

View File

@ -27,6 +27,8 @@ public interface ITermValueSetConceptView {
String getConceptDisplay();
String getConceptSystemVersion();
Long getSourceConceptPid();
String getSourceConceptDirectParentPids();

View File

@ -28,9 +28,27 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import org.hibernate.annotations.ColumnDefault;
import javax.annotation.Nonnull;
import javax.persistence.*;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.left;
@ -41,13 +59,11 @@ import static org.apache.commons.lang3.StringUtils.length;
})
@Entity()
public class TermValueSet implements Serializable {
private static final long serialVersionUID = 1L;
public static final int MAX_EXPANSION_STATUS_LENGTH = 50;
public static final int MAX_NAME_LENGTH = 200;
public static final int MAX_URL_LENGTH = 200;
public static final int MAX_VER_LENGTH = 200;
private static final long serialVersionUID = 1L;
@Id()
@SequenceGenerator(name = "SEQ_VALUESET_PID", sequenceName = "SEQ_VALUESET_PID")
@GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_VALUESET_PID")
@ -85,6 +101,10 @@ public class TermValueSet implements Serializable {
@Column(name = "EXPANSION_STATUS", nullable = false, length = MAX_EXPANSION_STATUS_LENGTH)
private TermValueSetPreExpansionStatusEnum myExpansionStatus;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "EXPANDED_AT", nullable = true)
private Date myExpansionTimestamp;
@Transient
private transient Integer myHashCode;
@ -95,6 +115,14 @@ public class TermValueSet implements Serializable {
myTotalConceptDesignations = 0L;
}
public Date getExpansionTimestamp() {
return myExpansionTimestamp;
}
public void setExpansionTimestamp(Date theExpansionTimestamp) {
myExpansionTimestamp = theExpansionTimestamp;
}
public Long getId() {
return myId;
}

View File

@ -49,6 +49,7 @@ import java.sql.SQLException;
" vsc.SYSTEM_URL AS CONCEPT_SYSTEM_URL, " +
" vsc.CODEVAL AS CONCEPT_CODEVAL, " +
" vsc.DISPLAY AS CONCEPT_DISPLAY, " +
" vsc.SYSTEM_VER AS SYSTEM_VER, " +
" vsc.SOURCE_PID AS SOURCE_PID, " +
" vsc.SOURCE_DIRECT_PARENT_PIDS AS SOURCE_DIRECT_PARENT_PIDS, " +
" vscd.PID AS DESIGNATION_PID, " +
@ -85,6 +86,9 @@ public class TermValueSetConceptView implements Serializable, ITermValueSetConce
@Column(name = "CONCEPT_DISPLAY", length = TermConcept.MAX_DESC_LENGTH)
private String myConceptDisplay;
@Column(name="SYSTEM_VER", length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
private String myConceptSystemVersion;
@Column(name = "DESIGNATION_PID")
private Long myDesignationPid;
@ -176,4 +180,10 @@ public class TermValueSetConceptView implements Serializable, ITermValueSetConce
public String getDesignationVal() {
return myDesignationVal;
}
@Override
public String getConceptSystemVersion() {
return myConceptSystemVersion;
}
}

View File

@ -49,6 +49,7 @@ import java.sql.SQLException;
" vsc.SYSTEM_URL AS CONCEPT_SYSTEM_URL, " +
" vsc.CODEVAL AS CONCEPT_CODEVAL, " +
" vsc.DISPLAY AS CONCEPT_DISPLAY, " +
" vsc.SYSTEM_VER AS SYSTEM_VER, " +
" vsc.SOURCE_PID AS SOURCE_PID, " +
" vsc.SOURCE_DIRECT_PARENT_PIDS AS SOURCE_DIRECT_PARENT_PIDS, " +
" vscd.PID AS DESIGNATION_PID, " +
@ -85,6 +86,9 @@ public class TermValueSetConceptViewOracle implements Serializable, ITermValueSe
@Column(name = "CONCEPT_DISPLAY", length = TermConcept.MAX_DESC_LENGTH)
private String myConceptDisplay;
@Column(name="SYSTEM_VER", length = TermCodeSystemVersion.MAX_VERSION_LENGTH)
private String myConceptSystemVersion;
@Column(name = "DESIGNATION_PID")
private Long myDesignationPid;
@ -130,6 +134,11 @@ public class TermValueSetConceptViewOracle implements Serializable, ITermValueSe
return myConceptDisplay;
}
@Override
public String getConceptSystemVersion() {
return myConceptSystemVersion;
}
@Override
public Long getSourceConceptPid() {
return mySourceConceptPid;

View File

@ -128,6 +128,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
version.onTable("HFJ_RESOURCE")
.dropIndex("20210908.1", "IDX_RES_LANG");
version.onTable("TRM_VALUESET")
.addColumn("20210915.1", "EXPANDED_AT")
.nullable()
.type(ColumnTypeEnum.DATE_TIMESTAMP);
}
private void init540() {

View File

@ -0,0 +1,229 @@
package ca.uhn.fhir.jpa.provider;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* 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.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ValueSetOperationProvider extends BaseJpaProvider {
private static final Logger ourLog = LoggerFactory.getLogger(ValueSetOperationProvider.class);
@Autowired
private DaoConfig myDaoConfig;
@Autowired
private DaoRegistry myDaoRegistry;
@Autowired
private ITermReadSvc myTermReadSvc;
@Operation(name = JpaConstants.OPERATION_EXPAND, idempotent = true, typeName = "ValueSet")
public IBaseResource expand(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IIdType theId,
@OperationParam(name = "valueSet", min = 0, max = 1) IBaseResource theValueSet,
@OperationParam(name = "url", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theValueSetVersion,
@OperationParam(name = "filter", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theFilter,
@OperationParam(name = "offset", min = 0, max = 1, typeName = "integer") IPrimitiveType<Integer> theOffset,
@OperationParam(name = "count", min = 0, max = 1, typeName = "integer") IPrimitiveType<Integer> theCount,
@OperationParam(name = JpaConstants.OPERATION_EXPAND_PARAM_INCLUDE_HIERARCHY, min = 0, max = 1, typeName = "boolean") IPrimitiveType<Boolean> theIncludeHierarchy,
RequestDetails theRequestDetails) {
boolean haveId = theId != null && theId.hasIdPart();
boolean haveIdentifier = theUrl != null && isNotBlank(theUrl.getValue());
boolean haveValueSet = theValueSet != null && !theValueSet.isEmpty();
boolean haveValueSetVersion = theValueSetVersion != null && !theValueSetVersion.isEmpty();
if (!haveId && !haveIdentifier && !haveValueSet) {
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires a url or a valueSet as a part of the request.");
}
if (moreThanOneTrue(haveId, haveIdentifier, haveValueSet)) {
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have a url specified, or have a ValueSet specified. Can not combine these options.");
}
ValueSetExpansionOptions options = createValueSetExpansionOptions(myDaoConfig, theOffset, theCount, theIncludeHierarchy, theFilter);
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<IBaseResource, ICompositeType, ICompositeType> dao = getDao();
if (haveId) {
return dao.expand(theId, options, theRequestDetails);
} else if (haveIdentifier) {
if (haveValueSetVersion) {
return dao.expandByIdentifier(theUrl.getValue() + "|" + theValueSetVersion.getValue(), options);
} else {
return dao.expandByIdentifier(theUrl.getValue(), options);
}
} else {
return dao.expand(theValueSet, options);
}
} finally {
endRequest(theServletRequest);
}
}
@SuppressWarnings("unchecked")
private IFhirResourceDaoValueSet<IBaseResource, ICompositeType, ICompositeType> getDao() {
return (IFhirResourceDaoValueSet<IBaseResource, ICompositeType, ICompositeType>) myDaoRegistry.getResourceDao("ValueSet");
}
@SuppressWarnings("unchecked")
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, typeName = "ValueSet", returnParameters = {
@OperationParam(name = "result", typeName = "boolean", min = 1),
@OperationParam(name = "message", typeName = "string"),
@OperationParam(name = "display", typeName = "string")
})
public IBaseParameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IIdType theId,
@OperationParam(name = "url", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theValueSetUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theValueSetVersion,
@OperationParam(name = "code", min = 0, max = 1) IPrimitiveType<String> theCode,
@OperationParam(name = "system", min = 0, max = 1, typeName = "uri") IPrimitiveType<String> theSystem,
@OperationParam(name = "systemVersion", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theSystemVersion,
@OperationParam(name = "display", min = 0, max = 1, typeName = "string") IPrimitiveType<String> theDisplay,
@OperationParam(name = "coding", min = 0, max = 1, typeName = "Coding") ICompositeType theCoding,
@OperationParam(name = "codeableConcept", min = 0, max = 1, typeName = "CodeableConcept") ICompositeType theCodeableConcept,
RequestDetails theRequestDetails
) {
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<IBaseResource, ICompositeType, ICompositeType> dao = getDao();
IPrimitiveType<String> valueSetIdentifier;
if (theValueSetVersion != null) {
valueSetIdentifier = (IPrimitiveType<String>) getContext().getElementDefinition("uri").newInstance();
valueSetIdentifier.setValue(theValueSetUrl.getValue() + "|" + theValueSetVersion);
} else {
valueSetIdentifier = theValueSetUrl;
}
IPrimitiveType<String> codeSystemIdentifier;
if (theSystemVersion != null) {
codeSystemIdentifier = (IPrimitiveType<String>) getContext().getElementDefinition("uri").newInstance();
codeSystemIdentifier.setValue(theSystem.getValue() + "|" + theSystemVersion);
} else {
codeSystemIdentifier = theSystem;
}
IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
@Operation(name = ProviderConstants.OPERATION_INVALIDATE_EXPANSION, idempotent = false, typeName = "ValueSet", returnParameters = {
@OperationParam(name = "message", typeName = "string", min = 1, max = 1)
})
public IBaseParameters invalidateValueSetExpansion(
@IdParam IIdType theValueSetId,
RequestDetails theRequestDetails,
HttpServletRequest theServletRequest) {
startRequest(theServletRequest);
try {
String outcome = myTermReadSvc.invalidatePreCalculatedExpansion(theValueSetId, theRequestDetails);
IBaseParameters retVal = ParametersUtil.newInstance(getContext());
ParametersUtil.addParameterToParametersString(getContext(), retVal, "message", outcome);
return retVal;
} finally {
endRequest(theServletRequest);
}
}
public static ValueSetExpansionOptions createValueSetExpansionOptions(DaoConfig theDaoConfig, IPrimitiveType<Integer> theOffset, IPrimitiveType<Integer> theCount, IPrimitiveType<Boolean> theIncludeHierarchy, IPrimitiveType<String> theFilter) {
int offset = theDaoConfig.getPreExpandValueSetsDefaultOffset();
if (theOffset != null && theOffset.hasValue()) {
if (theOffset.getValue() >= 0) {
offset = theOffset.getValue();
} else {
throw new InvalidRequestException("offset parameter for $expand operation must be >= 0 when specified. offset: " + theOffset.getValue());
}
}
int count = theDaoConfig.getPreExpandValueSetsDefaultCount();
if (theCount != null && theCount.hasValue()) {
if (theCount.getValue() >= 0) {
count = theCount.getValue();
} else {
throw new InvalidRequestException("count parameter for $expand operation must be >= 0 when specified. count: " + theCount.getValue());
}
}
int countMax = theDaoConfig.getPreExpandValueSetsMaxCount();
if (count > countMax) {
ourLog.warn("count parameter for $expand operation of {} exceeds maximum value of {}; using maximum value.", count, countMax);
count = countMax;
}
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(offset, count);
if (theIncludeHierarchy != null && Boolean.TRUE.equals(theIncludeHierarchy.getValue())) {
options.setIncludeHierarchy(true);
}
if (theFilter != null) {
options.setFilter(theFilter.getValue());
}
return options;
}
private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false;
for (boolean next : theBooleans) {
if (next) {
if (haveOne) {
return true;
} else {
haveOne = true;
}
}
}
return false;
}
}

View File

@ -20,152 +20,8 @@ package ca.uhn.fhir.jpa.provider.dstu3;
* #L%
*/
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.dstu3.model.BooleanType;
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.IdType;
import org.hl7.fhir.dstu3.model.IntegerType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.servlet.http.HttpServletRequest;
import static ca.uhn.fhir.jpa.provider.r4.BaseJpaResourceProviderValueSetR4.createValueSetExpansionOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDstu3<ValueSet> {
@Operation(name = JpaConstants.OPERATION_EXPAND, idempotent = true)
public ValueSet expand(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "valueSet", min = 0, max = 1) ValueSet theValueSet,
// Note: url is correct and identifier is not, but identifier was only added as
// of 3.1.0 so we'll leave url for now. See: https://groups.google.com/d/msgid/hapi-fhir/CAN2Cfy8kW%2BAOkgC6VjPsU3gRCpExCNZBmJdi-k5R_TWeyWH4tA%40mail.gmail.com?utm_medium=email&utm_source=footer
@OperationParam(name = "url", min = 0, max = 1) UriType theUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1) StringType theValueSetVersion,
@OperationParam(name = "identifier", min = 0, max = 1) UriType theIdentifier,
@OperationParam(name = "filter", min = 0, max = 1) StringType theFilter,
@OperationParam(name = "offset", min = 0, max = 1) IntegerType theOffset,
@OperationParam(name = "count", min = 0, max = 1) IntegerType theCount,
@OperationParam(name = JpaConstants.OPERATION_EXPAND_PARAM_INCLUDE_HIERARCHY, min = 0, max = 1, typeName = "boolean") IPrimitiveType<Boolean> theIncludeHierarchy,
RequestDetails theRequestDetails) {
boolean haveId = theId != null && theId.hasIdPart();
UriType url = theIdentifier;
if (theUrl != null && isNotBlank(theUrl.getValue())) {
url = theUrl;
}
boolean haveIdentifier = url != null && isNotBlank(url.getValue());
boolean haveValueSet = theValueSet != null && !theValueSet.isEmpty();
boolean haveValueSetVersion = theValueSetVersion != null && !theValueSetVersion.isEmpty();
if (!haveId && !haveIdentifier && !haveValueSet) {
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires an identifier or a valueSet as a part of the request.");
}
if (moreThanOneTrue(haveId, haveIdentifier, haveValueSet)) {
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.");
}
ValueSetExpansionOptions options = createValueSetExpansionOptions(myDaoConfig, theOffset, theCount, theIncludeHierarchy, theFilter);
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
if (haveId) {
return dao.expand(theId, options, theRequestDetails);
} else if (haveIdentifier) {
if (haveValueSetVersion) {
return dao.expandByIdentifier(url.getValue() + "|" + theValueSetVersion.getValue(), options);
} else {
return dao.expandByIdentifier(url.getValue(), options);
}
} else {
return dao.expand(theValueSet, options);
}
} finally {
endRequest(theServletRequest);
}
}
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "identifier", min = 0, max = 1) UriType theValueSetIdentifier,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1) StringType theValueSetVersion,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "systemVersion", min = 0, max = 1) StringType theSystemVersion,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
@OperationParam(name = "codeableConcept", min = 0, max = 1) CodeableConcept theCodeableConcept,
RequestDetails theRequestDetails
) {
UriType url = theValueSetIdentifier;
if (theValueSetUrl != null && isNotBlank(theValueSetUrl.getValue())) {
url = theValueSetUrl;
}
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
UriType valueSetIdentifier;
if (theValueSetVersion != null) {
valueSetIdentifier = new UriType(url.getValue() + "|" + theValueSetVersion);
} else {
valueSetIdentifier = url;
}
UriType codeSystemIdentifier;
if (theSystemVersion != null) {
codeSystemIdentifier = new UriType(theSystem.getValue() + "|" + theSystemVersion);
} else {
codeSystemIdentifier = theSystem;
}
IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false;
for (boolean next : theBooleans) {
if (next) {
if (haveOne) {
return true;
} else {
haveOne = true;
}
}
}
return false;
}
}

View File

@ -20,175 +20,8 @@ package ca.uhn.fhir.jpa.provider.r4;
* #L%
*/
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import javax.servlet.http.HttpServletRequest;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<ValueSet> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseJpaResourceProviderValueSetR4.class);
@Operation(name = JpaConstants.OPERATION_EXPAND, idempotent = true)
public ValueSet expand(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "valueSet", min = 0, max = 1) ValueSet theValueSet,
@OperationParam(name = "url", min = 0, max = 1) UriType theUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1) StringType theValueSetVersion,
@OperationParam(name = "filter", min = 0, max = 1) StringType theFilter,
@OperationParam(name = "offset", min = 0, max = 1) IntegerType theOffset,
@OperationParam(name = "count", min = 0, max = 1) IntegerType theCount,
@OperationParam(name = JpaConstants.OPERATION_EXPAND_PARAM_INCLUDE_HIERARCHY, min = 0, max = 1, typeName = "boolean") IPrimitiveType<Boolean> theIncludeHierarchy,
RequestDetails theRequestDetails) {
boolean haveId = theId != null && theId.hasIdPart();
boolean haveIdentifier = theUrl != null && isNotBlank(theUrl.getValue());
boolean haveValueSet = theValueSet != null && !theValueSet.isEmpty();
boolean haveValueSetVersion = theValueSetVersion != null && !theValueSetVersion.isEmpty();
if (!haveId && !haveIdentifier && !haveValueSet) {
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires a url or a valueSet as a part of the request.");
}
if (moreThanOneTrue(haveId, haveIdentifier, haveValueSet)) {
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have a url specified, or have a ValueSet specified. Can not combine these options.");
}
ValueSetExpansionOptions options = createValueSetExpansionOptions(myDaoConfig, theOffset, theCount, theIncludeHierarchy, theFilter);
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
if (haveId) {
return dao.expand(theId, options, theRequestDetails);
} else if (haveIdentifier) {
if (haveValueSetVersion) {
return dao.expandByIdentifier(theUrl.getValue() + "|" + theValueSetVersion.getValue(), options);
} else {
return dao.expandByIdentifier(theUrl.getValue(), options);
}
} else {
return dao.expand(theValueSet, options);
}
} finally {
endRequest(theServletRequest);
}
}
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1) StringType theValueSetVersion,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "systemVersion", min = 0, max = 1) StringType theSystemVersion,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
@OperationParam(name = "codeableConcept", min = 0, max = 1) CodeableConcept theCodeableConcept,
RequestDetails theRequestDetails
) {
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
UriType valueSetIdentifier;
if (theValueSetVersion != null) {
valueSetIdentifier = new UriType(theValueSetUrl.getValue() + "|" + theValueSetVersion);
} else {
valueSetIdentifier = theValueSetUrl;
}
UriType codeSystemIdentifier;
if (theSystemVersion != null) {
codeSystemIdentifier = new UriType(theSystem.getValue() + "|" + theSystemVersion);
} else {
codeSystemIdentifier = theSystem;
}
IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
public static ValueSetExpansionOptions createValueSetExpansionOptions(DaoConfig theDaoConfig, IPrimitiveType<Integer> theOffset, IPrimitiveType<Integer> theCount, IPrimitiveType<Boolean> theIncludeHierarchy, IPrimitiveType<String> theFilter) {
int offset = theDaoConfig.getPreExpandValueSetsDefaultOffset();
if (theOffset != null && theOffset.hasValue()) {
if (theOffset.getValue() >= 0) {
offset = theOffset.getValue();
} else {
throw new InvalidRequestException("offset parameter for $expand operation must be >= 0 when specified. offset: " + theOffset.getValue());
}
}
int count = theDaoConfig.getPreExpandValueSetsDefaultCount();
if (theCount != null && theCount.hasValue()) {
if (theCount.getValue() >= 0) {
count = theCount.getValue();
} else {
throw new InvalidRequestException("count parameter for $expand operation must be >= 0 when specified. count: " + theCount.getValue());
}
}
int countMax = theDaoConfig.getPreExpandValueSetsMaxCount();
if (count > countMax) {
ourLog.warn("count parameter for $expand operation of {} exceeds maximum value of {}; using maximum value.", count, countMax);
count = countMax;
}
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(offset, count);
if (theIncludeHierarchy != null && Boolean.TRUE.equals(theIncludeHierarchy.getValue())) {
options.setIncludeHierarchy(true);
}
if (theFilter != null) {
options.setFilter(theFilter.getValue());
}
return options;
}
private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false;
for (boolean next : theBooleans) {
if (next) {
if (haveOne) {
return true;
} else {
haveOne = true;
}
}
}
return false;
}
}

View File

@ -20,138 +20,8 @@ package ca.uhn.fhir.jpa.provider.r5;
* #L%
*/
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProviderValueSetDstu2;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import javax.servlet.http.HttpServletRequest;
import static ca.uhn.fhir.jpa.provider.r4.BaseJpaResourceProviderValueSetR4.createValueSetExpansionOptions;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class BaseJpaResourceProviderValueSetR5 extends JpaResourceProviderR5<ValueSet> {
@Operation(name = JpaConstants.OPERATION_EXPAND, idempotent = true)
public ValueSet expand(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "valueSet", min = 0, max = 1) ValueSet theValueSet,
@OperationParam(name = "url", min = 0, max = 1) UriType theUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1) StringType theValueSetVersion,
@OperationParam(name = "filter", min = 0, max = 1) StringType theFilter,
@OperationParam(name = "offset", min = 0, max = 1) IntegerType theOffset,
@OperationParam(name = "count", min = 0, max = 1) IntegerType theCount,
@OperationParam(name = JpaConstants.OPERATION_EXPAND_PARAM_INCLUDE_HIERARCHY, min = 0, max = 1, typeName = "boolean") IPrimitiveType<Boolean> theIncludeHierarchy,
RequestDetails theRequestDetails) {
boolean haveId = theId != null && theId.hasIdPart();
boolean haveIdentifier = theUrl != null && isNotBlank(theUrl.getValue());
boolean haveValueSet = theValueSet != null && !theValueSet.isEmpty();
boolean haveValueSetVersion = theValueSetVersion != null && !theValueSetVersion.isEmpty();
if (!haveId && !haveIdentifier && !haveValueSet) {
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires a url or a valueSet as a part of the request.");
}
if (moreThanOneTrue(haveId, haveIdentifier, haveValueSet)) {
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have a url specified, or have a ValueSet specified. Can not combine these options.");
}
ValueSetExpansionOptions options = createValueSetExpansionOptions(myDaoConfig, theOffset, theCount, theIncludeHierarchy, theFilter);
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
if (haveId) {
return dao.expand(theId, options, theRequestDetails);
} else if (haveIdentifier) {
if (haveValueSetVersion) {
return dao.expandByIdentifier(theUrl.getValue() + "|" + theValueSetVersion.getValue(), options);
} else {
return dao.expandByIdentifier(theUrl.getValue(), options);
}
} else {
return dao.expand(theValueSet, options);
}
} finally {
endRequest(theServletRequest);
}
}
@Operation(name = JpaConstants.OPERATION_VALIDATE_CODE, idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@OperationParam(name = "message", type = StringType.class),
@OperationParam(name = "display", type = StringType.class)
})
public Parameters validateCode(
HttpServletRequest theServletRequest,
@IdParam(optional = true) IdType theId,
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
@OperationParam(name = "valueSetVersion", min = 0, max = 1) StringType theValueSetVersion,
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
@OperationParam(name = "systemVersion", min = 0, max = 1) StringType theSystemVersion,
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
@OperationParam(name = "codeableConcept", min = 0, max = 1) CodeableConcept theCodeableConcept,
RequestDetails theRequestDetails
) {
startRequest(theServletRequest);
try {
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
UriType valueSetIdentifier;
if (theValueSetVersion != null) {
valueSetIdentifier = new UriType(theValueSetUrl.getValue() + "|" + theValueSetVersion);
} else {
valueSetIdentifier = theValueSetUrl;
}
UriType codeSystemIdentifier;
if (theSystemVersion != null) {
codeSystemIdentifier = new UriType(theSystem.getValue() + "|" + theSystemVersion);
} else {
codeSystemIdentifier = theSystem;
}
IValidationSupport.CodeValidationResult result = dao.validateCode(valueSetIdentifier, theId, theCode, codeSystemIdentifier, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
return (Parameters) BaseJpaResourceProviderValueSetDstu2.toValidateCodeResult(getContext(), result);
} finally {
endRequest(theServletRequest);
}
}
private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false;
for (boolean next : theBooleans) {
if (next) {
if (haveOne) {
return true;
} else {
haveOne = true;
}
}
}
return false;
}
// nothing
}

View File

@ -65,15 +65,14 @@ import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.CoverageIgnore;
@ -103,6 +102,10 @@ import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.context.ConversionContext40_50;
import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50;
import org.hl7.fhir.convertors.conv40_50.resources40_50.ValueSet40_50;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
@ -117,6 +120,7 @@ import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.DomainResource;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.InstantType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet;
@ -170,9 +174,11 @@ import java.util.StringTokenizer;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank;
@ -256,6 +262,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
private boolean addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, String theValueSetIncludeVersion) {
String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri();
String codeSystemVersion = theConcept.getCodeSystemVersion().getCodeSystemVersionId();
String code = theConcept.getCode();
String display = theConcept.getDisplay();
Long sourceConceptPid = theConcept.getId();
@ -268,9 +275,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Collection<TermConceptDesignation> designations = theConcept.getDesignations();
if (StringUtils.isNotEmpty(theValueSetIncludeVersion)) {
return addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, designations, theAdd, codeSystem + "|" + theValueSetIncludeVersion, code, display, sourceConceptPid, directParentPids);
return addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, designations, theAdd, codeSystem + "|" + theValueSetIncludeVersion, code, display, sourceConceptPid, directParentPids, codeSystemVersion);
} else {
return addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, designations, theAdd, codeSystem, code, display, sourceConceptPid, directParentPids);
return addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, designations, theAdd, codeSystem, code, display, sourceConceptPid, directParentPids, codeSystemVersion);
}
}
@ -278,7 +285,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
if (StringUtils.isNotEmpty(theCodeSystemVersion)) {
if (isNoneBlank(theCodeSystem, theCode)) {
if (theAdd && theAddedCodes.add(theCodeSystem + "|" + theCode)) {
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem + "|" + theCodeSystemVersion, theCode, theDisplay, null, theSourceConceptPid, theSourceConceptDirectParentPids);
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem + "|" + theCodeSystemVersion, theCode, theDisplay, null, theSourceConceptPid, theSourceConceptDirectParentPids, theCodeSystemVersion);
}
if (!theAdd && theAddedCodes.remove(theCodeSystem + "|" + theCode)) {
@ -287,7 +294,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
} else {
if (theAdd && theAddedCodes.add(theCodeSystem + "|" + theCode)) {
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem, theCode, theDisplay, null, theSourceConceptPid, theSourceConceptDirectParentPids);
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem, theCode, theDisplay, null, theSourceConceptPid, theSourceConceptDirectParentPids, theCodeSystemVersion);
}
if (!theAdd && theAddedCodes.remove(theCodeSystem + "|" + theCode)) {
@ -296,10 +303,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
private boolean addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, Collection<TermConceptDesignation> theDesignations, boolean theAdd, String theCodeSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids) {
private boolean addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, Collection<TermConceptDesignation> theDesignations, boolean theAdd, String theCodeSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theSystemVersion) {
if (isNoneBlank(theCodeSystem, theCode)) {
if (theAdd && theAddedCodes.add(theCodeSystem + "|" + theCode)) {
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem, theCode, theDisplay, theDesignations, theSourceConceptPid, theSourceConceptDirectParentPids);
theValueSetCodeAccumulator.includeConceptWithDesignations(theCodeSystem, theCode, theDisplay, theDesignations, theSourceConceptPid, theSourceConceptDirectParentPids, theSystemVersion);
return true;
}
@ -312,17 +319,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return false;
}
private void addConceptsToList(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, String theSystem, List<CodeSystem.ConceptDefinitionComponent> theConcept, boolean theAdd, @Nonnull ExpansionFilter theExpansionFilter) {
for (CodeSystem.ConceptDefinitionComponent next : theConcept) {
if (isNoneBlank(theSystem, next.getCode())) {
if (!theExpansionFilter.hasCode() || theExpansionFilter.getCode().equals(next.getCode())) {
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, theSystem, next.getCode(), next.getDisplay());
}
}
addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, theSystem, next.getConcept(), theAdd, theExpansionFilter);
}
}
private boolean addToSet(Set<TermConcept> theSetToPopulate, TermConcept theConcept) {
boolean retVal = theSetToPopulate.add(theConcept);
if (retVal) {
@ -351,13 +347,17 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get();
ourLog.info("Deleting existing TermValueSet[{}] and its children...", existingTermValueSet.getId());
myValueSetConceptDesignationDao.deleteByTermValueSetId(existingTermValueSet.getId());
myValueSetConceptDao.deleteByTermValueSetId(existingTermValueSet.getId());
deletePreCalculatedValueSetContents(existingTermValueSet);
myTermValueSetDao.deleteById(existingTermValueSet.getId());
ourLog.info("Done deleting existing TermValueSet[{}] and its children.", existingTermValueSet.getId());
}
}
private void deletePreCalculatedValueSetContents(TermValueSet theValueSet) {
myValueSetConceptDesignationDao.deleteByTermValueSetId(theValueSet.getId());
myValueSetConceptDao.deleteByTermValueSetId(theValueSet.getId());
}
@Override
@Transactional
public void deleteValueSetAndChildren(ResourceTable theResourceTable) {
@ -462,6 +462,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
*/
if (!optionalTermValueSet.isPresent()) {
ourLog.debug("ValueSet is not present in terminology tables. Will perform in-memory expansion without parameters. {}", getValueSetInfo(theValueSetToExpand));
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "valueSetExpandedUsingInMemoryExpansion", getValueSetInfo(theValueSetToExpand));
theAccumulator.addMessage(msg);
expandValueSet(theExpansionOptions, theValueSetToExpand, theAccumulator, theFilter);
return;
}
@ -480,11 +482,22 @@ 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");
String expansionTimestamp = toHumanReadableExpansionTimestamp(termValueSet);
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "valueSetExpandedUsingPreExpansion", expansionTimestamp);
theAccumulator.addMessage(msg);
expandConcepts(theAccumulator, termValueSet, theFilter, theAdd, isOracleDialect());
}
@Nonnull
private String toHumanReadableExpansionTimestamp(TermValueSet termValueSet) {
String expansionTimestamp = "(unknown)";
if (termValueSet.getExpansionTimestamp() != null) {
String timeElapsed = StopWatch.formatMillis(System.currentTimeMillis() - termValueSet.getExpansionTimestamp().getTime());
expansionTimestamp = new InstantType(termValueSet.getExpansionTimestamp()).getValueAsString() + " (" + timeElapsed + " ago)";
}
return expansionTimestamp;
}
private boolean isOracleDialect() {
return myHibernatePropertiesProvider.getDialect() instanceof org.hibernate.dialect.Oracle12cDialect;
}
@ -543,6 +556,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
String system = conceptView.getConceptSystemUrl();
String code = conceptView.getConceptCode();
String display = conceptView.getConceptDisplay();
String systemVersion = conceptView.getConceptSystemVersion();
//-- this is quick solution, may need to revisit
if (!applyFilter(display, filterDisplayValue))
@ -550,7 +564,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long conceptPid = conceptView.getConceptPid();
if (!pidToConcept.containsKey(conceptPid)) {
FhirVersionIndependentConcept concept = new FhirVersionIndependentConcept(system, code, display);
FhirVersionIndependentConcept concept = new FhirVersionIndependentConcept(system, code, display, systemVersion);
pidToConcept.put(conceptPid, concept);
}
@ -585,6 +599,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
String system = concept.getSystem();
String code = concept.getCode();
String display = concept.getDisplay();
String systemVersion = concept.getSystemVersion();
if (theAdd) {
if (theAccumulator.getCapacityRemaining() != null) {
@ -595,7 +610,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long sourceConceptPid = pidToSourcePid.get(nextPid);
String sourceConceptDirectParentPids = pidToSourceDirectParentPids.get(nextPid);
theAccumulator.includeConceptWithDesignations(system, code, display, designations, sourceConceptPid, sourceConceptDirectParentPids);
theAccumulator.includeConceptWithDesignations(system, code, display, designations, sourceConceptPid, sourceConceptDirectParentPids, systemVersion);
} else {
boolean removed = theAccumulator.excludeConcept(system, code);
if (removed) {
@ -789,77 +804,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
// No CodeSystem matching the URL found in the database.
CodeSystem codeSystemFromContext = fetchCanonicalCodeSystemFromCompleteContext(system);
if (codeSystemFromContext == null) {
Consumer<FhirVersionIndependentConcept> consumer = c -> {
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, c.getCode(), c.getDisplay(), c.getSystemVersion());
};
// This is a last ditch effort.. We don't have a CodeSystem resource for the desired CS, and we don't have
// anything at all in the database that matches it. So let's try asking the validation support context
// just in case there is a registered service that knows how to handle this. This can happen, for example,
// if someone creates a valueset that includes UCUM codes, since we don't have a CodeSystem resource for those
// but CommonCodeSystemsTerminologyService can validate individual codes.
List<FhirVersionIndependentConcept> includedConcepts = null;
if (theExpansionFilter.hasCode()) {
includedConcepts = new ArrayList<>();
includedConcepts.add(theExpansionFilter.toFhirVersionIndependentConcept());
} else if (!theIncludeOrExclude.getConcept().isEmpty()) {
includedConcepts = theIncludeOrExclude
.getConcept()
.stream()
.map(t -> new FhirVersionIndependentConcept(theIncludeOrExclude.getSystem(), t.getCode()))
.collect(Collectors.toList());
}
if (includedConcepts != null) {
int foundCount = 0;
for (FhirVersionIndependentConcept next : includedConcepts) {
String nextSystem = next.getSystem();
if (nextSystem == null) {
nextSystem = system;
}
LookupCodeResult lookup = myValidationSupport.lookupCode(new ValidationSupportContext(provideValidationSupport()), nextSystem, next.getCode());
if (lookup != null && lookup.isFound()) {
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, nextSystem, next.getCode(), lookup.getCodeDisplay());
foundCount++;
}
}
if (foundCount == includedConcepts.size()) {
return false;
// ELSE, we'll continue below and throw an exception
}
}
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionRefersToUnknownCs", system);
if (provideExpansionOptions(theExpansionOptions).isFailOnMissingCodeSystem()) {
throw new PreconditionFailedException(msg);
} else {
ourLog.warn(msg);
theValueSetCodeAccumulator.addMessage(msg);
try {
ConversionContext40_50.INSTANCE.init(new VersionConvertor_40_50(new BaseAdvisor_40_50()), "ValueSet");
org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude);
new InMemoryTerminologyServerValidationSupport(myContext).expandValueSetIncludeOrExclude(new ValidationSupportContext(provideValidationSupport()), consumer, includeOrExclude);
} catch (InMemoryTerminologyServerValidationSupport.ExpansionCouldNotBeCompletedInternallyException e) {
if (!theExpansionOptions.isFailOnMissingCodeSystem() && e.getFailureType() == InMemoryTerminologyServerValidationSupport.FailureType.UNKNOWN_CODE_SYSTEM) {
return false;
}
}
if (!theIncludeOrExclude.getConcept().isEmpty()) {
for (ValueSet.ConceptReferenceComponent next : theIncludeOrExclude.getConcept()) {
String nextCode = next.getCode();
if (!theExpansionFilter.hasCode() || theExpansionFilter.getCode().equals(nextCode)) {
if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) {
CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode);
if (code != null) {
String display = code.getDisplay();
addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, nextCode, display);
}
}
}
}
} else {
List<CodeSystem.ConceptDefinitionComponent> concept = codeSystemFromContext.getConcept();
addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, system, concept, theAdd, theExpansionFilter);
throw new InternalErrorException(e);
} finally {
ConversionContext40_50.INSTANCE.close("ValueSet");
}
return false;
@ -1038,10 +997,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
private ValueSet.ConceptReferenceComponent getMatchedConceptIncludedInValueSet(ValueSet.ConceptSetComponent theIncludeOrExclude, TermConcept concept) {
ValueSet.ConceptReferenceComponent theIncludeConcept = theIncludeOrExclude.getConcept().stream().filter(includedConcept ->
includedConcept.getCode().equalsIgnoreCase(concept.getCode())
).findFirst().orElse(null);
return theIncludeConcept;
return theIncludeOrExclude
.getConcept()
.stream().filter(includedConcept -> includedConcept.getCode().equalsIgnoreCase(concept.getCode()))
.findFirst()
.orElse(null);
}
/**
@ -1089,9 +1049,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
private void addOrRemoveCode(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, boolean theAdd, String theSystem, String theCode, String theDisplay) {
private void addOrRemoveCode(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, boolean theAdd, String theSystem, String theCode, String theDisplay, String theSystemVersion) {
if (theAdd && theAddedCodes.add(theSystem + "|" + theCode)) {
theValueSetCodeAccumulator.includeConcept(theSystem, theCode, theDisplay, null, null);
theValueSetCodeAccumulator.includeConcept(theSystem, theCode, theDisplay, null, null, theSystemVersion);
}
if (!theAdd && theAddedCodes.remove(theSystem + "|" + theCode)) {
theValueSetCodeAccumulator.excludeConcept(theSystem, theCode);
@ -1211,6 +1171,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
theBooleanClause.must(thePredicateFactory.exists().field(TermConceptPropertyBinder.CONCEPT_FIELD_PROPERTY_PREFIX + "EXTERNAL_COPYRIGHT_NOTICE"));
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincAncestor2(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
@ -1244,6 +1205,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) {
case EQUAL:
@ -1305,7 +1267,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
private boolean isCodeSystemLoinc(String theSystem) {
return ITermLoaderSvc.LOINC_URI.equals(theSystem);
return LOINC_URI.equals(theSystem);
}
private void handleFilterDisplay(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
@ -1436,7 +1398,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Validate.isTrue(theInclude.getFilter().isEmpty(), "Can not expand ValueSet with filters - Hibernate Search is not enabled on this server.");
Validate.isTrue(isNotBlank(theSystem), "Can not expand ValueSet without explicit system - Hibernate Search is not enabled on this server.");
if (theInclude.getConcept().isEmpty()) {
for (TermConcept next : theVersion.getConcepts()) {
addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, theAdd, theSystem, theInclude.getVersion(), next.getCode(), next.getDisplay(), next.getId(), next.getParentPidsAsString());
@ -1453,10 +1414,37 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
@Override
@Transactional
public String invalidatePreCalculatedExpansion(IIdType theValueSetId, RequestDetails theRequestDetails) {
IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").read(theValueSetId, theRequestDetails);
ValueSet canonicalValueSet = toCanonicalValueSet(valueSet);
Optional<TermValueSet> optionalTermValueSet = fetchValueSetEntity(canonicalValueSet);
if (!optionalTermValueSet.isPresent()) {
return myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "valueSetNotFoundInTerminologyDatabase", theValueSetId);
}
ourLog.info("Invalidating pre-calculated expansion on ValueSet {} / {}", theValueSetId, canonicalValueSet.getUrl());
TermValueSet termValueSet = optionalTermValueSet.get();
if (termValueSet.getExpansionStatus() == TermValueSetPreExpansionStatusEnum.NOT_EXPANDED) {
return myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "valueSetCantInvalidateNotYetPrecalculated", termValueSet.getUrl(), termValueSet.getExpansionStatus());
}
Long totalConcepts = termValueSet.getTotalConcepts();
deletePreCalculatedValueSetContents(termValueSet);
termValueSet.setExpansionStatus(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED);
termValueSet.setExpansionTimestamp(null);
myTermValueSetDao.save(termValueSet);
return myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "valueSetPreExpansionInvalidated", termValueSet.getUrl(), totalConcepts);
}
@Override
public boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet) {
ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement());
Optional<TermValueSet> optionalTermValueSet = myTermValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong());
Optional<TermValueSet> optionalTermValueSet = fetchValueSetEntity(theValueSet);
if (!optionalTermValueSet.isPresent()) {
ourLog.warn("ValueSet is not present in terminology tables. Will perform in-memory code validation. {}", getValueSetInfo(theValueSet));
@ -1474,6 +1462,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return true;
}
private Optional<TermValueSet> fetchValueSetEntity(ValueSet theValueSet) {
ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement());
Optional<TermValueSet> optionalTermValueSet = myTermValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong());
return optionalTermValueSet;
}
protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(
ConceptValidationOptions theValidationOptions,
ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {
@ -1505,36 +1499,42 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return null;
}
TermValueSet valueSetEntity = myTermValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong()).orElseThrow(() -> new IllegalStateException());
Object timingDescription = toHumanReadableExpansionTimestamp(valueSetEntity);
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "validationPerformedAgainstPreExpansion", timingDescription);
if (theValidationOptions.isValidateDisplay() && concepts.size() > 0) {
String systemVersion = null;
for (TermValueSetConcept concept : concepts) {
systemVersion = concept.getSystemVersion();
if (isBlank(theDisplay) || isBlank(concept.getDisplay()) || theDisplay.equals(concept.getDisplay())) {
return new IValidationSupport.CodeValidationResult()
.setCode(concept.getCode())
.setDisplay(concept.getDisplay());
.setDisplay(concept.getDisplay())
.setCodeSystemVersion(concept.getSystemVersion())
.setMessage(msg);
}
}
return createFailureCodeValidationResult(theSystem, theCode, " - Concept Display \"" + theDisplay + "\" does not match expected \"" + concepts.get(0).getDisplay() + "\"").setDisplay(concepts.get(0).getDisplay());
return createFailureCodeValidationResult(theSystem, theCode, systemVersion, " - Concept Display \"" + theDisplay + "\" does not match expected \"" + concepts.get(0).getDisplay() + "\". " + msg).setDisplay(concepts.get(0).getDisplay());
}
if (!concepts.isEmpty()) {
return new IValidationSupport.CodeValidationResult()
.setCode(concepts.get(0).getCode())
.setDisplay(concepts.get(0).getDisplay());
.setDisplay(concepts.get(0).getDisplay())
.setCodeSystemVersion(concepts.get(0).getSystemVersion())
.setMessage(msg);
}
return createFailureCodeValidationResult(theSystem, theCode);
return createFailureCodeValidationResult(theSystem, theCode, null, " - Unknown code " + theSystem + "#" + theCode + ". " + msg);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode) {
String append = "";
return createFailureCodeValidationResult(theSystem, theCode, append);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode, String theAppend) {
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode, String theCodeSystemVersion, String theAppend) {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage("Unknown code {" + theSystem + "}" + theCode + theAppend);
.setCodeSystemVersion(theCodeSystemVersion)
.setMessage("Unable to validate code " + theSystem + "#" + theCode + theAppend);
}
private List<TermValueSetConcept> findByValueSetResourcePidSystemAndCode(ResourcePersistentId theResourcePid, String theSystem, String theCode) {
@ -1781,6 +1781,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
// We are done with this ValueSet.
txTemplate.execute(t -> {
valueSetToExpand.setExpansionStatus(TermValueSetPreExpansionStatusEnum.EXPANDED);
valueSetToExpand.setExpansionTimestamp(new Date());
myTermValueSetDao.saveAndFlush(valueSetToExpand);
return null;
});
@ -2096,11 +2097,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
.setCode(code.getCode())
.setDisplay(code.getDisplay());
} else {
return createFailureCodeValidationResult(theCodeSystem, theCode, " - Concept Display \"" + code.getDisplay() + "\" does not match expected \"" + code.getDisplay() + "\"").setDisplay(code.getDisplay());
return createFailureCodeValidationResult(theCodeSystem, theCode, code.getSystemVersion(), " - Concept Display \"" + code.getDisplay() + "\" does not match expected \"" + code.getDisplay() + "\"").setDisplay(code.getDisplay());
}
}
return createFailureCodeValidationResult(theCodeSystem, theCode);
return createFailureCodeValidationResult(theCodeSystem, theCode, null, " - Code can not be found in CodeSystem");
}
IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theValueSetUrl, String theCodeSystem, String theCode, String theDisplay) {
@ -2112,7 +2113,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet);
if (pid != null) {
if (isValueSetPreExpandedForCodeValidation(valueSet)) {
return validateCodeIsInPreExpandedValueSet(theValidationOptions, valueSet, theCodeSystem, theCode, null, null, null);
return validateCodeIsInPreExpandedValueSet(theValidationOptions, valueSet, theCodeSystem, theCode, theDisplay, null, null);
}
}
}
@ -2122,7 +2123,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet);
} else {
String append = " - Unable to locate ValueSet[" + theValueSetUrl + "]";
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, append);
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, null, append);
}
return retVal;
@ -2383,10 +2384,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
predicates.add(criteriaBuilder.equal(root.get("myCode"), theCode));
}
if (isNotBlank(theDisplay)) {
predicates.add(criteriaBuilder.equal(root.get("myDisplay"), theDisplay));
}
if (isNoneBlank(theCodeSystemUrl)) {
predicates.add(criteriaBuilder.equal(systemJoin.get("myCodeSystemUri"), theCodeSystemUrl));
}
@ -2413,13 +2410,33 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
if (!resultsList.isEmpty()) {
TermConcept concept = resultsList.get(0);
if (isNotBlank(theDisplay) && !theDisplay.equals(concept.getDisplay())) {
String message = "Concept Display \"" + theDisplay + "\" does not match expected \"" + concept.getDisplay() + "\" for CodeSystem: " + theCodeSystemUrl;
return createFailureCodeValidationResult(theCodeSystemUrl, theCode, theCodeSystemVersion, message);
}
return new CodeValidationResult().setCode(concept.getCode()).setDisplay(concept.getDisplay());
}
if (isBlank(theDisplay))
return createFailureCodeValidationResult(theCodeSystemUrl, theCode);
else
return createFailureCodeValidationResult(theCodeSystemUrl, theCode, " - Concept Display : " + theDisplay);
return createFailureCodeValidationResult(theCodeSystemUrl, theCode, theCodeSystemVersion, " - Code is not found in CodeSystem: " + theCodeSystemUrl);
}
@Override
public Optional<IBaseResource> readCodeSystemByForcedId(String theForcedId) {
@SuppressWarnings("unchecked")
List<ResourceTable> resultList = (List<ResourceTable>) myEntityManager.createQuery(
"select f.myResource from ForcedId f " +
"where f.myResourceType = 'CodeSystem' and f.myForcedId = '" + theForcedId + "'").getResultList();
if (resultList.isEmpty()) return Optional.empty();
if (resultList.size() > 1) throw new NonUniqueResultException(
"More than one CodeSystem is pointed by forcedId: " + theForcedId + ". Was constraint "
+ ForcedId.IDX_FORCEDID_TYPE_FID + " removed?");
IFhirResourceDao<CodeSystem> csDao = myDaoRegistry.getResourceDao("CodeSystem");
IBaseResource cs = csDao.toResource(resultList.get(0), false);
return Optional.of(cs);
}
public static class Job implements HapiJob {
@ -2526,22 +2543,4 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return theReqLang.equalsIgnoreCase(theStoredLang);
}
@Override
public Optional<IBaseResource> readCodeSystemByForcedId(String theForcedId) {
@SuppressWarnings("unchecked")
List<ResourceTable> resultList = (List<ResourceTable>) myEntityManager.createQuery(
"select f.myResource from ForcedId f " +
"where f.myResourceType = 'CodeSystem' and f.myForcedId = '" + theForcedId + "'").getResultList();
if (resultList.isEmpty()) return Optional.empty();
if (resultList.size() > 1) throw new NonUniqueResultException(
"More than one CodeSystem is pointed by forcedId: " + theForcedId + ". Was constraint "
+ ForcedId.IDX_FORCEDID_TYPE_FID + " removed?");
IFhirResourceDao<CodeSystem> csDao = myDaoRegistry.getResourceDao("CodeSystem");
IBaseResource cs = csDao.toResource(resultList.get(0), false);
return Optional.of(cs );
}
}

View File

@ -29,9 +29,9 @@ public interface IValueSetConceptAccumulator {
void addMessage(String theMessage);
void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids);
void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids, @Nullable String theSystemVersion);
void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, @Nullable Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids);
void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, @Nullable Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids, @Nullable String theSystemVersion);
/**
* @return Returns <code>true</code> if the code was actually present and was removed

View File

@ -65,7 +65,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = toCanonicalValueSet(theValueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(theExpansionOptions, valueSetToExpandR4);
return new ValueSetExpansionOutcome(VersionConvertorFactory_30_40.convertResource(expandedR4, new BaseAdvisor_30_40(false)), null);
return new ValueSetExpansionOutcome(VersionConvertorFactory_30_40.convertResource(expandedR4, new BaseAdvisor_30_40(false)));
} catch (FHIRException e) {
throw new InternalErrorException(e);
}

View File

@ -66,13 +66,13 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator {
}
@Override
public void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids) {
saveConcept(theSystem, theCode, theDisplay, theSourceConceptPid, theSourceConceptDirectParentPids);
public void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theSystemVersion) {
saveConcept(theSystem, theCode, theDisplay, theSourceConceptPid, theSourceConceptDirectParentPids, theSystemVersion);
}
@Override
public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids) {
TermValueSetConcept concept = saveConcept(theSystem, theCode, theDisplay, theSourceConceptPid, theSourceConceptDirectParentPids);
public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theSystemVersion) {
TermValueSetConcept concept = saveConcept(theSystem, theCode, theDisplay, theSourceConceptPid, theSourceConceptDirectParentPids, theSystemVersion);
if (theDesignations != null) {
for (TermConceptDesignation designation : theDesignations) {
saveConceptDesignation(concept, designation);
@ -117,7 +117,7 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator {
return false;
}
private TermValueSetConcept saveConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids) {
private TermValueSetConcept saveConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theSystemVersion) {
ValidateUtil.isNotBlankOrThrowInvalidRequest(theSystem, "ValueSet contains a concept with no system value");
ValidateUtil.isNotBlankOrThrowInvalidRequest(theCode, "ValueSet contains a concept with no code value");
@ -135,6 +135,7 @@ public class ValueSetConceptAccumulator implements IValueSetConceptAccumulator {
if (isNotBlank(theDisplay)) {
concept.setDisplay(theDisplay);
}
concept.setSystemVersion(theSystemVersion);
concept.setSourceConceptPid(theSourceConceptPid);
concept.setSourceConceptDirectParentPids(theSourceConceptDirectParentPids);

View File

@ -94,7 +94,7 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
}
@Override
public void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids) {
public void includeConcept(String theSystem, String theCode, String theDisplay, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theCodeSystemVersion) {
if (mySkipCountRemaining > 0) {
mySkipCountRemaining--;
return;
@ -106,10 +106,11 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
setSystemAndVersion(theSystem, contains);
contains.setCode(theCode);
contains.setDisplay(theDisplay);
contains.setVersion(theCodeSystemVersion);
}
@Override
public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids) {
public void includeConceptWithDesignations(String theSystem, String theCode, String theDisplay, Collection<TermConceptDesignation> theDesignations, Long theSourceConceptPid, String theSourceConceptDirectParentPids, String theCodeSystemVersion) {
if (mySkipCountRemaining > 0) {
mySkipCountRemaining--;
return;
@ -129,6 +130,11 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
setSystemAndVersion(theSystem, contains);
contains.setCode(theCode);
contains.setDisplay(theDisplay);
if (isNotBlank(theCodeSystemVersion)) {
contains.setVersion(theCodeSystemVersion);
}
if (theDesignations != null) {
for (TermConceptDesignation termConceptDesignation : theDesignations) {
contains

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
@ -116,6 +117,8 @@ public interface ITermReadSvc extends IValidationSupport {
*/
CodeValidationResult codeSystemValidateCode(IIdType theCodeSystemId, String theValueSetUrl, String theVersion, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
String invalidatePreCalculatedExpansion(IIdType theValueSetId, RequestDetails theRequestDetails);
/**
* Version independent
*/

View File

@ -72,6 +72,7 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
@PostConstruct
public void postConstruct() {
addValidationSupport(myUnknownCodeSystemWarningValidationSupport);
addValidationSupport(myDefaultProfileValidationSupport);
addValidationSupport(myJpaValidationSupport);
//TODO MAKE SURE THAT THIS IS BEING CAL
@ -81,7 +82,6 @@ public class JpaValidationSupportChain extends ValidationSupportChain {
addValidationSupport(myNpmJpaValidationSupport);
addValidationSupport(new CommonCodeSystemsTerminologyService(myFhirContext));
addValidationSupport(myConceptMappingSvc);
addValidationSupport(myUnknownCodeSystemWarningValidationSupport);
}
}

View File

@ -91,6 +91,7 @@ public class TransactionProcessorTest {
when(myEntityManager.unwrap(eq(Session.class))).thenReturn(mySession);
}
@Test
public void testTransactionWithDisabledResourceType() {

View File

@ -78,8 +78,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
CodingDt coding = null;
CodeableConceptDt codeableConcept = null;
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
//TODO JA, from what I read, this _should_ pass, but this was flipped to false in a previous commit.
assertTrue(result.isOk());
assertFalse(result.isOk());
}
@Test
@ -107,8 +106,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
CodeableConceptDt codeableConcept = null;
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\" for in-memory expansion of ValueSet: http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", result.getMessage());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
}
@Test

View File

@ -550,23 +550,6 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
assertEquals(0, expansion.getExpansion().getContains().size());
}
@Test
public void testExpandWithNoResultsInLocalValueSet2() {
createLocalCsAndVs();
ValueSet vs = new ValueSet();
ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(URL_MY_CODE_SYSTEM + "AA");
include.addConcept().setCode("A");
try {
myValueSetDao.expand(vs, null);
fail();
} catch (PreconditionFailedException e) {
assertEquals("Unknown CodeSystem URI \"http://example.com/my_code_systemAA\" referenced from ValueSet", e.getMessage());
}
}
@Test
public void testExpandWithSystemAndCodesInExternalValueSet() {
createExternalCsAndLocalVs();

View File

@ -1510,20 +1510,18 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
id2 = myOrganizationDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
}
// FIXME: restore
int size;
SearchParameterMap params = new SearchParameterMap();
// params.setLoadSynchronous(true);
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
//
// params = new SearchParameterMap();
// params.add("_id", new StringParam(id1));
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
//
// params = new SearchParameterMap();
// params.add("_id", new StringParam("9999999999999999"));
// assertEquals(0, toList(myPatientDao.search(params)).size());
params.setLoadSynchronous(true);
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
params = new SearchParameterMap();
params.add("_id", new StringParam(id1));
assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
params = new SearchParameterMap();
params.add("_id", new StringParam("9999999999999999"));
assertEquals(0, toList(myPatientDao.search(params)).size());
myCaptureQueriesListener.clear();
params = new SearchParameterMap();

View File

@ -14,7 +14,7 @@ import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.term.ValueSetExpansionR4Test;
import ca.uhn.fhir.jpa.util.ValueSetTestUtil;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
@ -300,7 +300,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
// Non Pre-Expanded
ValueSet outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
assertEquals("ValueSet \"ValueSet.url[http://vs]\" has not yet been pre-expanded. Performing in-memory expansion without parameters. Current status: NOT_EXPANDED | The ValueSet is waiting to be picked up and pre-expanded by a scheduled task.", outcome.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE));
assertThat(ValueSetExpansionR4Test.toCodes(outcome).toString(), ValueSetExpansionR4Test.toCodes(outcome), contains(
assertThat(ValueSetTestUtil.toCodes(outcome).toString(), ValueSetTestUtil.toCodes(outcome), contains(
"code5", "code4", "code3", "code2", "code1"
));

View File

@ -655,23 +655,6 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
}
@Test
public void testExpandWithNoResultsInLocalValueSet2() {
createLocalCsAndVs();
ValueSet vs = new ValueSet();
ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(URL_MY_CODE_SYSTEM + "AA");
include.addConcept().setCode("A");
try {
myValueSetDao.expand(vs, null);
fail();
} catch (PreconditionFailedException e) {
assertEquals("Unknown CodeSystem URI \"http://example.com/my_code_systemAA\" referenced from ValueSet", e.getMessage());
}
}
// TODO: get this working
@Disabled
@Test

View File

@ -154,7 +154,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
encoded = encode(oo);
ourLog.info(encoded);
assertEquals(1, oo.getIssue().size(), encoded);
assertEquals("The code provided (http://cs#code99) is not in the value set http://vs, and a code from this value set is required: Unknown code system: http://cs", oo.getIssueFirstRep().getDiagnostics(), encoded);
assertEquals("The code provided (http://cs#code99) is not in the value set http://vs, and a code from this value set is required: Unknown code 'http://cs#code99' for in-memory expansion of ValueSet 'http://vs'", oo.getIssueFirstRep().getDiagnostics(), encoded);
assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity(), encoded);
}
@ -194,9 +194,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
oo = validateAndReturnOutcome(obs);
encoded = encode(oo);
ourLog.info(encoded);
assertEquals(1, oo.getIssue().size(), encoded);
assertEquals("Error Unknown code system: http://cs validating Coding", oo.getIssueFirstRep().getDiagnostics(), encoded);
assertEquals(OperationOutcome.IssueSeverity.WARNING, oo.getIssueFirstRep().getSeverity(), encoded);
assertEquals("No issues detected during validation", oo.getIssueFirstRep().getDiagnostics(), encoded);
}
@ -463,7 +461,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO");
oo = validateAndReturnOutcome(obs);
assertEquals("Unknown code 'http://terminology.hl7.org/CodeSystem/observation-category#FOO'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
assertEquals("Unknown code 'http://terminology.hl7.org/CodeSystem/observation-category#FOO' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-category'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
// Make sure we're caching the validations as opposed to hitting the DB every time
myCaptureQueriesListener.clear();
@ -792,7 +790,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setDisplay("Some Code");
outcome = (OperationOutcome) myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, "http://example.com/structuredefinition", mySrd).getOperationOutcome();
ourLog.info("Outcome: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("Unknown code in fragment CodeSystem 'http://example.com/codesystem#foo-foo'", outcome.getIssueFirstRep().getDiagnostics());
assertEquals("Unknown code in fragment CodeSystem 'http://example.com/codesystem#foo-foo' for in-memory expansion of ValueSet 'http://example.com/valueset'", outcome.getIssueFirstRep().getDiagnostics());
assertEquals(OperationOutcome.IssueSeverity.WARNING, outcome.getIssueFirstRep().getSeverity());
// Correct codesystem, Code in codesystem
@ -901,7 +899,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO");
oo = validateAndReturnOutcome(obs);
assertEquals("Unknown code 'http://terminology.hl7.org/CodeSystem/observation-category#FOO'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
assertEquals("Unknown code 'http://terminology.hl7.org/CodeSystem/observation-category#FOO' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-category'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
}
@ -1508,7 +1506,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info("Outcome:\n{}", encoded);
assertThat(encoded, containsString("Unknown code {urn:oid:2.16.840.1.113883.6.238}2106-3AAA"));
assertThat(encoded, containsString("Unable to validate code urn:oid:2.16.840.1.113883.6.238#2106-3AAA"));
}
}
{
@ -1720,7 +1718,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://fooVs"), null, new StringType("10013-1"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertFalse(result.isOk());
assertEquals("Unknown code {http://loinc.org}10013-1 - Unable to locate ValueSet[http://fooVs]", result.getMessage());
assertEquals("Unable to validate code http://loinc.org#10013-1 - Unable to locate ValueSet[http://fooVs]", result.getMessage());
}

View File

@ -4,7 +4,9 @@ import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.jpa.dao.data.ITermValueSetConceptDao;
import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.ValueSetTestUtil;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.hamcrest.Matchers;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.Test;
@ -17,6 +19,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -233,12 +236,9 @@ public class FhirResourceDaoR4ValueSetMultiVersionTest extends BaseJpaR4Test {
include.setVersion("1");
include.addConcept().setCode("A");
try {
myValueSetDao.expand(vs, null);
fail();
} catch (PreconditionFailedException e) {
assertEquals("Unknown CodeSystem URI \"http://example.com/my_code_systemAA\" referenced from ValueSet", e.getMessage());
}
ValueSet expansion = myValueSetDao.expand(vs, null);
assertThat(ValueSetTestUtil.toCodes(expansion), Matchers.contains("A"));
}

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
@ -12,6 +14,7 @@ import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
@ -98,7 +101,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\" for in-memory expansion of ValueSet: http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", result.getMessage());
}
@Test
@ -273,6 +276,47 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsFragmentWithoutCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("0.1.17");
myCodeSystemDao.update(cs);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myValueSetDao.update(vs);
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(null, new IdType("ValueSet/vaccinecode"), new CodeType("28571000087109"), new CodeType("http://snomed.info/sct"), null, null, null, mySrd);
assertTrue(outcome.isOk());
outcome = myTermSvc.validateCodeInValueSet(
new ValidationSupportContext(myValidationSupport),
new ConceptValidationOptions(),
"http://snomed.info/sct",
"28571000087109",
"MODERNA COVID-19 mRNA-1273",
vs
);
assertTrue(outcome.isOk());
ValueSet expansion = myValueSetDao.expand(new IdType("ValueSet/vaccinecode"), new ValueSetExpansionOptions(), mySrd);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
}
}

View File

@ -103,7 +103,7 @@ public class FhirResourceDaoR5ValueSetTest extends BaseJpaR5Test {
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\"", result.getMessage());
assertEquals("Concept Display \"Systolic blood pressure at First encounterXXXX\" does not match expected \"Systolic blood pressure at First encounter\" for in-memory expansion of ValueSet: http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", result.getMessage());
}
@Test

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
@ -96,6 +97,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
ourRestServer.registerProvider(subscriptionTriggeringProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry);
confProvider.setImplementationDescription("THIS IS THE DESC");

View File

@ -373,7 +373,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "identifier", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.withParameter(Parameters.class, "url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
@ -564,7 +564,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: $expand operation at the type level (no ID specified) requires an identifier or a valueSet as a part of the request.", e.getMessage());
assertEquals("HTTP 400 Bad Request: $expand operation at the type level (no ID specified) requires a url or a valueSet as a part of the request.", e.getMessage());
}
try {
@ -574,11 +574,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("identifier", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: $expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.", e.getMessage());
assertEquals("HTTP 400 Bad Request: $expand must EITHER be invoked at the instance level, or have a url specified, or have a ValueSet specified. Can not combine these options.", e.getMessage());
}
try {
@ -588,11 +588,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onInstance(myExtensionalVsId)
.named("expand")
.withParameter(Parameters.class, "valueSet", toExpand)
.andParameter("identifier", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.andParameter("url", new UriType("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2"))
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: $expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.", e.getMessage());
assertEquals("HTTP 400 Bad Request: $expand must EITHER be invoked at the instance level, or have a url specified, or have a ValueSet specified. Can not combine these options.", e.getMessage());
}
try {
@ -670,7 +670,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.operation()
.onType(ValueSet.class)
.named("expand")
.withParameter(Parameters.class, "identifier", new UriType(URL_MY_VALUE_SET))
.withParameter(Parameters.class, "url", new UriType(URL_MY_VALUE_SET))
.execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
@ -796,7 +796,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onType(ValueSet.class)
.named("validate-code")
.withParameter(Parameters.class, "code", new StringType("male"))
.andParameter("identifier", new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"))
.andParameter("url", new StringType("http://hl7.org/fhir/ValueSet/administrative-gender"))
.andParameter("system", new StringType("http://hl7.org/fhir/administrative-gender"))
.useHttpGet()
.execute();
@ -807,8 +807,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
}
/**
@ -834,8 +837,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
}
@Test

View File

@ -9,6 +9,7 @@ import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
@ -115,6 +116,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider, myDeleteExpungeProvider, myReindexProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(DiffProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class);

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.provider.r4;
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.jpa.config.TestR4Config;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils;
@ -73,6 +74,11 @@ public class ResourceProviderConcurrencyR4Test extends BaseResourceProviderR4Tes
*/
@Test
public void testSearchesExecuteConcurrently() {
if (TestR4Config.getMaxThreads() == 1) {
ourLog.info("Skipping this test because the test thread pool only has one max connection");
return;
}
createPatient(withFamily("FAMILY1"));
createPatient(withFamily("FAMILY2"));
createPatient(withFamily("FAMILY3"));

View File

@ -482,7 +482,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
assertEquals("Unable to validate code http://acme.org#8452-5-a - Code is not found in CodeSystem: http://acme.org", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@ -518,7 +518,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5 - Concept Display : Old Systolic blood pressure.inspiration - expiration", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
assertEquals("Unable to validate code http://acme.org#8452-5Concept Display \"Old Systolic blood pressure.inspiration - expiration\" does not match expected \"Systolic blood pressure.inspiration - expiration\" for CodeSystem: http://acme.org", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test
@ -675,7 +675,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
assertEquals("Unable to validate code http://acme.org#8452-5-a - Code is not found in CodeSystem: http://acme.org", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@ -755,7 +755,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue());
assertEquals("Unknown code {http://acme.org}8452-5-a", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
assertEquals("Unable to validate code http://acme.org#8452-5-a - Code is not found in CodeSystem: http://acme.org", ((StringType) respParam.getParameter().get(1).getValue()).getValueAsString());
}
@Test

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet;
import ca.uhn.fhir.jpa.config.TestR4WithLuceneDisabledConfig;
import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.parser.IParser;
@ -190,6 +191,8 @@ public class ResourceProviderR4ValueSetLuceneDisabledTest extends BaseJpaTest {
ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class);
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
Server server = new Server(0);
ServletContextHandler proxyHandler = new ServletContextHandler();

View File

@ -15,6 +15,7 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
@ -119,7 +120,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
private void loadAndPersistValueSet() throws IOException {
ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
valueSet.setId("ValueSet/vs");
persistValueSet(valueSet, HttpVerb.POST);
persistValueSet(valueSet, HttpVerb.PUT);
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
@ -1104,7 +1105,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(toDirectCodes(expansion.getExpansion().getContains()), containsInAnyOrder("A", "AA", "AB", "AAA"));
assertEquals(15, myCaptureQueriesListener.getSelectQueries().size());
assertEquals(null, expansion.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE));
assertEquals("ValueSet with URL \"Unidentified ValueSet\" was expanded using an in-memory expansion", expansion.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE));
// Hierarchical
myCaptureQueriesListener.clear();
@ -1156,7 +1157,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(toDirectCodes(expansion.getExpansion().getContains()), containsInAnyOrder("A", "AA", "AB", "AAA"));
assertEquals(3, myCaptureQueriesListener.getSelectQueries().size());
assertEquals("ValueSet was expanded using a pre-calculated expansion", expansion.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE));
assertThat(expansion.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE), containsString("ValueSet was expanded using an expansion that was pre-calculated"));
// Hierarchical
myCaptureQueriesListener.clear();
@ -1284,8 +1285,11 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
}
@Test
@ -1370,6 +1374,48 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
}
@Test
public void testInvalidatePrecalculatedExpansion() throws IOException {
loadAndPersistCodeSystemAndValueSet();
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, runInTransaction(()->myTermValueSetDao.findTermValueSetByUrlAndNullVersion("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2").orElseThrow(()->new IllegalStateException()).getExpansionStatus()));
Parameters outcome = myClient
.operation()
.onInstance("ValueSet/vs")
.named(ProviderConstants.OPERATION_INVALIDATE_EXPANSION)
.withNoParameters(Parameters.class)
.execute();
assertEquals("ValueSet with URL \"http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2\" precaluclated expansion with 24 concept(s) has been invalidated", outcome.getParameter("message").primitiveValue());
assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, runInTransaction(()->myTermValueSetDao.findTermValueSetByUrlAndNullVersion("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2").orElseThrow(()->new IllegalStateException()).getExpansionStatus()));
outcome = myClient
.operation()
.onInstance("ValueSet/vs")
.named(ProviderConstants.OPERATION_INVALIDATE_EXPANSION)
.withNoParameters(Parameters.class)
.execute();
assertEquals("ValueSet with URL \"http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2\" already has status: NOT_EXPANDED", outcome.getParameter("message").primitiveValue());
}
@Test
public void testInvalidatePrecalculatedExpansion_NonExistent() {
try {
myClient
.operation()
.onInstance("ValueSet/FOO")
.named(ProviderConstants.OPERATION_INVALIDATE_EXPANSION)
.withNoParameters(Parameters.class)
.execute();
fail();
} catch (ResourceNotFoundException e) {
assertEquals("HTTP 404 Not Found: Resource ValueSet/FOO is not known", e.getMessage());
}
}
@Test
public void testExpandByValueSetWithFilterContainsPrefixValue() throws IOException {
loadAndPersistCodeSystem();

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
@ -108,6 +109,8 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
myDaoRegistry = myAppCtx.getBean(DaoRegistry.class);
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider);
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.class);

View File

@ -1248,8 +1248,11 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(2).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(2).getValue()).getValue());
}
// Good code and system, but not in specified valueset
@ -1271,7 +1274,7 @@ public class ResourceProviderR5ValueSetTest extends BaseResourceProviderR5Test {
assertEquals(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Unknown code 'http://hl7.org/fhir/administrative-gender#male'", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
assertEquals("Unknown code 'http://hl7.org/fhir/administrative-gender#male' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/marital-status'", ((StringType) respParam.getParameter().get(1).getValue()).getValue());
}
}

View File

@ -245,8 +245,8 @@ public class TerminologyLoaderSvcIntegrationDstu3Test extends BaseJpaDstu3Test {
myTerminologyDeferredStorageSvc.saveDeferred();
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(new UriType("http://loinc.org/vs"), null, new StringType("10013-1-9999999999"), new StringType(ITermLoaderSvc.LOINC_URI), null, null, null, mySrd);
assertNull(result);
assertFalse(result.isOk());
assertEquals("Failed to expand ValueSet 'http://loinc.org/vs' (in-memory). Could not validate code http://loinc.org#10013-1-9999999999. Error was: Unable to expand ValueSet because CodeSystem could not be found: http://loinc.org|1.0.0", result.getMessage());
}
private Set<String> toExpandedCodes(ValueSet theExpanded) {

View File

@ -42,7 +42,7 @@ public class ValueSetConceptAccumulatorTest {
@Test
public void testIncludeConcept() {
for (int i = 0; i < 1000; i++) {
myAccumulator.includeConcept("sys", "code", "display", null, null);
myAccumulator.includeConcept("sys", "code", "display", null, null, null);
}
verify(myValueSetConceptDao, times(1000)).save(any());
}

View File

@ -206,7 +206,7 @@ public class ValueSetExpansionR4ElasticsearchIT extends BaseJpaTest {
include.setSystem(CS_URL);
myTermSvc.expandValueSet(null, vs, myValueSetCodeAccumulator);
verify(myValueSetCodeAccumulator, times(9)).includeConceptWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection(), nullable(Long.class), nullable(String.class));
verify(myValueSetCodeAccumulator, times(9)).includeConceptWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection(), nullable(Long.class), nullable(String.class), nullable(String.class));
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
@ -13,14 +15,17 @@ import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.util.SqlQuery;
import ca.uhn.fhir.jpa.util.ValueSetTestUtil;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.HttpVerb;
import org.junit.jupiter.api.AfterEach;
@ -47,8 +52,10 @@ import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -90,15 +97,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet);
assertEquals(24, expandedValueSet.getExpansion().getContains().size());
runInTransaction(() -> {
assertEquals(24, myTermValueSetConceptDao.count());
});
runInTransaction(() -> assertEquals(24, myTermValueSetConceptDao.count()));
myValueSetDao.delete(valueSet.getIdElement());
runInTransaction(() -> {
assertEquals(0, myTermValueSetConceptDao.count());
});
runInTransaction(() -> assertEquals(0, myTermValueSetConceptDao.count()));
expandedValueSet = myTermSvc.expandValueSet(null, valueSet);
assertEquals(24, expandedValueSet.getExpansion().getContains().size());
@ -183,10 +186,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet));
assertThat(toCodes(expandedValueSet).toString(), toCodes(expandedValueSet), containsInAnyOrder(
assertThat(ValueSetTestUtil.toCodes(expandedValueSet).toString(), ValueSetTestUtil.toCodes(expandedValueSet), containsInAnyOrder(
"code9", "code90", "code91", "code92", "code93", "code94", "code95", "code96", "code97", "code98", "code99"
));
assertEquals(11, expandedValueSet.getExpansion().getContains().size(), toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getContains().size(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getTotal());
// Make sure we used the pre-expanded version
@ -215,7 +218,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Expansion should contain all codes
myCaptureQueriesListener.clear();
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
List<String> codes = expandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
List<String> codes = ValueSetTestUtil.toCodes(expandedValueSet);
assertThat(codes.toString(), codes, containsInAnyOrder("code100", "code1000", "code1001", "code1002", "code1003", "code1004"));
// Make sure we used the pre-expanded version
@ -230,7 +233,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
myCaptureQueriesListener.clear();
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(0, 1000).setFilter("display value 100");
ValueSet expandedValueSet = myValueSetDao.expand(vsId, options, mySrd);
List<String> codes = expandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
List<String> codes = ValueSetTestUtil.toCodes(expandedValueSet);
assertThat(codes.toString(), codes, containsInAnyOrder("code100", "code1000", "code1001", "code1002", "code1003", "code1004"));
// Make sure we used the pre-expanded version
@ -273,8 +276,8 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
expandedConceptCodes.removeIf(concept -> !concept.startsWith("code9"));
//Ensure that the subsequent expansion with offset returns the same slice we are anticipating.
assertThat(toCodes(expandedValueSet).toString(), toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(offset, offset + count))));
assertEquals(4, expandedValueSet.getExpansion().getContains().size(), toCodes(expandedValueSet).toString());
assertThat(ValueSetTestUtil.toCodes(expandedValueSet).toString(), ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(offset, offset + count))));
assertEquals(4, expandedValueSet.getExpansion().getContains().size(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getTotal());
// Make sure we used the pre-expanded version
@ -301,7 +304,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(null, input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet));
assertThat(toCodes(expandedValueSet).toString(), toCodes(expandedValueSet), contains("code99"));
assertThat(ValueSetTestUtil.toCodes(expandedValueSet).toString(), ValueSetTestUtil.toCodes(expandedValueSet), contains("code99"));
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
@ -335,10 +338,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet));
assertThat(toCodes(expandedValueSet).toString(), toCodes(expandedValueSet), containsInAnyOrder(
assertThat(ValueSetTestUtil.toCodes(expandedValueSet).toString(), ValueSetTestUtil.toCodes(expandedValueSet), containsInAnyOrder(
"code9", "code91", "code92", "code93", "code94", "code95", "code96", "code97", "code98", "code99"
));
assertEquals(10, expandedValueSet.getExpansion().getContains().size(), toCodes(expandedValueSet).toString());
assertEquals(10, expandedValueSet.getExpansion().getContains().size(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
assertEquals(10, expandedValueSet.getExpansion().getTotal());
// Make sure we used the pre-expanded version
@ -434,11 +437,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet));
assertThat(toCodes(expandedValueSet).toString(), toCodes(expandedValueSet), containsInAnyOrder(
assertThat(ValueSetTestUtil.toCodes(expandedValueSet).toString(), ValueSetTestUtil.toCodes(expandedValueSet), containsInAnyOrder(
"code9", "code90", "code91", "code92", "code93", "code94", "code95", "code96", "code97", "code98", "code99"
));
assertEquals(11, expandedValueSet.getExpansion().getContains().size(), toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getTotal(), toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getContains().size(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getTotal(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
// Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
@ -446,6 +449,32 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertThat(lastSelectQuery, containsString(" like '%display value 9%'"));
}
@Test
public void testExpandNonPersistedValueSet() {
// Expand
ValueSet expansion = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), "http://hl7.org/fhir/ValueSet/administrative-gender");
assertThat(ValueSetTestUtil.toCodes(expansion), containsInAnyOrder("male", "female", "other", "unknown"));
assertEquals("ValueSet with URL \"ValueSet.url[http://hl7.org/fhir/ValueSet/administrative-gender]\" was expanded using an in-memory expansion", ValueSetTestUtil.extractExpansionMessage(expansion));
// Validate Code - Good
String codeSystemUrl = "http://hl7.org/fhir/administrative-gender";
String valueSetUrl = "http://hl7.org/fhir/ValueSet/administrative-gender";
String code = "male";
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/administrative-gender", outcome.getMessage());
// Validate Code - Bad
code = "AAA";
outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals("Unknown code 'http://hl7.org/fhir/administrative-gender#AAA' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/administrative-gender'", outcome.getMessage());
assertEquals("error", outcome.getSeverityCode());
}
@SuppressWarnings("SpellCheckingInspection")
@Test
public void testExpandTermValueSetAndChildren() throws Exception {
@ -557,8 +586,8 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
//Ensure they are streamed back in the same order.
List<String> firstExpansionCodes = reexpandedValueSet.getExpansion().getContains().stream().map(cn -> cn.getCode()).collect(Collectors.toList());
List<String> secondExpansionCodes = expandedValueSet.getExpansion().getContains().stream().map(cn -> cn.getCode()).collect(Collectors.toList());
List<String> firstExpansionCodes = ValueSetTestUtil.toCodes(reexpandedValueSet);
List<String> secondExpansionCodes = ValueSetTestUtil.toCodes(expandedValueSet);
assertThat(firstExpansionCodes, is(equalTo(secondExpansionCodes)));
//Ensure that internally the designations are expanded back in the same order.
@ -638,7 +667,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(23, expandedValueSet.getExpansion().getContains().size());
//It is enough to test that the sublist returned is the correct one.
assertThat(toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(0, 23))));
assertThat(ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(0, 23))));
}
@Test
@ -666,7 +695,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet);
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"));
assertThat(expandedValueSetString, containsString("ValueSet was expanded using an expansion that was pre-calculated"));
assertEquals(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal());
assertEquals(myDaoConfig.getPreExpandValueSetsDefaultOffset(), expandedValueSet.getExpansion().getOffset());
@ -781,7 +810,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(1000, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(codeSystem.getConcept().size() - expandedValueSet.getExpansion().getOffset(), expandedValueSet.getExpansion().getContains().size());
assertThat(toCodes(expandedValueSet), is(equalTo(expandedConcepts.subList(1, expandedConcepts.size()))));
assertThat(ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConcepts.subList(1, expandedConcepts.size()))));
}
@Test
@ -813,7 +842,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(1000, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(codeSystem.getConcept().size() - expandedValueSet.getExpansion().getOffset(), expandedValueSet.getExpansion().getContains().size());
assertThat(toCodes(expandedValueSet), is(equalTo(expandedConcepts.subList(1, expandedConcepts.size()))));
assertThat(ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConcepts.subList(1, expandedConcepts.size()))));
}
@Test
@ -849,21 +878,67 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(22, expandedValueSet.getExpansion().getContains().size());
//It is enough to test that the sublist returned is the correct one.
assertThat(toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(1, 23))));
assertThat(ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(1, 23))));
}
@Test
public void testExpandValueSetWithUnknownCodeSystem() {
// Direct expansion
ValueSet vs = new ValueSet();
ValueSet.ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem("http://unknown-system");
ValueSet outcome = myTermSvc.expandValueSet(new ValueSetExpansionOptions().setFailOnMissingCodeSystem(false), vs);
assertEquals(0, outcome.getExpansion().getContains().size());
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
ourLog.info(encoded);
vs.getCompose().addInclude().setSystem("http://unknown-system");
vs = myTermSvc.expandValueSet(new ValueSetExpansionOptions().setFailOnMissingCodeSystem(false), vs);
assertNotNull(vs);
assertEquals(0, vs.getExpansion().getContains().size());
// Store it
vs = new ValueSet();
vs.setId("ValueSet/vs-with-invalid-cs");
vs.setUrl("http://vs-with-invalid-cs");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.getCompose().addInclude().setSystem("http://unknown-system");
myValueSetDao.update(vs);
// In memory expansion
try {
myValueSetDao.expand(vs, new ValueSetExpansionOptions());
fail();
} catch (InternalErrorException e) {
assertEquals("Unable to expand ValueSet because CodeSystem could not be found: http://unknown-system", e.getMessage());
}
// Try validating a code against this VS - This code isn't in a system that's included by the VS, so we know
// conclusively that the code isn't valid for the VS even though we don't have the CS that actually is included
String codeSystemUrl = "http://invalid-cs";
String valueSetUrl = "http://vs-with-invalid-cs";
String code = "28571000087109";
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals("Unknown code 'http://invalid-cs#28571000087109' for in-memory expansion of ValueSet 'http://vs-with-invalid-cs'", outcome.getMessage());
assertEquals("error", outcome.getSeverityCode());
// Try validating a code that is in the missing CS that is imported by the VS
codeSystemUrl = "http://unknown-system";
outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals("Failed to expand ValueSet 'http://vs-with-invalid-cs' (in-memory). Could not validate code http://unknown-system#28571000087109. Error was: Unable to expand ValueSet because CodeSystem could not be found: http://unknown-system", outcome.getMessage());
assertEquals("error", outcome.getSeverityCode());
// Perform Pre-Expansion
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Make sure it's done
runInTransaction(() -> assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://snomed.info/sct")));
runInTransaction(() -> assertEquals(TermValueSetPreExpansionStatusEnum.FAILED_TO_EXPAND, myTermValueSetDao.findByUrl("http://vs-with-invalid-cs").orElseThrow(()->new IllegalStateException()).getExpansionStatus()));
// Try expansion again
try {
myValueSetDao.expand(vs, new ValueSetExpansionOptions());
fail();
} catch (InternalErrorException e) {
assertEquals("Unable to expand ValueSet because CodeSystem could not be found: http://unknown-system", e.getMessage());
}
Extension extensionByUrl = outcome.getMeta().getExtensionByUrl(HapiExtensions.EXT_VALUESET_EXPANSION_MESSAGE);
assertEquals("Unknown CodeSystem URI \"http://unknown-system\" referenced from ValueSet", extensionByUrl.getValueAsPrimitive().getValueAsString());
}
@Test
@ -899,7 +974,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(22, expandedValueSet.getExpansion().getContains().size());
//It is enough to test that the sublist returned is the correct one.
assertThat(toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(1, 23))));
assertThat(ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(1, 23))));
}
@Test
@ -947,7 +1022,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
include.setSystem(CS_URL);
myTermSvc.expandValueSet(null, vs, myValueSetCodeAccumulator);
verify(myValueSetCodeAccumulator, times(9)).includeConceptWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection(), nullable(Long.class), nullable(String.class));
verify(myValueSetCodeAccumulator, times(9)).includeConceptWithDesignations(anyString(), anyString(), nullable(String.class), anyCollection(), nullable(Long.class), nullable(String.class), nullable(String.class));
}
@Test
@ -981,7 +1056,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Non Pre-Expanded
ValueSet outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
assertEquals("ValueSet \"ValueSet.url[http://vs]\" has not yet been pre-expanded. Performing in-memory expansion without parameters. Current status: NOT_EXPANDED | The ValueSet is waiting to be picked up and pre-expanded by a scheduled task.", outcome.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE));
assertThat(toCodes(outcome).toString(), toCodes(outcome), contains(
assertThat(ValueSetTestUtil.toCodes(outcome).toString(), ValueSetTestUtil.toCodes(outcome), contains(
"code5", "code4", "code3", "code2", "code1"
));
@ -991,8 +1066,8 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
myCaptureQueriesListener.clear();
outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals("ValueSet was expanded using a pre-calculated expansion", outcome.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE));
assertThat(toCodes(outcome).toString(), toCodes(outcome), contains(
assertThat(outcome.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE), containsString("ValueSet was expanded using an expansion that was pre-calculated"));
assertThat(ValueSetTestUtil.toCodes(outcome).toString(), ValueSetTestUtil.toCodes(outcome), contains(
"code5", "code4", "code3", "code2", "code1"
));
@ -1474,9 +1549,469 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
});
}
@Nonnull
public static List<String> toCodes(ValueSet theExpandedValueSet) {
return theExpandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
@Test
public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferentVersionPresent() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myCodeSystemDao.update(cs);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("0.17"); // different version
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myValueSetDao.update(vs);
ConceptValidationOptions options = new ConceptValidationOptions();
options.setValidateDisplay(true);
String codeSystemUrl;
String code;
ValueSet expansion;
IdType vsId = new IdType("ValueSet/vaccinecode");
// Expand VS
expansion = myValueSetDao.expand(vsId, new ValueSetExpansionOptions(), mySrd);
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("Current status: NOT_EXPANDED"));
assertThat(ValueSetTestUtil.toCodes(expansion), contains("28571000087109"));
// Validate code - good
codeSystemUrl = "http://snomed.info/sct";
code = "28571000087109";
String display = null;
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("28571000087109", outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - good code, bad display
codeSystemUrl = "http://snomed.info/sct";
code = "28571000087109";
display = "BLAH";
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals(null, outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\" for in-memory expansion of ValueSet: http://ehealthontario.ca/fhir/ValueSet/vaccinecode", outcome.getMessage());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - good code, good display
codeSystemUrl = "http://snomed.info/sct";
code = "28571000087109";
display = "MODERNA COVID-19 mRNA-1273";
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("28571000087109", outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - bad code
codeSystemUrl = "http://snomed.info/sct";
code = "BLAH";
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals(null, outcome.getCode());
assertEquals(null, outcome.getDisplay());
assertEquals(null, outcome.getCodeSystemVersion());
// Calculate pre-expansions
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Validate code - good
codeSystemUrl = "http://snomed.info/sct";
code = "28571000087109";
display = null;
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("28571000087109", outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
assertThat(outcome.getMessage(), startsWith("Code validation occurred using a ValueSet expansion that was pre-calculated at "));
// Validate code - good code, bad display
codeSystemUrl = "http://snomed.info/sct";
code = "28571000087109";
display = "BLAH";
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals(null, outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
assertThat(outcome.getMessage(), containsString("Unable to validate code http://snomed.info/sct#28571000087109 - Concept Display \"BLAH\" does not match expected \"MODERNA COVID-19 mRNA-1273\". Code validation occurred using a ValueSet expansion that was pre-calculated at "));
// Validate code - good code, good display
codeSystemUrl = "http://snomed.info/sct";
code = "28571000087109";
display = "MODERNA COVID-19 mRNA-1273";
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("28571000087109", outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - bad code
codeSystemUrl = "http://snomed.info/sct";
code = "BLAH";
outcome = myValueSetDao.validateCode(null, vsId, new CodeType(code), new UriType(codeSystemUrl), new StringType(display), null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals(null, outcome.getCode());
assertEquals(null, outcome.getDisplay());
assertEquals(null, outcome.getCodeSystemVersion());
}
@Test
public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsIsFragmentWithWrongVersion() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://foo-cs");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myCodeSystemDao.update(cs, mySrd);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://foo-cs");
vsInclude.setVersion("0.17"); // different version
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myValueSetDao.update(vs, mySrd);
String codeSystemUrl;
String valueSetUrl;
String code;
// Make sure nothing is stored in the TRM DB yet
runInTransaction(() -> assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://snomed.info/sct")));
runInTransaction(() -> assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, myTermValueSetDao.findByUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode").get().getExpansionStatus()));
// In memory expansion
ValueSet expansion = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("has not yet been pre-expanded"));
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("Current status: NOT_EXPANDED"));
assertThat(ValueSetTestUtil.toCodes(expansion), contains("28571000087109"));
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals("Unknown code 'http://snomed.info/sct#28571000087109' for in-memory expansion of ValueSet 'http://ehealthontario.ca/fhir/ValueSet/vaccinecode'", outcome.getMessage());
assertEquals("error", outcome.getSeverityCode());
// Perform Pre-Expansion
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Make sure it's done
runInTransaction(() -> assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://snomed.info/sct")));
runInTransaction(() -> assertEquals(TermValueSetPreExpansionStatusEnum.EXPANDED, myTermValueSetDao.findByUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode").get().getExpansionStatus()));
// Try expansion again
expansion = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("ValueSet was expanded using an expansion that was pre-calculated"));
assertThat(ValueSetTestUtil.toCodes(expansion), contains("28571000087109"));
}
@Test
public void testExpandValueSet_VsIsNonEnumeratedWithVersionedSystem_CsIsFragmentWithWrongVersion() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://foo-cs");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myCodeSystemDao.update(cs, mySrd);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://foo-cs");
vsInclude.setVersion("0.17"); // different version
myValueSetDao.update(vs, mySrd);
String codeSystemUrl;
String valueSetUrl;
String code;
// Make sure nothing is stored in the TRM DB yet
runInTransaction(() -> assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://snomed.info/sct")));
runInTransaction(() -> assertEquals(TermValueSetPreExpansionStatusEnum.NOT_EXPANDED, myTermValueSetDao.findByUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode").get().getExpansionStatus()));
// In memory expansion
try {
myValueSetDao.expand(vs, new ValueSetExpansionOptions());
} catch (InternalErrorException e) {
assertEquals("Unable to expand ValueSet because CodeSystem could not be found: http://foo-cs|0.17", e.getMessage());
}
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
assertEquals("Unknown code 'http://snomed.info/sct#28571000087109' for in-memory expansion of ValueSet 'http://ehealthontario.ca/fhir/ValueSet/vaccinecode'", outcome.getMessage());
assertEquals("error", outcome.getSeverityCode());
// Perform Pre-Expansion
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Make sure it's done
runInTransaction(() -> assertNull(myTermCodeSystemDao.findByCodeSystemUri("http://snomed.info/sct")));
runInTransaction(() -> assertEquals(TermValueSetPreExpansionStatusEnum.FAILED_TO_EXPAND, myTermValueSetDao.findByUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode").get().getExpansionStatus()));
// Try expansion again
try {
myValueSetDao.expand(vs, new ValueSetExpansionOptions());
} catch (InternalErrorException e) {
assertEquals("Unable to expand ValueSet because CodeSystem could not be found: http://foo-cs|0.17", e.getMessage());
}
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsFragmentWithoutCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myCodeSystemDao.update(cs, mySrd);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myValueSetDao.update(vs, mySrd);
String codeSystemUrl;
String valueSetUrl;
String code;
ValueSet valueSet = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
assertEquals("28571000087109", valueSet.getExpansion().getContains().get(0).getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay());
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertTrue(outcome.isOk());
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsFragmentWithCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myCodeSystemDao.update(cs, mySrd);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myValueSetDao.update(vs, mySrd);
String codeSystemUrl;
String valueSetUrl;
String code;
IValidationSupport.CodeValidationResult outcome;
// Good code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
// Bad code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "123";
outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
ValueSet valueSet = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
ValueSet.ValueSetExpansionContainsComponent expansionCode = valueSet.getExpansion().getContains().get(0);
assertEquals("28571000087109", expansionCode.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", expansionCode.getDisplay());
assertEquals("http://snomed.info/sct/20611000087101/version/20210331", expansionCode.getVersion());
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
valueSet = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
expansionCode = valueSet.getExpansion().getContains().get(0);
assertEquals("28571000087109", expansionCode.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", expansionCode.getDisplay());
assertEquals("http://snomed.info/sct/20611000087101/version/20210331", expansionCode.getVersion());
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsCompleteWithCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myCodeSystemDao.update(cs, mySrd);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myValueSetDao.update(vs, mySrd);
String codeSystemUrl;
String valueSetUrl;
String code;
IValidationSupport.CodeValidationResult outcome;
// Good code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertTrue(outcome.isOk());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
// Bad code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "123";
outcome = myValueSetDao.validateCode(new CodeType(valueSetUrl), null, new CodeType(code), new CodeType(codeSystemUrl), null, null, null, mySrd);
assertFalse(outcome.isOk());
ValueSet valueSet = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
assertEquals("28571000087109", valueSet.getExpansion().getContains().get(0).getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay());
}
@Test
public void testRequestValueSetReExpansion() {
CodeSystem cs = new CodeSystem();
cs.setId("cs");
cs.setUrl("http://cs");
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.addConcept().setCode("A").setDisplay("Code A");
myCodeSystemDao.update(cs, mySrd);
ValueSet vs = new ValueSet();
vs.setId("vs");
vs.setUrl("http://vs");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
vs.getCompose().addInclude().setSystem("http://cs");
myValueSetDao.update(vs, mySrd);
// Perform pre-expansion
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Expand
ValueSet expansion = myValueSetDao.expand(new IdType("ValueSet/vs"), new ValueSetExpansionOptions(), mySrd);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("ValueSet was expanded using an expansion that was pre-calculated"));
assertThat(ValueSetTestUtil.toCodes(expansion), contains("A"));
// Change the CodeSystem
cs.getConcept().clear();
cs.addConcept().setCode("B").setDisplay("Code B");
myCodeSystemDao.update(cs, mySrd);
// Previous precalculated expansion should still hold
expansion = myValueSetDao.expand(new IdType("ValueSet/vs"), new ValueSetExpansionOptions(), mySrd);
assertThat(ValueSetTestUtil.toCodes(expansion), contains("A"));
// Invalidate the precalculated expansion
myTermSvc.invalidatePreCalculatedExpansion(new IdType("ValueSet/vs"), mySrd);
// Expand (should not use a precalculated expansion)
expansion = myValueSetDao.expand(new IdType("ValueSet/vs"), new ValueSetExpansionOptions(), mySrd);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("Performing in-memory expansion without parameters"));
assertThat(ValueSetTestUtil.toCodes(expansion), contains("B"));
// Perform pre-expansion
myTerminologyDeferredStorageSvc.saveAllDeferred();
myTermSvc.preExpandDeferredValueSetsToTerminologyTables();
// Expand (should use the new precalculated expansion)
expansion = myValueSetDao.expand(new IdType("ValueSet/vs"), new ValueSetExpansionOptions(), mySrd);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(ValueSetTestUtil.extractExpansionMessage(expansion), containsString("ValueSet was expanded using an expansion that was pre-calculated"));
assertThat(ValueSetTestUtil.toCodes(expansion), contains("B"));
// Validate code that is good
IValidationSupport.CodeValidationResult outcome = myValueSetDao.validateCode(vs.getUrlElement(), null, new StringType("B"), cs.getUrlElement(), null, null, null, mySrd);
assertEquals(true, outcome.isOk());
assertThat(outcome.getMessage(), containsString("Code validation occurred using a ValueSet expansion that was pre-calculated"));
// Validate code that is bad
outcome = myValueSetDao.validateCode(vs.getUrlElement(), null, new StringType("A"), cs.getUrlElement(), null, null, null, mySrd);
assertEquals(false, outcome.isOk());
assertThat(outcome.getMessage(), containsString("Code validation occurred using a ValueSet expansion that was pre-calculated"));
}

View File

@ -0,0 +1,25 @@
package ca.uhn.fhir.jpa.util;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.HapiExtensions.EXT_VALUESET_EXPANSION_MESSAGE;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ValueSetTestUtil {
public static String extractExpansionMessage(ValueSet outcome) {
List<Extension> extensions = outcome.getMeta().getExtensionsByUrl(EXT_VALUESET_EXPANSION_MESSAGE);
assertEquals(1, extensions.size());
String expansionMessage = extensions.get(0).getValueAsPrimitive().getValueAsString();
return expansionMessage;
}
@Nonnull
public static List<String> toCodes(ValueSet theExpandedValueSet) {
return theExpandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList());
}
}

View File

@ -5,6 +5,7 @@ import ca.uhn.fhir.cql.common.helper.PartitionHelper;
import ca.uhn.fhir.cql.common.provider.CqlProviderTestBase;
import ca.uhn.fhir.cql.config.CqlR4Config;
import ca.uhn.fhir.cql.config.TestCqlConfig;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
@ -13,6 +14,9 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.test.utilities.RequestDetailsHelper;
import org.apache.commons.io.FileUtils;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Meta;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger;
@ -45,7 +49,19 @@ public class BaseCqlR4Test extends BaseJpaR4Test implements CqlProviderTestBase
protected
FhirContext myFhirContext;
@Autowired
IFhirSystemDao mySystemDao;
IFhirSystemDao<Bundle, Meta> mySystemDao;
@Autowired
DaoConfig myDaoConfig;
@BeforeEach
public void beforeEach() {
myDaoConfig.setMaximumExpansionSize(5000);
}
@AfterEach
public void afterEach() {
myDaoConfig.setMaximumExpansionSize(new DaoConfig().getMaximumExpansionSize());
}
protected int loadDataFromDirectory(String theDirectoryName) throws IOException {
int count = 0;

View File

@ -40,10 +40,16 @@ public class RuntimeSearchParamCache extends ReadOnlySearchParamCache {
getSearchParamMap(theResourceName).put(theName, theSearchParam);
String uri = theSearchParam.getUri();
if (isNotBlank(uri)) {
if (myUrlToParam.containsKey(uri)) {
RuntimeSearchParam existingForUrl = myUrlToParam.get(uri);
if (existingForUrl == theSearchParam) {
// This is expected, since the same SP can span multiple resource types
// so it may get added more than once by this method
ourLog.trace("Search param was previously registered for url: {}", uri);
} else if (existingForUrl != null) {
ourLog.warn("Multiple search parameters have URL: {}", uri);
} else {
myUrlToParam.put(uri, theSearchParam);
}
myUrlToParam.put(uri, theSearchParam);
}
if (theSearchParam.getId() != null && theSearchParam.getId().hasIdPart()) {
String value = theSearchParam.getId().toUnqualifiedVersionless().getValue();

View File

@ -156,6 +156,11 @@ public class ProviderConstants {
*/
public static final String OPERATION_REINDEX = "$reindex";
/**
* Operation name for the $invalidate-expansion operation
*/
public static final String OPERATION_INVALIDATE_EXPANSION = "$invalidate-expansion";
/**
* url of resources to delete for the $delete-expunge operation
*/

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.provider;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -28,9 +29,11 @@ import ca.uhn.fhir.rest.api.server.storage.IReindexJobSubmitter;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable;
import java.math.BigDecimal;
@ -55,7 +58,7 @@ public class ReindexProvider {
@OperationParam(name = ProviderConstants.OPERATION_REINDEX_PARAM_EVERYTHING, typeName = "boolean", min = 0, max = 1) IPrimitiveType<Boolean> theEverything,
RequestDetails theRequestDetails
) {
Boolean everything = theEverything != null && theEverything.getValue();
boolean everything = theEverything != null && theEverything.getValue();
@Nullable Integer batchSize = myMultiUrlProcessor.getBatchSize(theBatchSize);
if (everything) {
return processEverything(batchSize, theRequestDetails);
@ -70,9 +73,9 @@ public class ReindexProvider {
private IBaseParameters processEverything(Integer theBatchSize, RequestDetails theRequestDetails) {
try {
JobExecution jobExecution = myReindexJobSubmitter.submitEverythingJob(theBatchSize, theRequestDetails);
IBaseParameters retval = ParametersUtil.newInstance(myFhirContext);
ParametersUtil.addParameterToParametersLong(myFhirContext, retval, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, jobExecution.getJobId());
return retval;
IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
ParametersUtil.addParameterToParametersLong(myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, jobExecution.getJobId());
return retVal;
} catch (JobParametersInvalidException e) {
throw new InvalidRequestException("Invalid job parameters: " + e.getMessage(), e);
}

View File

@ -66,6 +66,11 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
return myWrap.validateCode(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, theValueSetUrl);
}
@Override
public IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
return myWrap.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet);
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode, theDisplayLanguage);
@ -102,11 +107,6 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport {
return myWrap.generateSnapshot(theValidationSupportContext, theInput, theUrl, theWebUrl, theProfileName);
}
@Override
public IValidationSupport.CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theValidationOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
return myWrap.validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, theValueSet);
}
@Override
public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
return myWrap.translateConcept(theRequest);

View File

@ -90,6 +90,21 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
return loadFromCache(myCache, key, t -> super.fetchAllNonBaseStructureDefinitions());
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
return loadFromCache(myCache, "fetchCodeSystem " + theSystem, t -> super.fetchCodeSystem(theSystem));
}
@Override
public IBaseResource fetchValueSet(String theUri) {
return loadFromCache(myCache, "fetchValueSet " + theUri, t -> super.fetchValueSet(theUri));
}
@Override
public IBaseResource fetchStructureDefinition(String theUrl) {
return loadFromCache(myCache, "fetchStructureDefinition " + theUrl, t -> super.fetchStructureDefinition(theUrl));
}
@Override
public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri,

View File

@ -31,6 +31,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -59,8 +60,16 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) {
return expandValueSet(theValidationSupportContext, theExpansionOptions, theValueSetToExpand, null, null);
}
org.hl7.fhir.r5.model.ValueSet expansionR5 = expandValueSetToCanonical(theValidationSupportContext, theValueSetToExpand, null, null);
private ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, IBaseResource theValueSetToExpand, String theWantSystemAndVersion, String theWantCode) {
org.hl7.fhir.r5.model.ValueSet expansionR5;
try {
expansionR5 = expandValueSetToCanonical(theValidationSupportContext, theValueSetToExpand, theWantSystemAndVersion, theWantCode);
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
return new ValueSetExpansionOutcome(e.getMessage());
}
if (expansionR5 == null) {
return null;
}
@ -89,10 +98,10 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion());
}
return new ValueSetExpansionOutcome(expansion, null);
return new ValueSetExpansionOutcome(expansion);
}
private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) {
private org.hl7.fhir.r5.model.ValueSet expandValueSetToCanonical(ValidationSupportContext theValidationSupportContext, IBaseResource theValueSetToExpand, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
org.hl7.fhir.r5.model.ValueSet expansionR5;
switch (theValueSetToExpand.getStructureFhirVersionEnum()) {
case DSTU2: {
@ -112,7 +121,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
break;
}
case R5: {
expansionR5 = expandValueSetR5(theValidationSupportContext, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand);
expansionR5 = expandValueSetR5(theValidationSupportContext, (org.hl7.fhir.r5.model.ValueSet) theValueSetToExpand, theWantSystemUrlAndVersion, theWantCode);
break;
}
case DSTU2_1:
@ -124,17 +133,34 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
@Override
public CodeValidationResult
validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode);
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
org.hl7.fhir.r5.model.ValueSet expansion;
String vsUrl = CommonCodeSystemsTerminologyService.getValueSetUrl(theValueSet);
try {
expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode);
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
CodeValidationResult codeValidationResult = new CodeValidationResult();
codeValidationResult.setSeverityCode("error");
String msg = "Failed to expand ValueSet '" + vsUrl + "' (in-memory). Could not validate code " + theCodeSystemUrlAndVersion + "#" + theCode;
if (e.getMessage() != null) {
msg += ". Error was: " + e.getMessage();
}
codeValidationResult.setMessage(msg);
return codeValidationResult;
}
if (expansion == null) {
return null;
}
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion);
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion, vsUrl);
}
@Override
@Nullable
public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
IBaseResource vs;
if (isNotBlank(theValueSetUrl)) {
@ -198,18 +224,22 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
}
ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, null, vs);
ValueSetExpansionOutcome valueSetExpansionOutcome = expandValueSet(theValidationSupportContext, null, vs, theCodeSystem, theCode);
if (valueSetExpansionOutcome == null) {
return null;
}
if (valueSetExpansionOutcome.getError() != null) {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage(valueSetExpansionOutcome.getError());
}
IBaseResource expansion = valueSetExpansionOutcome.getValueSet();
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion);
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl);
}
private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersionToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion) {
private CodeValidationResult validateCodeInExpandedValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersionToValidate, String theCodeToValidate, String theDisplayToValidate, IBaseResource theExpansion, String theValueSetUrl) {
assert theExpansion != null;
boolean caseSensitive = true;
@ -290,13 +320,13 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
}
String codeSystemUrlToValidate=null;
String codeSystemVersionToValidate=null;
String codeSystemUrlToValidate = null;
String codeSystemVersionToValidate = null;
if (theCodeSystemUrlAndVersionToValidate != null) {
int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|");
if (versionIndex > -1) {
codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex);
codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex+1);
codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1);
} else {
codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate;
}
@ -311,19 +341,31 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
if (codeMatches) {
if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) {
String csVersion = codeSystemResourceVersion;
if (isNotBlank(nextExpansionCode.getSystemVersion())) {
csVersion = nextExpansionCode.getSystemVersion();
}
if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) {
return new CodeValidationResult()
CodeValidationResult codeValidationResult = new CodeValidationResult()
.setCode(theCodeToValidate)
.setDisplay(nextExpansionCode.getDisplay())
.setCodeSystemName(codeSystemResourceName)
.setCodeSystemVersion(codeSystemResourceVersion);
.setCodeSystemVersion(csVersion);
if (isNotBlank(theValueSetUrl)) {
codeValidationResult.setMessage("Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl);
}
return codeValidationResult;
} else {
String message = "Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"";
if (isNotBlank(theValueSetUrl)) {
message += " for in-memory expansion of ValueSet: " + theValueSetUrl;
}
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setDisplay(nextExpansionCode.getDisplay())
.setMessage("Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"")
.setMessage(message)
.setCodeSystemName(codeSystemResourceName)
.setCodeSystemVersion(codeSystemResourceVersion);
.setCodeSystemVersion(csVersion);
}
}
}
@ -338,6 +380,9 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
severity = ValidationMessage.IssueSeverity.ERROR;
message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'";
}
if (isNotBlank(theValueSetUrl)) {
message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'";
}
return new CodeValidationResult()
.setSeverityCode(severity.toCode())
@ -346,52 +391,27 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
return validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode);
CodeValidationResult codeValidationResult = validateCode(theValidationSupportContext, new ConceptValidationOptions(), theSystem, theCode, null, null);
if (codeValidationResult == null) {
return null;
}
return codeValidationResult.asLookupCodeResult(theSystem, theCode);
}
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) {
Function<String, CodeSystem> codeSystemLoader = t -> {
org.hl7.fhir.dstu2.model.ValueSet codeSystem = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
CodeSystem retVal = new CodeSystem();
addCodesDstu2Hl7Org(codeSystem.getCodeSystem().getConcept(), retVal.getConcept());
return retVal;
};
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> {
org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false));
};
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(theInput, new BaseAdvisor_10_50(false));
org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode);
return (output);
return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
}
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) {
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2(ValidationSupportContext theValidationSupportContext, ca.uhn.fhir.model.dstu2.resource.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser();
Function<String, CodeSystem> codeSystemLoader = t -> {
ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystem = theInput;
CodeSystem retVal = null;
if (codeSystem != null) {
retVal = new CodeSystem();
retVal.setUrl(codeSystem.getUrl());
addCodesDstu2(codeSystem.getCodeSystem().getConcept(), retVal.getConcept());
}
return retVal;
};
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> {
ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet));
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
};
org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(theInput));
org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode);
return (output);
return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
}
@Override
@ -437,55 +457,28 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) {
Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> {
org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
return (CodeSystem) VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false));
};
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> {
org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false));
};
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(theInput, new BaseAdvisor_30_50(false));
org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode);
return (output);
return (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
}
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) {
Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> {
org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
return (CodeSystem) VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false));
};
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> {
org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false));
};
private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
org.hl7.fhir.r5.model.ValueSet input = (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(theInput, new BaseAdvisor_40_50(false));
org.hl7.fhir.r5.model.ValueSet output = expandValueSetR5(theValidationSupportContext, input, codeSystemLoader, valueSetLoader, theWantSystemUrlAndVersion, theWantCode);
return (output);
return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode);
}
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) {
Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return expandValueSetR5(theValidationSupportContext, theInput, codeSystemLoader, valueSetLoader, null, null);
private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) throws ExpansionCouldNotBeCompletedInternallyException {
return expandValueSetR5(theValidationSupportContext, theInput, null, null);
}
@Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, Function<String, CodeSystem> theCodeSystemLoader, Function<String, org.hl7.fhir.r5.model.ValueSet> theValueSetLoader, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) {
private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
Set<FhirVersionIndependentConcept> concepts = new HashSet<>();
try {
expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode);
expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode);
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
return null;
}
expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode);
expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode);
org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet();
for (FhirVersionIndependentConcept next : concepts) {
@ -499,130 +492,308 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
return retVal;
}
private void expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Set<FhirVersionIndependentConcept> theConcepts, Function<String, CodeSystem> theCodeSystemLoader, Function<String, org.hl7.fhir.r5.model.ValueSet> theValueSetLoader, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
/**
* Use with caution - this is not a stable API
*
* @since 5.6.0
*/
public void expandValueSetIncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theIncludeOrExclude) throws ExpansionCouldNotBeCompletedInternallyException {
expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, null, null, theIncludeOrExclude);
}
private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Set<FhirVersionIndependentConcept> theConcepts, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, boolean theComposeListIsInclude, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
Consumer<FhirVersionIndependentConcept> consumer = c -> {
if (theComposeListIsInclude) {
theConcepts.add(c);
} else {
theConcepts.remove(c);
}
};
expandValueSetR5IncludeOrExcludes(theValidationSupportContext, consumer, theComposeList, theWantSystemUrlAndVersion, theWantCode);
}
private void expandValueSetR5IncludeOrExcludes(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, List<org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent> theComposeList, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
ExpansionCouldNotBeCompletedInternallyException caughtException = null;
for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) {
try {
boolean outcome = expandValueSetR5IncludeOrExclude(theValidationSupportContext, theConsumer, theWantSystemUrlAndVersion, theWantCode, nextInclude);
if (isNotBlank(theWantCode)) {
if (outcome) {
return;
}
}
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
if (isBlank(theWantCode)) {
throw e;
} else {
caughtException = e;
}
}
}
if (caughtException != null) {
throw caughtException;
}
}
/**
* Returns <code>true</code> if at least one code was addded
*/
private boolean expandValueSetR5IncludeOrExclude(ValidationSupportContext theValidationSupportContext, Consumer<FhirVersionIndependentConcept> theConsumer, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent theInclude) throws ExpansionCouldNotBeCompletedInternallyException {
String wantSystemUrl = null;
String wantSystemVersion = null;
if (theWantSystemUrlAndVersion != null) {
int versionIndex = theWantSystemUrlAndVersion.indexOf("|");
if (versionIndex > -1) {
wantSystemUrl = theWantSystemUrlAndVersion.substring(0,versionIndex);
wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex+1);
wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex);
wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1);
} else {
wantSystemUrl = theWantSystemUrlAndVersion;
}
}
for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent nextInclude : theComposeList) {
Function<String, CodeSystem> codeSystemLoader = newCodeSystemLoader(theValidationSupportContext);
Function<String, org.hl7.fhir.r5.model.ValueSet> valueSetLoader = newValueSetLoader(theValidationSupportContext);
List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>();
String includeOrExcludeConceptSystemUrl = nextInclude.getSystem();
String includeOrExcludeConceptSystemVersion = nextInclude.getVersion();
if (isNotBlank(includeOrExcludeConceptSystemUrl)) {
List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>();
String includeOrExcludeConceptSystemUrl = theInclude.getSystem();
String includeOrExcludeConceptSystemVersion = theInclude.getVersion();
CodeSystem includeOrExcludeSystemResource = null;
if (isNotBlank(includeOrExcludeConceptSystemUrl)) {
if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) {
continue;
}
if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) {
return false;
}
if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) {
continue;
}
if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) {
return false;
}
CodeSystem includeOrExcludeSystemResource;
if (includeOrExcludeConceptSystemVersion != null) {
includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion);
} else {
includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl);
}
String loadedCodeSystemUrl;
if (includeOrExcludeConceptSystemVersion != null) {
loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion;
} else {
loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl;
}
Set<String> wantCodes;
if (nextInclude.getConcept().isEmpty()) {
wantCodes = null;
} else {
wantCodes = nextInclude
.getConcept()
.stream().map(t -> t.getCode()).collect(Collectors.toSet());
}
includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl);
boolean ableToHandleCode = false;
if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) {
Set<String> wantCodes;
if (theInclude.getConcept().isEmpty()) {
wantCodes = null;
} else {
wantCodes = theInclude
.getConcept()
.stream().map(t -> t.getCode()).collect(Collectors.toSet());
}
if (theWantCode != null) {
if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) {
LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode, null);
if (lookup != null && lookup.isFound()) {
boolean ableToHandleCode = false;
String failureMessage = null;
FailureType failureType = FailureType.OTHER;
if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) {
if (theWantCode != null) {
if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) {
LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode, null);
if (lookup != null && lookup.isFound()) {
CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
.addConcept()
.setCode(theWantCode)
.setDisplay(lookup.getCodeDisplay());
List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes);
ableToHandleCode = true;
}
} else {
/*
* If we're doing an expansion specifically looking for a single code, that means we're validating that code.
* In the case where we have a ValueSet that explicitly enumerates a collection of codes
* (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid
* even if we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for
* CodeSystems to always be known, but realistically there are always going to be CodeSystems that
* can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to
* enumerate a set of good codes for them is a nice compromise there.
*/
if (Objects.equals(theInclude.getSystem(), theWantSystemUrlAndVersion)) {
Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = theInclude.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst();
// If the ValueSet.compose.include has no individual concepts in it, and
// we can't find the actual referenced CodeSystem, we have no choice
// but to fail
if (!theInclude.getConcept().isEmpty()) {
ableToHandleCode = true;
} else {
failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl);
}
if (matchingEnumeratedConcept.isPresent()) {
CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
.addConcept()
.setCode(theWantCode)
.setDisplay(lookup.getCodeDisplay());
.setDisplay(matchingEnumeratedConcept.get().getDisplay());
List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes);
ableToHandleCode = true;
}
} else if (theComposeListIsInclude) {
/*
* If we're doing an expansion specifically looking for a single code, that means we're validating that code.
* In the case where we have a ValueSet that explicitly enumerates a collection of codes
* (via ValueSet.compose.include.code) in a code system that is unknown we'll assume the code is valid
* even iof we can't find the CodeSystem. This is a compromise obviously, since it would be ideal for
* CodeSystems to always be known, but realistically there are always going to be CodeSystems that
* can't be supplied because of copyright issues, or because they are grammar based. Allowing a VS to
* enumerate a set of good codes for them is a nice compromise there.
*/
for (org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent next : theComposeList) {
if (Objects.equals(next.getSystem(), theWantSystemUrlAndVersion)) {
Optional<org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent> matchingEnumeratedConcept = next.getConcept().stream().filter(t -> Objects.equals(t.getCode(), theWantCode)).findFirst();
if (matchingEnumeratedConcept.isPresent()) {
CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
.addConcept()
.setCode(theWantCode)
.setDisplay(matchingEnumeratedConcept.get().getDisplay());
List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes);
ableToHandleCode = true;
break;
}
}
}
}
}
}
} else {
ableToHandleCode = true;
}
if (!ableToHandleCode) {
throw new ExpansionCouldNotBeCompletedInternallyException();
}
if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) {
addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes);
}
}
for (CanonicalType nextValueSetInclude : nextInclude.getValueSet()) {
org.hl7.fhir.r5.model.ValueSet vs = theValueSetLoader.apply(nextValueSetInclude.getValueAsString());
if (vs != null) {
org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theCodeSystemLoader, theValueSetLoader, theWantSystemUrlAndVersion, theWantCode);
if (subExpansion == null) {
throw new ExpansionCouldNotBeCompletedInternallyException();
if (isNotBlank(theInclude.getSystem()) && !theInclude.getConcept().isEmpty() && theInclude.getFilter().isEmpty() && theInclude.getValueSet().isEmpty()) {
theInclude
.getConcept()
.stream()
.map(t -> new FhirVersionIndependentConcept(theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion()))
.forEach(t -> nextCodeList.add(t));
ableToHandleCode = true;
}
for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) {
nextCodeList.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
if (!ableToHandleCode) {
failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl);
}
}
}
if (theComposeListIsInclude) {
theConcepts.addAll(nextCodeList);
} else {
theConcepts.removeAll(nextCodeList);
ableToHandleCode = true;
}
if (!ableToHandleCode) {
if (includeOrExcludeSystemResource == null && failureMessage == null) {
failureMessage = getFailureMessageForMissingOrUnusableCodeSystem(includeOrExcludeSystemResource, loadedCodeSystemUrl);
}
if (includeOrExcludeSystemResource == null) {
failureType = FailureType.UNKNOWN_CODE_SYSTEM;
}
throw new ExpansionCouldNotBeCompletedInternallyException(failureMessage, failureType);
}
if (includeOrExcludeSystemResource != null && includeOrExcludeSystemResource.getContent() != CodeSystem.CodeSystemContentMode.NOTPRESENT) {
addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, includeOrExcludeSystemResource.getConcept(), nextCodeList, wantCodes);
}
}
for (CanonicalType nextValueSetInclude : theInclude.getValueSet()) {
org.hl7.fhir.r5.model.ValueSet vs = valueSetLoader.apply(nextValueSetInclude.getValueAsString());
if (vs != null) {
org.hl7.fhir.r5.model.ValueSet subExpansion = expandValueSetR5(theValidationSupportContext, vs, theWantSystemUrlAndVersion, theWantCode);
if (subExpansion == null) {
throw new ExpansionCouldNotBeCompletedInternallyException("Failed to expand ValueSet: " + nextValueSetInclude.getValueAsString(), FailureType.OTHER);
}
for (org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent next : subExpansion.getExpansion().getContains()) {
nextCodeList.add(new FhirVersionIndependentConcept(next.getSystem(), next.getCode(), next.getDisplay(), next.getVersion()));
}
}
}
boolean retVal = false;
for (FhirVersionIndependentConcept next : nextCodeList) {
if (includeOrExcludeSystemResource != null && theWantCode != null) {
boolean matches;
if (includeOrExcludeSystemResource.getCaseSensitive()) {
matches = theWantCode.equals(next.getCode());
} else {
matches = theWantCode.equalsIgnoreCase(next.getCode());
}
if (!matches) {
continue;
}
}
theConsumer.accept(next);
retVal = true;
}
return retVal;
}
private Function<String, org.hl7.fhir.r5.model.ValueSet> newValueSetLoader(ValidationSupportContext theValidationSupportContext) {
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG:
return t -> {
IBaseResource vs = theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
if (vs instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) {
IParser parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
IParser parserHapi = FhirContext.forDstu2Cached().newJsonParser();
ca.uhn.fhir.model.dstu2.resource.ValueSet valueSet = (ca.uhn.fhir.model.dstu2.resource.ValueSet) vs;
org.hl7.fhir.dstu2.model.ValueSet valueSetRi = parserRi.parseResource(org.hl7.fhir.dstu2.model.ValueSet.class, parserHapi.encodeResourceToString(valueSet));
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSetRi, new BaseAdvisor_10_50(false));
} else {
org.hl7.fhir.dstu2.model.ValueSet valueSet = (org.hl7.fhir.dstu2.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_10_50.convertResource(valueSet, new BaseAdvisor_10_50(false));
}
};
case DSTU3:
return t -> {
org.hl7.fhir.dstu3.model.ValueSet valueSet = (org.hl7.fhir.dstu3.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_30_50.convertResource(valueSet, new BaseAdvisor_30_50(false));
};
case R4:
return t -> {
org.hl7.fhir.r4.model.ValueSet valueSet = (org.hl7.fhir.r4.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
return (org.hl7.fhir.r5.model.ValueSet) VersionConvertorFactory_40_50.convertResource(valueSet, new BaseAdvisor_40_50(false));
};
default:
case DSTU2_1:
case R5:
return t -> (org.hl7.fhir.r5.model.ValueSet) theValidationSupportContext.getRootValidationSupport().fetchValueSet(t);
}
}
private Function<String, CodeSystem> newCodeSystemLoader(ValidationSupportContext theValidationSupportContext) {
switch (myCtx.getVersion().getVersion()) {
case DSTU2:
case DSTU2_HL7ORG:
return t -> {
IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
CodeSystem retVal = null;
if (codeSystem != null) {
retVal = new CodeSystem();
if (codeSystem instanceof ca.uhn.fhir.model.dstu2.resource.ValueSet) {
ca.uhn.fhir.model.dstu2.resource.ValueSet codeSystemCasted = (ca.uhn.fhir.model.dstu2.resource.ValueSet) codeSystem;
retVal.setUrl(codeSystemCasted.getUrl());
addCodesDstu2(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept());
} else {
org.hl7.fhir.dstu2.model.ValueSet codeSystemCasted = (org.hl7.fhir.dstu2.model.ValueSet) codeSystem;
retVal.setUrl(codeSystemCasted.getUrl());
addCodesDstu2Hl7Org(codeSystemCasted.getCodeSystem().getConcept(), retVal.getConcept());
}
}
return retVal;
};
case DSTU3:
return t -> {
org.hl7.fhir.dstu3.model.CodeSystem codeSystem = (org.hl7.fhir.dstu3.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
return (CodeSystem) VersionConvertorFactory_30_50.convertResource(codeSystem, new BaseAdvisor_30_50(false));
};
case R4:
return t -> {
org.hl7.fhir.r4.model.CodeSystem codeSystem = (org.hl7.fhir.r4.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
return (CodeSystem) VersionConvertorFactory_40_50.convertResource(codeSystem, new BaseAdvisor_40_50(false));
};
case DSTU2_1:
case R5:
default:
return t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t);
}
}
private String getFailureMessageForMissingOrUnusableCodeSystem(CodeSystem includeOrExcludeSystemResource, String loadedCodeSystemUrl) {
String failureMessage;
if (includeOrExcludeSystemResource == null) {
failureMessage = "Unable to expand ValueSet because CodeSystem could not be found: " + loadedCodeSystemUrl;
} else {
assert includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT;
failureMessage = "Unable to expand ValueSet because CodeSystem has CodeSystem.content=not-present but contents were not found: " + loadedCodeSystemUrl;
}
return failureMessage;
}
private void addCodes(String theCodeSystemUrl, String theCodeSystemVersion, List<CodeSystem.ConceptDefinitionComponent> theSource, List<FhirVersionIndependentConcept> theTarget, Set<String> theCodeFilter) {
@ -636,8 +807,27 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
}
}
private static class ExpansionCouldNotBeCompletedInternallyException extends Exception {
public enum FailureType {
UNKNOWN_CODE_SYSTEM,
OTHER
}
public static class ExpansionCouldNotBeCompletedInternallyException extends Exception {
private static final long serialVersionUID = -2226561628771483085L;
private final FailureType myFailureType;
public ExpansionCouldNotBeCompletedInternallyException(String theMessage, FailureType theFailureType) {
super(theMessage);
myFailureType = theFailureType;
}
public FailureType getFailureType() {
return myFailureType;
}
}
private static void flattenAndConvertCodesDstu2(List<org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent> theInput, List<FhirVersionIndependentConcept> theFhirVersionIndependentConcepts) {

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import org.apache.commons.compress.utils.Sets;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -14,10 +15,13 @@ import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nonnull;
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.Optional;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -75,22 +79,39 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* </p>
*/
public void addCodeSystem(IBaseResource theCodeSystem) {
String url = processResourceAndReturnUrl(theCodeSystem, "CodeSystem");
addToMap(theCodeSystem, myCodeSystems, url);
Set<String> urls = processResourceAndReturnUrls(theCodeSystem, "CodeSystem");
addToMap(theCodeSystem, myCodeSystems, urls);
}
private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) {
Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null");
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theCodeSystem);
private Set<String> processResourceAndReturnUrls(IBaseResource theResource, String theResourceName) {
Validate.notNull(theResource, "the" + theResourceName + " must not be null");
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResource);
String actualResourceName = resourceDef.getName();
Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName);
Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theCodeSystem);
Optional<IBase> urlValue = resourceDef.getChildByName("url").getAccessor().getFirstValueOrNull(theResource);
String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null);
Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null");
Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value");
return url;
String urlWithoutVersion;
int pipeIdx = url.indexOf('|');
if (pipeIdx != -1) {
urlWithoutVersion = url.substring(0, pipeIdx);
} else {
urlWithoutVersion = url;
}
HashSet<String> retVal = Sets.newHashSet(url, urlWithoutVersion);
Optional<IBase> versionValue = resourceDef.getChildByName("version").getAccessor().getFirstValueOrNull(theResource);
String version = versionValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null);
if (isNotBlank(version)) {
retVal.add(urlWithoutVersion + "|" + version);
}
return retVal;
}
/**
@ -108,23 +129,24 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* </p>
*/
public void addStructureDefinition(IBaseResource theStructureDefinition) {
String url = processResourceAndReturnUrl(theStructureDefinition, "StructureDefinition");
Set<String> url = processResourceAndReturnUrls(theStructureDefinition, "StructureDefinition");
addToMap(theStructureDefinition, myStructureDefinitions, url);
}
private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, String theUrl) {
if (isNotBlank(theUrl)) {
theMap.put(theUrl, theResource);
private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, Collection<String> theUrls) {
for (String urls : theUrls) {
if (isNotBlank(urls)) {
theMap.put(urls, theResource);
int lastSlashIdx = theUrl.lastIndexOf('/');
if (lastSlashIdx != -1) {
theMap.put(theUrl.substring(lastSlashIdx + 1), theResource);
int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1);
if (previousSlashIdx != -1) {
theMap.put(theUrl.substring(previousSlashIdx + 1), theResource);
int lastSlashIdx = urls.lastIndexOf('/');
if (lastSlashIdx != -1) {
theMap.put(urls.substring(lastSlashIdx + 1), theResource);
int previousSlashIdx = urls.lastIndexOf('/', lastSlashIdx - 1);
if (previousSlashIdx != -1) {
theMap.put(urls.substring(previousSlashIdx + 1), theResource);
}
}
}
}
}
@ -143,8 +165,8 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* </p>
*/
public void addValueSet(IBaseResource theValueSet) {
String url = processResourceAndReturnUrl(theValueSet, "ValueSet");
addToMap(theValueSet, myValueSets, url);
Set<String> urls = processResourceAndReturnUrls(theValueSet, "ValueSet");
addToMap(theValueSet, myValueSets, urls);
}

View File

@ -37,19 +37,21 @@ public class UnknownCodeSystemWarningValidationSupport extends BaseValidationSup
@Nullable
@Override
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
if (!myAllowNonExistentCodeSystem) {
return null;
}
if (theCodeSystem == null) {
return null;
}
IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem);
if (codeSystem != null) {
return null;
}
String message = "Unknown code system: " + theCodeSystem;
if (!myAllowNonExistentCodeSystem) {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage(message);
}
throw new TerminologyServiceException(message);
return new CodeValidationResult()
.setCode(theCode)
.setSeverity(IssueSeverity.INFORMATION)
.setMessage("Code " + theCodeSystem + "#" + theCode + " was not checked because the CodeSystem is not available");
}
public void setAllowNonExistentCodeSystem(boolean theAllowNonExistentCodeSystem) {

View File

@ -5,38 +5,74 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class InMemoryTerminologyServerValidationSupportTest {
public class InMemoryTerminologyServerValidationSupportTest {
private static final Logger ourLog = LoggerFactory.getLogger(InMemoryTerminologyServerValidationSupportTest.class);
private InMemoryTerminologyServerValidationSupport mySvc;
private FhirContext myCtx = FhirContext.forR4();
private DefaultProfileValidationSupport myDefaultSupport;
private ValidationSupportChain myChain;
private PrePopulatedValidationSupport myPrePopulated;
private CommonCodeSystemsTerminologyService myCommonCodeSystemsTermSvc;
@BeforeEach
public void before( ){
public void before() {
mySvc = new InMemoryTerminologyServerValidationSupport(myCtx);
myDefaultSupport = new DefaultProfileValidationSupport(myCtx);
myPrePopulated = new PrePopulatedValidationSupport(myCtx);
myChain = new ValidationSupportChain(mySvc,myPrePopulated, myDefaultSupport);
myCommonCodeSystemsTermSvc = new CommonCodeSystemsTerminologyService(myCtx);
myChain = new ValidationSupportChain(mySvc, myPrePopulated, myDefaultSupport, myCommonCodeSystemsTermSvc);
// Force load
myDefaultSupport.fetchCodeSystem("http://foo");
}
@Test
public void testValidateCodeInUnknownCodeSystemWithEnumeratedValueSet() {
public void testValidateCodeWithInferredSystem_CommonCodeSystemsCs_BuiltInVs() {
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions().setInferSystem(true);
IValidationSupport.CodeValidationResult outcome;
String valueSetUrl = "http://hl7.org/fhir/ValueSet/mimetypes";
// ValidateCode
outcome = myChain.validateCode(valCtx, options, null, "txt", null, valueSetUrl);
assertTrue(outcome.isOk());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/mimetypes", outcome.getMessage());
assertEquals("txt", outcome.getCode());
// ValidateCodeInValueSet
IBaseResource valueSet = myChain.fetchValueSet(valueSetUrl);
assertNotNull(valueSet);
outcome = myChain.validateCodeInValueSet(valCtx, options, null, "txt", null, valueSet);
assertTrue(outcome.isOk());
assertEquals("Code was validated against in-memory expansion of ValueSet: http://hl7.org/fhir/ValueSet/mimetypes", outcome.getMessage());
assertEquals("txt", outcome.getCode());
}
@Test
public void testValidateCode_UnknownCodeSystem_EnumeratedValueSet() {
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
vs
@ -49,12 +85,75 @@ class InMemoryTerminologyServerValidationSupportTest {
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
IValidationSupport.CodeValidationResult outcome;
IValidationSupport.CodeValidationResult outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code1", null, vs);
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code1", null, vs);
assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage());
assertTrue(outcome.isOk());
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs);
assertNull(outcome);
assertNotNull(outcome);
assertFalse(outcome.isOk());
assertEquals("Unknown code 'http://cs#code99' for in-memory expansion of ValueSet 'http://vs'", outcome.getMessage());
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
}
@Test
public void testValidateCode_UnknownCodeSystem_EnumeratedValueSet_MultipleIncludes() {
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
vs
.getCompose()
.addInclude()
.setSystem("http://cs")
.addFilter()
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("blah");
vs
.getCompose()
.addInclude()
.setSystem("http://cs")
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code1")))
.addConcept(new ValueSet.ConceptReferenceComponent(new CodeType("code2")));
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
IValidationSupport.CodeValidationResult outcome;
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code1", null, vs);
assertEquals("Code was validated against in-memory expansion of ValueSet: http://vs", outcome.getMessage());
assertTrue(outcome.isOk());
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs);
assertNotNull(outcome);
assertFalse(outcome.isOk());
assertEquals("Failed to expand ValueSet 'http://vs' (in-memory). Could not validate code http://cs#code99. Error was: Unable to expand ValueSet because CodeSystem could not be found: http://cs", outcome.getMessage());
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
}
@Test
public void testValidateCode_UnknownCodeSystem_NonEnumeratedValueSet() {
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
vs
.getCompose()
.addInclude()
.setSystem("http://cs");
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
IValidationSupport.CodeValidationResult outcome;
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs);
assertNotNull(outcome);
assertFalse(outcome.isOk());
assertEquals("Failed to expand ValueSet 'http://vs' (in-memory). Could not validate code http://cs#code99. Error was: Unable to expand ValueSet because CodeSystem could not be found: http://cs", outcome.getMessage());
assertEquals(IValidationSupport.IssueSeverity.ERROR, outcome.getSeverity());
}
@ -88,6 +187,242 @@ class InMemoryTerminologyServerValidationSupportTest {
}
@Test
public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferentVersionPresent() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addCodeSystem(cs);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("0.17"); // different version
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
options.setValidateDisplay(true);
String codeSystemUrl;
String valueSetUrl;
String code;
IValidationSupport.ValueSetExpansionOutcome expansion = mySvc.expandValueSet(valCtx, new ValueSetExpansionOptions(), vs);
assertNotNull(expansion.getValueSet());
assertEquals(1, ((ValueSet)expansion.getValueSet()).getExpansion().getContains().size());
// Validate code - good
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
String display = null;
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, display, valueSetUrl);
assertTrue(outcome.isOk());
assertEquals("28571000087109", outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - good code, bad display
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
display = "BLAH";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, display, valueSetUrl);
assertFalse(outcome.isOk());
assertEquals(null, outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - good code, good display
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
display = "MODERNA COVID-19 mRNA-1273";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, display, valueSetUrl);
assertTrue(outcome.isOk());
assertEquals("28571000087109", outcome.getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
assertEquals("0.17", outcome.getCodeSystemVersion());
// Validate code - bad code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "BLAH";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, null, valueSetUrl);
assertFalse(outcome.isOk());
assertEquals(null, outcome.getCode());
assertEquals(null, outcome.getDisplay());
assertEquals(null, outcome.getCodeSystemVersion());
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsFragmentWithoutCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addCodeSystem(cs);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("0.1.17");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
String codeSystemUrl;
String valueSetUrl;
String code;
IValidationSupport.ValueSetExpansionOutcome expansion = mySvc.expandValueSet(valCtx, new ValueSetExpansionOptions(), vs);
assertNull(expansion.getError());
ValueSet valueSet = (ValueSet) expansion.getValueSet();
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
assertEquals("28571000087109", valueSet.getExpansion().getContains().get(0).getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay());
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, null, valueSetUrl);
assertTrue(outcome.isOk());
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsFragmentWithCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.FRAGMENT);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addCodeSystem(cs);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
String codeSystemUrl;
String valueSetUrl;
String code;
IValidationSupport.CodeValidationResult outcome;
// Good code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, null, valueSetUrl);
assertTrue(outcome.isOk());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
// Bad code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "123";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, null, valueSetUrl);
assertFalse(outcome.isOk());
IValidationSupport.ValueSetExpansionOutcome expansion = mySvc.expandValueSet(valCtx, new ValueSetExpansionOptions(), vs);
assertNull(expansion.getError());
ValueSet valueSet = (ValueSet) expansion.getValueSet();
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
assertEquals("28571000087109", valueSet.getExpansion().getContains().get(0).getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay());
}
@Test
public void testExpandValueSet_VsUsesVersionedSystem_CsIsCompleteWithCode() {
CodeSystem cs = new CodeSystem();
cs.setId("snomed-ct-ca-imm");
cs.setStatus(Enumerations.PublicationStatus.ACTIVE);
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
cs.setUrl("http://snomed.info/sct");
cs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
cs.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addCodeSystem(cs);
ValueSet vs = new ValueSet();
vs.setId("vaccinecode");
vs.setUrl("http://ehealthontario.ca/fhir/ValueSet/vaccinecode");
vs.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vs.setStatus(Enumerations.PublicationStatus.ACTIVE);
ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude();
vsInclude.setSystem("http://snomed.info/sct");
vsInclude.setVersion("http://snomed.info/sct/20611000087101/version/20210331");
vsInclude.addConcept().setCode("28571000087109").setDisplay("MODERNA COVID-19 mRNA-1273");
myPrePopulated.addValueSet(vs);
ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions();
String codeSystemUrl;
String valueSetUrl;
String code;
IValidationSupport.CodeValidationResult outcome;
// Good code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "28571000087109";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, null, valueSetUrl);
assertTrue(outcome.isOk());
assertEquals("MODERNA COVID-19 mRNA-1273", outcome.getDisplay());
// Bad code
codeSystemUrl = "http://snomed.info/sct";
valueSetUrl = "http://ehealthontario.ca/fhir/ValueSet/vaccinecode";
code = "123";
outcome = mySvc.validateCode(valCtx, options, codeSystemUrl, code, null, valueSetUrl);
assertFalse(outcome.isOk());
IValidationSupport.ValueSetExpansionOutcome expansion = mySvc.expandValueSet(valCtx, new ValueSetExpansionOptions(), vs);
ValueSet valueSet = (ValueSet) expansion.getValueSet();
assertNotNull(valueSet);
assertEquals(1, valueSet.getExpansion().getContains().size());
assertEquals("28571000087109", valueSet.getExpansion().getContains().get(0).getCode());
assertEquals("MODERNA COVID-19 mRNA-1273", valueSet.getExpansion().getContains().get(0).getDisplay());
}
private static class PrePopulatedValidationSupportDstu2 extends PrePopulatedValidationSupport {
private final Map<String, IBaseResource> myDstu2ValueSets;

View File

@ -2,17 +2,18 @@ package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.util.TestUtil;
import org.junit.jupiter.api.AfterAll;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.CodeSystem;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class DefaultProfileValidationSupportTest {
private static FhirContext ourCtx = FhirContext.forR4Cached();
private DefaultProfileValidationSupport mySvc = new DefaultProfileValidationSupport(ourCtx);
private static FhirContext ourCtx = FhirContext.forDstu3();
@Test
public void testGetStructureDefinitionsWithRelativeUrls() {
@ -26,10 +27,17 @@ public class DefaultProfileValidationSupportTest {
}
@AfterAll
public static void afterClassClearContext() {
TestUtil.randomizeLocaleAndTimezone();
}
@Test
public void testLoadCodeSystemWithVersion() {
CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-0291");
assertNotNull(cs);
String version = cs.getVersion();
assertEquals("2.9", version);
cs = (CodeSystem) mySvc.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-0291|" + version);
assertNotNull(cs);
cs = (CodeSystem) mySvc.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-0291|999");
assertNotNull(cs);
}
}

View File

@ -777,7 +777,7 @@ public class FhirInstanceValidatorDstu3Test {
Patient resource = loadResource("/dstu3/nl/nl-core-patient-01.json", Patient.class);
ValidationResult results = myVal.validateWithResult(resource);
List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results);
assertThat(outcome.toString(), containsString("The Coding provided (urn:oid:2.16.840.1.113883.2.4.4.16.34#6030) is not in the value set http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.1--20171231000000"));
assertThat(outcome.toString(), containsString("Could not confirm that the codes provided are in the value set http://decor.nictiz.nl/fhir/ValueSet/2.16.840.1.113883.2.4.3.11.60.40.2.20.5.1--20171231000000"));
}
private void loadNL() throws IOException {
@ -1180,7 +1180,7 @@ public class FhirInstanceValidatorDstu3Test {
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
assertEquals(
"The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status), and a code is required from this value set) (error message = Unknown code 'notvalidcode')",
"The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status), and a code is required from this value set) (error message = Unknown code 'notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')",
output.getMessages().get(0).getMessage());
}

View File

@ -1200,7 +1200,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output);
assertEquals(
"The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status|4.0.1 (http://hl7.org/fhir/ValueSet/observation-status), and a code is required from this value set) (error message = Unknown code 'notvalidcode')",
"The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status|4.0.1 (http://hl7.org/fhir/ValueSet/observation-status), and a code is required from this value set) (error message = Unknown code 'notvalidcode' for in-memory expansion of ValueSet 'http://hl7.org/fhir/ValueSet/observation-status')",
output.getMessages().get(0).getMessage());
}

View File

@ -363,7 +363,7 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(encoded);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(1, errors.size());
assertEquals(1, errors.size(), ()->errors.toString());
assertEquals("The value '%%%2@()()' is not a valid Base64 value", errors.get(0).getMessage());
}
@ -383,7 +383,7 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(encoded);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(0, errors.size());
assertEquals(0, errors.size(), ()->errors.toString());
}
@ -669,8 +669,8 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output);
assertEquals(2, res.size(), output.toString());
assertEquals("A code with no system has no defined meaning. A system should be provided", output.getMessages().get(1).getMessage());
assertEquals(1, res.size(), output.toString());
assertEquals("A code with no system has no defined meaning. A system should be provided", res.get(0).getMessage());
}
@Test

View File

@ -67,7 +67,7 @@ public class QuestionnaireResponseValidatorR5Test {
private static final String CODE_ICC_SCHOOLTYPE_PT = "PT";
private static final String ID_VS_SCHOOLTYPE = "ValueSet/schooltype";
private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/schooltype";
private static FhirContext ourCtx = FhirContext.forR5();
private static final FhirContext ourCtx = FhirContext.forR5Cached();
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx);
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
@ -184,7 +184,7 @@ public class QuestionnaireResponseValidatorR5Test {
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat("index[" + i + "]: " + errors.toString(), errors.getMessages(), empty());
assertThat("index[" + i + "]: " + errors, errors.getMessages(), empty());
}
}
@ -715,7 +715,6 @@ public class QuestionnaireResponseValidatorR5Test {
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.randomizeLocaleAndTimezone();
}