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; 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 { public class DefaultProfileValidationSupport implements IValidationSupport {
private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/"; 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" // System can take the form "http://url|version"
String system = theSystem; String system = theSystem;
if (system.contains("|")) { String version = null;
String version = system.substring(system.indexOf('|') + 1); int pipeIdx = system.indexOf('|');
if (version.matches("^[0-9.]+$")) { if (pipeIdx > 0) {
system = system.substring(0, system.indexOf('|')); 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 candidate;
return codeSystems.get(system);
} else {
return valueSets.get(system);
}
} }
} }
@ -205,8 +224,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
} }
Map<String, IBaseResource> structureDefinitionMap = provideStructureDefinitionMap(); Map<String, IBaseResource> structureDefinitionMap = provideStructureDefinitionMap();
IBaseResource retVal = structureDefinitionMap.get(url); return structureDefinitionMap.get(url);
return retVal;
} }
@Override @Override

View File

@ -586,8 +586,8 @@ public interface IValidationSupport {
private final IBaseResource myValueSet; private final IBaseResource myValueSet;
private final String myError; private final String myError;
public ValueSetExpansionOutcome(IBaseResource theValueSet, String theError) { public ValueSetExpansionOutcome(String theError) {
myValueSet = theValueSet; myValueSet = null;
myError = theError; 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.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=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.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 # 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.DiffProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; 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.reindex.ReindexJobSubmitterImpl;
import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory; import ca.uhn.fhir.jpa.sched.AutowiringSpringBeanJobFactory;
import ca.uhn.fhir.jpa.sched.HapiSchedulerServiceImpl; import ca.uhn.fhir.jpa.sched.HapiSchedulerServiceImpl;
@ -318,6 +319,12 @@ public abstract class BaseConfig {
return new SubscriptionTriggeringProvider(); return new SubscriptionTriggeringProvider();
} }
@Bean
@Lazy
public ValueSetOperationProvider valueSetOperationProvider() {
return new ValueSetOperationProvider();
}
@Bean @Bean
public TransactionProcessor transactionProcessor() { public TransactionProcessor transactionProcessor() {
return new TransactionProcessor(); return new TransactionProcessor();

View File

@ -88,7 +88,7 @@ public class JpaPersistedResourceValidationSupport implements IValidationSupport
private Class<? extends IBaseResource> myValueSetType; private Class<? extends IBaseResource> myValueSetType;
private Class<? extends IBaseResource> myQuestionnaireType; private Class<? extends IBaseResource> myQuestionnaireType;
private Class<? extends IBaseResource> myImplementationGuideType; 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 * Constructor

View File

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

View File

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

View File

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

View File

@ -128,6 +128,10 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
version.onTable("HFJ_RESOURCE") version.onTable("HFJ_RESOURCE")
.dropIndex("20210908.1", "IDX_RES_LANG"); .dropIndex("20210908.1", "IDX_RES_LANG");
version.onTable("TRM_VALUESET")
.addColumn("20210915.1", "EXPANDED_AT")
.nullable()
.type(ColumnTypeEnum.DATE_TIMESTAMP);
} }
private void init540() { 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% * #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.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> { 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% * #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 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> { 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% * #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 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> { public class BaseJpaResourceProviderValueSetR5 extends JpaResourceProviderR5<ValueSet> {
// nothing
@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;
}
} }

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.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc; import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; 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.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException; import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.jpa.util.LogicUtil; import ca.uhn.fhir.jpa.util.LogicUtil;
import ca.uhn.fhir.rest.api.Constants; 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.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; 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.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.CoverageIgnore; 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.hibernate.search.mapper.orm.session.SearchSession;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; 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.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype; 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.DomainResource;
import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Extension; 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.IntegerType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
@ -170,9 +174,11 @@ import java.util.StringTokenizer;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; 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.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isBlank; 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) { private boolean addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, String theValueSetIncludeVersion) {
String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri(); String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri();
String codeSystemVersion = theConcept.getCodeSystemVersion().getCodeSystemVersionId();
String code = theConcept.getCode(); String code = theConcept.getCode();
String display = theConcept.getDisplay(); String display = theConcept.getDisplay();
Long sourceConceptPid = theConcept.getId(); Long sourceConceptPid = theConcept.getId();
@ -268,9 +275,9 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Collection<TermConceptDesignation> designations = theConcept.getDesignations(); Collection<TermConceptDesignation> designations = theConcept.getDesignations();
if (StringUtils.isNotEmpty(theValueSetIncludeVersion)) { 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 { } 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 (StringUtils.isNotEmpty(theCodeSystemVersion)) {
if (isNoneBlank(theCodeSystem, theCode)) { if (isNoneBlank(theCodeSystem, theCode)) {
if (theAdd && theAddedCodes.add(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)) { if (!theAdd && theAddedCodes.remove(theCodeSystem + "|" + theCode)) {
@ -287,7 +294,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} else { } else {
if (theAdd && theAddedCodes.add(theCodeSystem + "|" + theCode)) { 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)) { 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 (isNoneBlank(theCodeSystem, theCode)) {
if (theAdd && theAddedCodes.add(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; return true;
} }
@ -312,17 +319,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return false; 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) { private boolean addToSet(Set<TermConcept> theSetToPopulate, TermConcept theConcept) {
boolean retVal = theSetToPopulate.add(theConcept); boolean retVal = theSetToPopulate.add(theConcept);
if (retVal) { if (retVal) {
@ -351,13 +347,17 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get(); TermValueSet existingTermValueSet = optionalExistingTermValueSetById.get();
ourLog.info("Deleting existing TermValueSet[{}] and its children...", existingTermValueSet.getId()); ourLog.info("Deleting existing TermValueSet[{}] and its children...", existingTermValueSet.getId());
myValueSetConceptDesignationDao.deleteByTermValueSetId(existingTermValueSet.getId()); deletePreCalculatedValueSetContents(existingTermValueSet);
myValueSetConceptDao.deleteByTermValueSetId(existingTermValueSet.getId());
myTermValueSetDao.deleteById(existingTermValueSet.getId()); myTermValueSetDao.deleteById(existingTermValueSet.getId());
ourLog.info("Done deleting existing TermValueSet[{}] and its children.", 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 @Override
@Transactional @Transactional
public void deleteValueSetAndChildren(ResourceTable theResourceTable) { public void deleteValueSetAndChildren(ResourceTable theResourceTable) {
@ -462,6 +462,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
*/ */
if (!optionalTermValueSet.isPresent()) { if (!optionalTermValueSet.isPresent()) {
ourLog.debug("ValueSet is not present in terminology tables. Will perform in-memory expansion without parameters. {}", getValueSetInfo(theValueSetToExpand)); 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); expandValueSet(theExpansionOptions, theValueSetToExpand, theAccumulator, theFilter);
return; return;
} }
@ -480,11 +482,22 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
/* /*
* ValueSet is pre-expanded in database so let's use that * 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); theAccumulator.addMessage(msg);
expandConcepts(theAccumulator, termValueSet, theFilter, theAdd, isOracleDialect()); 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() { private boolean isOracleDialect() {
return myHibernatePropertiesProvider.getDialect() instanceof org.hibernate.dialect.Oracle12cDialect; return myHibernatePropertiesProvider.getDialect() instanceof org.hibernate.dialect.Oracle12cDialect;
} }
@ -543,6 +556,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
String system = conceptView.getConceptSystemUrl(); String system = conceptView.getConceptSystemUrl();
String code = conceptView.getConceptCode(); String code = conceptView.getConceptCode();
String display = conceptView.getConceptDisplay(); String display = conceptView.getConceptDisplay();
String systemVersion = conceptView.getConceptSystemVersion();
//-- this is quick solution, may need to revisit //-- this is quick solution, may need to revisit
if (!applyFilter(display, filterDisplayValue)) if (!applyFilter(display, filterDisplayValue))
@ -550,7 +564,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long conceptPid = conceptView.getConceptPid(); Long conceptPid = conceptView.getConceptPid();
if (!pidToConcept.containsKey(conceptPid)) { if (!pidToConcept.containsKey(conceptPid)) {
FhirVersionIndependentConcept concept = new FhirVersionIndependentConcept(system, code, display); FhirVersionIndependentConcept concept = new FhirVersionIndependentConcept(system, code, display, systemVersion);
pidToConcept.put(conceptPid, concept); pidToConcept.put(conceptPid, concept);
} }
@ -585,6 +599,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
String system = concept.getSystem(); String system = concept.getSystem();
String code = concept.getCode(); String code = concept.getCode();
String display = concept.getDisplay(); String display = concept.getDisplay();
String systemVersion = concept.getSystemVersion();
if (theAdd) { if (theAdd) {
if (theAccumulator.getCapacityRemaining() != null) { if (theAccumulator.getCapacityRemaining() != null) {
@ -595,7 +610,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
Long sourceConceptPid = pidToSourcePid.get(nextPid); Long sourceConceptPid = pidToSourcePid.get(nextPid);
String sourceConceptDirectParentPids = pidToSourceDirectParentPids.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 { } else {
boolean removed = theAccumulator.excludeConcept(system, code); boolean removed = theAccumulator.excludeConcept(system, code);
if (removed) { if (removed) {
@ -789,77 +804,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
} }
// No CodeSystem matching the URL found in the database. Consumer<FhirVersionIndependentConcept> consumer = c -> {
CodeSystem codeSystemFromContext = fetchCanonicalCodeSystemFromCompleteContext(system); addOrRemoveCode(theValueSetCodeAccumulator, theAddedCodes, theAdd, system, c.getCode(), c.getDisplay(), c.getSystemVersion());
if (codeSystemFromContext == null) { };
// This is a last ditch effort.. We don't have a CodeSystem resource for the desired CS, and we don't have try {
// anything at all in the database that matches it. So let's try asking the validation support context ConversionContext40_50.INSTANCE.init(new VersionConvertor_40_50(new BaseAdvisor_40_50()), "ValueSet");
// just in case there is a registered service that knows how to handle this. This can happen, for example, org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent includeOrExclude = ValueSet40_50.convertConceptSetComponent(theIncludeOrExclude);
// if someone creates a valueset that includes UCUM codes, since we don't have a CodeSystem resource for those new InMemoryTerminologyServerValidationSupport(myContext).expandValueSetIncludeOrExclude(new ValidationSupportContext(provideValidationSupport()), consumer, includeOrExclude);
// but CommonCodeSystemsTerminologyService can validate individual codes. } catch (InMemoryTerminologyServerValidationSupport.ExpansionCouldNotBeCompletedInternallyException e) {
List<FhirVersionIndependentConcept> includedConcepts = null; if (!theExpansionOptions.isFailOnMissingCodeSystem() && e.getFailureType() == InMemoryTerminologyServerValidationSupport.FailureType.UNKNOWN_CODE_SYSTEM) {
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);
return false; return false;
} }
throw new InternalErrorException(e);
} } finally {
ConversionContext40_50.INSTANCE.close("ValueSet");
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);
} }
return false; return false;
@ -1038,10 +997,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
private ValueSet.ConceptReferenceComponent getMatchedConceptIncludedInValueSet(ValueSet.ConceptSetComponent theIncludeOrExclude, TermConcept concept) { private ValueSet.ConceptReferenceComponent getMatchedConceptIncludedInValueSet(ValueSet.ConceptSetComponent theIncludeOrExclude, TermConcept concept) {
ValueSet.ConceptReferenceComponent theIncludeConcept = theIncludeOrExclude.getConcept().stream().filter(includedConcept -> return theIncludeOrExclude
includedConcept.getCode().equalsIgnoreCase(concept.getCode()) .getConcept()
).findFirst().orElse(null); .stream().filter(includedConcept -> includedConcept.getCode().equalsIgnoreCase(concept.getCode()))
return theIncludeConcept; .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)) { 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)) { if (!theAdd && theAddedCodes.remove(theSystem + "|" + theCode)) {
theValueSetCodeAccumulator.excludeConcept(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")); 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) { private void handleFilterLoincAncestor2(String theSystem, SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
case EQUAL: case EQUAL:
@ -1244,6 +1205,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) { private void handleFilterLoincParentChild(SearchPredicateFactory f, BooleanPredicateClausesStep<?> b, ValueSet.ConceptSetFilterComponent theFilter) {
switch (theFilter.getOp()) { switch (theFilter.getOp()) {
case EQUAL: case EQUAL:
@ -1305,7 +1267,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
} }
private boolean isCodeSystemLoinc(String theSystem) { 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) { 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(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."); Validate.isTrue(isNotBlank(theSystem), "Can not expand ValueSet without explicit system - Hibernate Search is not enabled on this server.");
if (theInclude.getConcept().isEmpty()) { if (theInclude.getConcept().isEmpty()) {
for (TermConcept next : theVersion.getConcepts()) { for (TermConcept next : theVersion.getConcepts()) {
addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, theAdd, theSystem, theInclude.getVersion(), next.getCode(), next.getDisplay(), next.getId(), next.getParentPidsAsString()); 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 @Override
public boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet) { public boolean isValueSetPreExpandedForCodeValidation(ValueSet theValueSet) {
ResourcePersistentId valueSetResourcePid = myConceptStorageSvc.getValueSetResourcePid(theValueSet.getIdElement()); Optional<TermValueSet> optionalTermValueSet = fetchValueSetEntity(theValueSet);
Optional<TermValueSet> optionalTermValueSet = myTermValueSetDao.findByResourcePid(valueSetResourcePid.getIdAsLong());
if (!optionalTermValueSet.isPresent()) { if (!optionalTermValueSet.isPresent()) {
ourLog.warn("ValueSet is not present in terminology tables. Will perform in-memory code validation. {}", getValueSetInfo(theValueSet)); 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; 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( protected IValidationSupport.CodeValidationResult validateCodeIsInPreExpandedValueSet(
ConceptValidationOptions theValidationOptions, ConceptValidationOptions theValidationOptions,
ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) { ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Coding theCoding, CodeableConcept theCodeableConcept) {
@ -1505,36 +1499,42 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return null; 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) { if (theValidationOptions.isValidateDisplay() && concepts.size() > 0) {
String systemVersion = null;
for (TermValueSetConcept concept : concepts) { for (TermValueSetConcept concept : concepts) {
systemVersion = concept.getSystemVersion();
if (isBlank(theDisplay) || isBlank(concept.getDisplay()) || theDisplay.equals(concept.getDisplay())) { if (isBlank(theDisplay) || isBlank(concept.getDisplay()) || theDisplay.equals(concept.getDisplay())) {
return new IValidationSupport.CodeValidationResult() return new IValidationSupport.CodeValidationResult()
.setCode(concept.getCode()) .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()) { if (!concepts.isEmpty()) {
return new IValidationSupport.CodeValidationResult() return new IValidationSupport.CodeValidationResult()
.setCode(concepts.get(0).getCode()) .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) { private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode, String theCodeSystemVersion, String theAppend) {
String append = "";
return createFailureCodeValidationResult(theSystem, theCode, append);
}
private CodeValidationResult createFailureCodeValidationResult(String theSystem, String theCode, String theAppend) {
return new CodeValidationResult() return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR) .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) { 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. // We are done with this ValueSet.
txTemplate.execute(t -> { txTemplate.execute(t -> {
valueSetToExpand.setExpansionStatus(TermValueSetPreExpansionStatusEnum.EXPANDED); valueSetToExpand.setExpansionStatus(TermValueSetPreExpansionStatusEnum.EXPANDED);
valueSetToExpand.setExpansionTimestamp(new Date());
myTermValueSetDao.saveAndFlush(valueSetToExpand); myTermValueSetDao.saveAndFlush(valueSetToExpand);
return null; return null;
}); });
@ -2096,11 +2097,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
.setCode(code.getCode()) .setCode(code.getCode())
.setDisplay(code.getDisplay()); .setDisplay(code.getDisplay());
} else { } 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) { 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); Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet);
if (pid != null) { if (pid != null) {
if (isValueSetPreExpandedForCodeValidation(valueSet)) { 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); retVal = new InMemoryTerminologyServerValidationSupport(myContext).validateCodeInValueSet(theValidationSupportContext, theValidationOptions, theCodeSystem, theCode, theDisplay, valueSet);
} else { } else {
String append = " - Unable to locate ValueSet[" + theValueSetUrl + "]"; String append = " - Unable to locate ValueSet[" + theValueSetUrl + "]";
retVal = createFailureCodeValidationResult(theCodeSystem, theCode, append); retVal = createFailureCodeValidationResult(theCodeSystem, theCode, null, append);
} }
return retVal; return retVal;
@ -2383,10 +2384,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
predicates.add(criteriaBuilder.equal(root.get("myCode"), theCode)); predicates.add(criteriaBuilder.equal(root.get("myCode"), theCode));
} }
if (isNotBlank(theDisplay)) {
predicates.add(criteriaBuilder.equal(root.get("myDisplay"), theDisplay));
}
if (isNoneBlank(theCodeSystemUrl)) { if (isNoneBlank(theCodeSystemUrl)) {
predicates.add(criteriaBuilder.equal(systemJoin.get("myCodeSystemUri"), theCodeSystemUrl)); predicates.add(criteriaBuilder.equal(systemJoin.get("myCodeSystemUri"), theCodeSystemUrl));
} }
@ -2413,13 +2410,33 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
if (!resultsList.isEmpty()) { if (!resultsList.isEmpty()) {
TermConcept concept = resultsList.get(0); 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()); return new CodeValidationResult().setCode(concept.getCode()).setDisplay(concept.getDisplay());
} }
if (isBlank(theDisplay)) return createFailureCodeValidationResult(theCodeSystemUrl, theCode, theCodeSystemVersion, " - Code is not found in CodeSystem: " + theCodeSystemUrl);
return createFailureCodeValidationResult(theCodeSystemUrl, theCode); }
else
return createFailureCodeValidationResult(theCodeSystemUrl, theCode, " - Concept Display : " + theDisplay); @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 { public static class Job implements HapiJob {
@ -2526,22 +2543,4 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
return theReqLang.equalsIgnoreCase(theStoredLang); 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 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 * @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; org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = toCanonicalValueSet(theValueSetToExpand); valueSetToExpandR4 = toCanonicalValueSet(theValueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(theExpansionOptions, valueSetToExpandR4); 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) { } catch (FHIRException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }

View File

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

View File

@ -94,7 +94,7 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
} }
@Override @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) { if (mySkipCountRemaining > 0) {
mySkipCountRemaining--; mySkipCountRemaining--;
return; return;
@ -106,10 +106,11 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
setSystemAndVersion(theSystem, contains); setSystemAndVersion(theSystem, contains);
contains.setCode(theCode); contains.setCode(theCode);
contains.setDisplay(theDisplay); contains.setDisplay(theDisplay);
contains.setVersion(theCodeSystemVersion);
} }
@Override @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) { if (mySkipCountRemaining > 0) {
mySkipCountRemaining--; mySkipCountRemaining--;
return; return;
@ -129,6 +130,11 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
setSystemAndVersion(theSystem, contains); setSystemAndVersion(theSystem, contains);
contains.setCode(theCode); contains.setCode(theCode);
contains.setDisplay(theDisplay); contains.setDisplay(theDisplay);
if (isNotBlank(theCodeSystemVersion)) {
contains.setVersion(theCodeSystemVersion);
}
if (theDesignations != null) { if (theDesignations != null) {
for (TermConceptDesignation termConceptDesignation : theDesignations) { for (TermConceptDesignation termConceptDesignation : theDesignations) {
contains 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.entity.TermValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator; import ca.uhn.fhir.jpa.term.IValueSetConceptAccumulator;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.util.FhirVersionIndependentConcept; import ca.uhn.fhir.util.FhirVersionIndependentConcept;
import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype; 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); CodeValidationResult codeSystemValidateCode(IIdType theCodeSystemId, String theValueSetUrl, String theVersion, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept);
String invalidatePreCalculatedExpansion(IIdType theValueSetId, RequestDetails theRequestDetails);
/** /**
* Version independent * Version independent
*/ */

View File

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

View File

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

View File

@ -78,8 +78,7 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
CodingDt coding = null; CodingDt coding = null;
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); 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. assertFalse(result.isOk());
assertTrue(result.isOk());
} }
@Test @Test
@ -107,8 +106,8 @@ public class FhirResourceDaoValueSetDstu2Test extends BaseJpaDstu2Test {
CodeableConceptDt codeableConcept = null; CodeableConceptDt codeableConcept = null;
IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk()); 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("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 @Test

View File

@ -550,23 +550,6 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
assertEquals(0, expansion.getExpansion().getContains().size()); 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 @Test
public void testExpandWithSystemAndCodesInExternalValueSet() { public void testExpandWithSystemAndCodesInExternalValueSet() {
createExternalCsAndLocalVs(); createExternalCsAndLocalVs();

View File

@ -1510,20 +1510,18 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
id2 = myOrganizationDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue(); id2 = myOrganizationDao.create(patient, mySrd).getId().toUnqualifiedVersionless().getValue();
} }
// FIXME: restore
int size; int size;
SearchParameterMap params = new SearchParameterMap(); SearchParameterMap params = new SearchParameterMap();
// params.setLoadSynchronous(true); params.setLoadSynchronous(true);
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1)); assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
//
// params = new SearchParameterMap(); params = new SearchParameterMap();
// params.add("_id", new StringParam(id1)); params.add("_id", new StringParam(id1));
// assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1)); assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(params)), contains(id1));
//
// params = new SearchParameterMap(); params = new SearchParameterMap();
// params.add("_id", new StringParam("9999999999999999")); params.add("_id", new StringParam("9999999999999999"));
// assertEquals(0, toList(myPatientDao.search(params)).size()); assertEquals(0, toList(myPatientDao.search(params)).size());
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
params = new SearchParameterMap(); 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.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 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.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
@ -300,7 +300,7 @@ public class FhirResourceDaoR4SearchWithLuceneDisabledTest extends BaseJpaTest {
// Non Pre-Expanded // Non Pre-Expanded
ValueSet outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions()); 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)); 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" "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 // TODO: get this working
@Disabled @Disabled
@Test @Test

View File

@ -154,7 +154,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
encoded = encode(oo); encoded = encode(oo);
ourLog.info(encoded); ourLog.info(encoded);
assertEquals(1, oo.getIssue().size(), 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); assertEquals(OperationOutcome.IssueSeverity.ERROR, oo.getIssueFirstRep().getSeverity(), encoded);
} }
@ -194,9 +194,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
oo = validateAndReturnOutcome(obs); oo = validateAndReturnOutcome(obs);
encoded = encode(oo); encoded = encode(oo);
ourLog.info(encoded); ourLog.info(encoded);
assertEquals(1, oo.getIssue().size(), encoded); assertEquals("No issues detected during validation", oo.getIssueFirstRep().getDiagnostics(), encoded);
assertEquals("Error Unknown code system: http://cs validating Coding", oo.getIssueFirstRep().getDiagnostics(), encoded);
assertEquals(OperationOutcome.IssueSeverity.WARNING, oo.getIssueFirstRep().getSeverity(), encoded);
} }
@ -463,7 +461,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3"); 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"); obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO");
oo = validateAndReturnOutcome(obs); 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 // Make sure we're caching the validations as opposed to hitting the DB every time
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
@ -792,7 +790,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
obs.getCode().getCodingFirstRep().setDisplay("Some Code"); obs.getCode().getCodingFirstRep().setDisplay("Some Code");
outcome = (OperationOutcome) myObservationDao.validate(obs, null, null, null, ValidationModeEnum.CREATE, "http://example.com/structuredefinition", mySrd).getOperationOutcome(); 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)); 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()); assertEquals(OperationOutcome.IssueSeverity.WARNING, outcome.getIssueFirstRep().getSeverity());
// Correct codesystem, Code in codesystem // 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.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO"); obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO");
oo = validateAndReturnOutcome(obs); 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(); OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo); String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
ourLog.info("Outcome:\n{}", encoded); 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); 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()); 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.dao.data.ITermValueSetConceptDao;
import ca.uhn.fhir.jpa.entity.TermValueSet; import ca.uhn.fhir.jpa.entity.TermValueSet;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.util.ValueSetTestUtil;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; 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.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -17,6 +19,7 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -233,12 +236,9 @@ public class FhirResourceDaoR4ValueSetMultiVersionTest extends BaseJpaR4Test {
include.setVersion("1"); include.setVersion("1");
include.addConcept().setCode("A"); include.addConcept().setCode("A");
try { ValueSet expansion = myValueSetDao.expand(vs, null);
myValueSetDao.expand(vs, null); assertThat(ValueSetTestUtil.toCodes(expansion), Matchers.contains("A"));
fail();
} catch (PreconditionFailedException e) {
assertEquals("Unknown CodeSystem URI \"http://example.com/my_code_systemAA\" referenced from ValueSet", e.getMessage());
}
} }

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.dao.r4; 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.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; 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.CodeType;
import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding; 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.IdType;
import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType; 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); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); 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 @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); IValidationSupport.CodeValidationResult result = myValueSetDao.validateCode(valueSetIdentifier, id, code, system, display, coding, codeableConcept, mySrd);
assertFalse(result.isOk()); assertFalse(result.isOk());
assertEquals("Systolic blood pressure at First encounter", result.getDisplay()); 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 @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.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; 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.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
@ -96,6 +97,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
ourRestServer.registerProvider(subscriptionTriggeringProvider); ourRestServer.registerProvider(subscriptionTriggeringProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class)); ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry); JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(ourRestServer, mySystemDao, myDaoConfig, ourSearchParamRegistry);
confProvider.setImplementationDescription("THIS IS THE DESC"); confProvider.setImplementationDescription("THIS IS THE DESC");

View File

@ -373,7 +373,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.operation() .operation()
.onType(ValueSet.class) .onType(ValueSet.class)
.named("expand") .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(); .execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
@ -564,7 +564,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.execute(); .execute();
fail(); fail();
} catch (InvalidRequestException e) { } 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 { try {
@ -574,11 +574,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onType(ValueSet.class) .onType(ValueSet.class)
.named("expand") .named("expand")
.withParameter(Parameters.class, "valueSet", toExpand) .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(); .execute();
fail(); fail();
} catch (InvalidRequestException e) { } 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 { try {
@ -588,11 +588,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onInstance(myExtensionalVsId) .onInstance(myExtensionalVsId)
.named("expand") .named("expand")
.withParameter(Parameters.class, "valueSet", toExpand) .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(); .execute();
fail(); fail();
} catch (InvalidRequestException e) { } 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 { try {
@ -670,7 +670,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.operation() .operation()
.onType(ValueSet.class) .onType(ValueSet.class)
.named("expand") .named("expand")
.withParameter(Parameters.class, "identifier", new UriType(URL_MY_VALUE_SET)) .withParameter(Parameters.class, "url", new UriType(URL_MY_VALUE_SET))
.execute(); .execute();
ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource(); ValueSet expanded = (ValueSet) respParam.getParameter().get(0).getResource();
@ -796,7 +796,7 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
.onType(ValueSet.class) .onType(ValueSet.class)
.named("validate-code") .named("validate-code")
.withParameter(Parameters.class, "code", new StringType("male")) .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")) .andParameter("system", new StringType("http://hl7.org/fhir/administrative-gender"))
.useHttpGet() .useHttpGet()
.execute(); .execute();
@ -807,8 +807,11 @@ public class ResourceProviderDstu3ValueSetTest extends BaseResourceProviderDstu3
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); 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("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); 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 @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.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; 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.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; 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.registerProviders(mySystemProvider, myTerminologyUploaderProvider, myDeleteExpungeProvider, myReindexProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class)); ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(DiffProvider.class)); ourRestServer.registerProvider(myAppCtx.getBean(DiffProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.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.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut; 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.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -73,6 +74,11 @@ public class ResourceProviderConcurrencyR4Test extends BaseResourceProviderR4Tes
*/ */
@Test @Test
public void testSearchesExecuteConcurrently() { 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("FAMILY1"));
createPatient(withFamily("FAMILY2")); createPatient(withFamily("FAMILY2"));
createPatient(withFamily("FAMILY3")); createPatient(withFamily("FAMILY3"));

View File

@ -482,7 +482,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
ourLog.info(resp); ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); 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); ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); 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 @Test
@ -675,7 +675,7 @@ public class ResourceProviderR4CodeSystemTest extends BaseResourceProviderR4Test
ourLog.info(resp); ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); 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); ourLog.info(resp);
assertFalse(((BooleanType) respParam.getParameter().get(0).getValue()).booleanValue()); 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 @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.config.TestR4WithLuceneDisabledConfig;
import ca.uhn.fhir.jpa.dao.BaseJpaTest; import ca.uhn.fhir.jpa.dao.BaseJpaTest;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; 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.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
@ -190,6 +191,8 @@ public class ResourceProviderR4ValueSetLuceneDisabledTest extends BaseJpaTest {
ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class); ourPagingProvider = myAppCtx.getBean(DatabaseBackedPagingProvider.class);
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
Server server = new Server(0); Server server = new Server(0);
ServletContextHandler proxyHandler = new ServletContextHandler(); 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.api.server.storage.ResourcePersistentId;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; 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.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
@ -119,7 +120,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
private void loadAndPersistValueSet() throws IOException { private void loadAndPersistValueSet() throws IOException {
ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml"); ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
valueSet.setId("ValueSet/vs"); valueSet.setId("ValueSet/vs");
persistValueSet(valueSet, HttpVerb.POST); persistValueSet(valueSet, HttpVerb.PUT);
} }
@SuppressWarnings("EnumSwitchStatementWhichMissesCases") @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
@ -1104,7 +1105,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(toDirectCodes(expansion.getExpansion().getContains()), containsInAnyOrder("A", "AA", "AB", "AAA")); assertThat(toDirectCodes(expansion.getExpansion().getContains()), containsInAnyOrder("A", "AA", "AB", "AAA"));
assertEquals(15, myCaptureQueriesListener.getSelectQueries().size()); 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 // Hierarchical
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
@ -1156,7 +1157,7 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expansion));
assertThat(toDirectCodes(expansion.getExpansion().getContains()), containsInAnyOrder("A", "AA", "AB", "AAA")); assertThat(toDirectCodes(expansion.getExpansion().getContains()), containsInAnyOrder("A", "AA", "AB", "AAA"));
assertEquals(3, myCaptureQueriesListener.getSelectQueries().size()); 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 // Hierarchical
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
@ -1284,8 +1285,11 @@ public class ResourceProviderR4ValueSetNoVerCSNoVerTest extends BaseResourceProv
assertEquals("result", respParam.getParameter().get(0).getName()); assertEquals("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); 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 @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 @Test
public void testExpandByValueSetWithFilterContainsPrefixValue() throws IOException { public void testExpandByValueSetWithFilterContainsPrefixValue() throws IOException {
loadAndPersistCodeSystem(); 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.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; 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.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
@ -108,6 +109,8 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
myDaoRegistry = myAppCtx.getBean(DaoRegistry.class); myDaoRegistry = myAppCtx.getBean(DaoRegistry.class);
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider); ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider);
ourRestServer.registerProvider(myAppCtx.getBean(ValueSetOperationProvider.class));
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class)); ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
IValidationSupport validationSupport = myAppCtx.getBean(IValidationSupport.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("result", respParam.getParameter().get(0).getName());
assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue()); assertEquals(true, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("display", respParam.getParameter().get(1).getName()); assertEquals("message", respParam.getParameter().get(1).getName());
assertEquals("Male", ((StringType) respParam.getParameter().get(1).getValue()).getValue()); 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 // 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(false, ((BooleanType) respParam.getParameter().get(0).getValue()).getValue());
assertEquals("message", respParam.getParameter().get(1).getName()); 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(); 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); 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);
assertFalse(result.isOk());
assertNull(result); 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) { private Set<String> toExpandedCodes(ValueSet theExpanded) {

View File

@ -42,7 +42,7 @@ public class ValueSetConceptAccumulatorTest {
@Test @Test
public void testIncludeConcept() { public void testIncludeConcept() {
for (int i = 0; i < 1000; i++) { 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()); verify(myValueSetConceptDao, times(1000)).save(any());
} }

View File

@ -206,7 +206,7 @@ public class ValueSetExpansionR4ElasticsearchIT extends BaseJpaTest {
include.setSystem(CS_URL); include.setSystem(CS_URL);
myTermSvc.expandValueSet(null, vs, myValueSetCodeAccumulator); 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; 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.context.support.ValueSetExpansionOptions;
import ca.uhn.fhir.jpa.entity.TermCodeSystem; import ca.uhn.fhir.jpa.entity.TermCodeSystem;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; 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.search.builder.SearchBuilder;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet; import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.jpa.util.SqlQuery; 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.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType; 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.ValueSet;
import org.hl7.fhir.r4.model.codesystems.HttpVerb; import org.hl7.fhir.r4.model.codesystems.HttpVerb;
import org.junit.jupiter.api.AfterEach; 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.contains;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ -90,15 +97,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet); ValueSet expandedValueSet = myTermSvc.expandValueSet(null, valueSet);
assertEquals(24, expandedValueSet.getExpansion().getContains().size()); assertEquals(24, expandedValueSet.getExpansion().getContains().size());
runInTransaction(() -> { runInTransaction(() -> assertEquals(24, myTermValueSetConceptDao.count()));
assertEquals(24, myTermValueSetConceptDao.count());
});
myValueSetDao.delete(valueSet.getIdElement()); myValueSetDao.delete(valueSet.getIdElement());
runInTransaction(() -> { runInTransaction(() -> assertEquals(0, myTermValueSetConceptDao.count()));
assertEquals(0, myTermValueSetConceptDao.count());
});
expandedValueSet = myTermSvc.expandValueSet(null, valueSet); expandedValueSet = myTermSvc.expandValueSet(null, valueSet);
assertEquals(24, expandedValueSet.getExpansion().getContains().size()); assertEquals(24, expandedValueSet.getExpansion().getContains().size());
@ -183,10 +186,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input); ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); 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" "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()); assertEquals(11, expandedValueSet.getExpansion().getTotal());
// Make sure we used the pre-expanded version // Make sure we used the pre-expanded version
@ -215,7 +218,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Expansion should contain all codes // Expansion should contain all codes
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input); 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")); assertThat(codes.toString(), codes, containsInAnyOrder("code100", "code1000", "code1001", "code1002", "code1003", "code1004"));
// Make sure we used the pre-expanded version // Make sure we used the pre-expanded version
@ -230,7 +233,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(0, 1000).setFilter("display value 100"); ValueSetExpansionOptions options = ValueSetExpansionOptions.forOffsetAndCount(0, 1000).setFilter("display value 100");
ValueSet expandedValueSet = myValueSetDao.expand(vsId, options, mySrd); 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")); assertThat(codes.toString(), codes, containsInAnyOrder("code100", "code1000", "code1001", "code1002", "code1003", "code1004"));
// Make sure we used the pre-expanded version // Make sure we used the pre-expanded version
@ -273,8 +276,8 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
expandedConceptCodes.removeIf(concept -> !concept.startsWith("code9")); expandedConceptCodes.removeIf(concept -> !concept.startsWith("code9"));
//Ensure that the subsequent expansion with offset returns the same slice we are anticipating. //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)))); assertThat(ValueSetTestUtil.toCodes(expandedValueSet).toString(), ValueSetTestUtil.toCodes(expandedValueSet), is(equalTo(expandedConceptCodes.subList(offset, offset + count))));
assertEquals(4, expandedValueSet.getExpansion().getContains().size(), toCodes(expandedValueSet).toString()); assertEquals(4, expandedValueSet.getExpansion().getContains().size(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
assertEquals(11, expandedValueSet.getExpansion().getTotal()); assertEquals(11, expandedValueSet.getExpansion().getTotal());
// Make sure we used the pre-expanded version // Make sure we used the pre-expanded version
@ -301,7 +304,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(null, input); ValueSet expandedValueSet = myTermSvc.expandValueSet(null, input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); 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 // Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries(); List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
@ -335,10 +338,10 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input); ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); 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" "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()); assertEquals(10, expandedValueSet.getExpansion().getTotal());
// Make sure we used the pre-expanded version // Make sure we used the pre-expanded version
@ -434,11 +437,11 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input); ValueSet expandedValueSet = myTermSvc.expandValueSet(new ValueSetExpansionOptions(), input);
ourLog.debug("Expanded ValueSet:\n" + myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet)); 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" "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(), toCodes(expandedValueSet).toString()); assertEquals(11, expandedValueSet.getExpansion().getTotal(), ValueSetTestUtil.toCodes(expandedValueSet).toString());
// Make sure we used the pre-expanded version // Make sure we used the pre-expanded version
List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries(); List<SqlQuery> selectQueries = myCaptureQueriesListener.getSelectQueries();
@ -446,6 +449,32 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertThat(lastSelectQuery, containsString(" like '%display value 9%'")); 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") @SuppressWarnings("SpellCheckingInspection")
@Test @Test
public void testExpandTermValueSetAndChildren() throws Exception { 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"); assertConceptContainsDesignation(otherConcept, "nl", "http://snomed.info/sct", "900000000000013009", "Synonym", "Systolische bloeddruk minimaal 1 uur");
//Ensure they are streamed back in the same order. //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> firstExpansionCodes = ValueSetTestUtil.toCodes(reexpandedValueSet);
List<String> secondExpansionCodes = expandedValueSet.getExpansion().getContains().stream().map(cn -> cn.getCode()).collect(Collectors.toList()); List<String> secondExpansionCodes = ValueSetTestUtil.toCodes(expandedValueSet);
assertThat(firstExpansionCodes, is(equalTo(secondExpansionCodes))); assertThat(firstExpansionCodes, is(equalTo(secondExpansionCodes)));
//Ensure that internally the designations are expanded back in the same order. //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()); assertEquals(23, expandedValueSet.getExpansion().getContains().size());
//It is enough to test that the sublist returned is the correct one. //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 @Test
@ -666,7 +695,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet); ValueSet expandedValueSet = myTermSvc.expandValueSet(options, valueSet);
String expandedValueSetString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet); String expandedValueSetString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(expandedValueSet);
ourLog.info("Expanded ValueSet:\n" + expandedValueSetString); 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(codeSystem.getConcept().size(), expandedValueSet.getExpansion().getTotal());
assertEquals(myDaoConfig.getPreExpandValueSetsDefaultOffset(), expandedValueSet.getExpansion().getOffset()); 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(1000, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(codeSystem.getConcept().size() - expandedValueSet.getExpansion().getOffset(), expandedValueSet.getExpansion().getContains().size()); 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 @Test
@ -813,7 +842,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(1000, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue()); assertEquals(1000, expandedValueSet.getExpansion().getParameter().get(1).getValueIntegerType().getValue().intValue());
assertEquals(codeSystem.getConcept().size() - expandedValueSet.getExpansion().getOffset(), expandedValueSet.getExpansion().getContains().size()); 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 @Test
@ -849,21 +878,67 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(22, expandedValueSet.getExpansion().getContains().size()); assertEquals(22, expandedValueSet.getExpansion().getContains().size());
//It is enough to test that the sublist returned is the correct one. //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 @Test
public void testExpandValueSetWithUnknownCodeSystem() { public void testExpandValueSetWithUnknownCodeSystem() {
// Direct expansion
ValueSet vs = new ValueSet(); ValueSet vs = new ValueSet();
ValueSet.ConceptSetComponent include = vs.getCompose().addInclude(); vs.getCompose().addInclude().setSystem("http://unknown-system");
include.setSystem("http://unknown-system"); vs = myTermSvc.expandValueSet(new ValueSetExpansionOptions().setFailOnMissingCodeSystem(false), vs);
ValueSet outcome = myTermSvc.expandValueSet(new ValueSetExpansionOptions().setFailOnMissingCodeSystem(false), vs); assertNotNull(vs);
assertEquals(0, outcome.getExpansion().getContains().size()); assertEquals(0, vs.getExpansion().getContains().size());
String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
ourLog.info(encoded); // 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 @Test
@ -899,7 +974,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
assertEquals(22, expandedValueSet.getExpansion().getContains().size()); assertEquals(22, expandedValueSet.getExpansion().getContains().size());
//It is enough to test that the sublist returned is the correct one. //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 @Test
@ -947,7 +1022,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
include.setSystem(CS_URL); include.setSystem(CS_URL);
myTermSvc.expandValueSet(null, vs, myValueSetCodeAccumulator); 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 @Test
@ -981,7 +1056,7 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
// Non Pre-Expanded // Non Pre-Expanded
ValueSet outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions()); 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)); 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" "code5", "code4", "code3", "code2", "code1"
)); ));
@ -991,8 +1066,8 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
myCaptureQueriesListener.clear(); myCaptureQueriesListener.clear();
outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions()); outcome = myValueSetDao.expand(vs, new ValueSetExpansionOptions());
myCaptureQueriesListener.logSelectQueriesForCurrentThread(); myCaptureQueriesListener.logSelectQueriesForCurrentThread();
assertEquals("ValueSet was expanded using a pre-calculated expansion", outcome.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE)); assertThat(outcome.getMeta().getExtensionString(EXT_VALUESET_EXPANSION_MESSAGE), containsString("ValueSet was expanded using an expansion that was pre-calculated"));
assertThat(toCodes(outcome).toString(), toCodes(outcome), contains( assertThat(ValueSetTestUtil.toCodes(outcome).toString(), ValueSetTestUtil.toCodes(outcome), contains(
"code5", "code4", "code3", "code2", "code1" "code5", "code4", "code3", "code2", "code1"
)); ));
@ -1474,9 +1549,469 @@ public class ValueSetExpansionR4Test extends BaseTermR4Test {
}); });
} }
@Nonnull @Test
public static List<String> toCodes(ValueSet theExpandedValueSet) { public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferentVersionPresent() {
return theExpandedValueSet.getExpansion().getContains().stream().map(t -> t.getCode()).collect(Collectors.toList()); 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.common.provider.CqlProviderTestBase;
import ca.uhn.fhir.cql.config.CqlR4Config; import ca.uhn.fhir.cql.config.CqlR4Config;
import ca.uhn.fhir.cql.config.TestCqlConfig; 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.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; 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 ca.uhn.fhir.test.utilities.RequestDetailsHelper;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.hl7.fhir.r4.model.Bundle; 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.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension; import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -45,7 +49,19 @@ public class BaseCqlR4Test extends BaseJpaR4Test implements CqlProviderTestBase
protected protected
FhirContext myFhirContext; FhirContext myFhirContext;
@Autowired @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 { protected int loadDataFromDirectory(String theDirectoryName) throws IOException {
int count = 0; int count = 0;

View File

@ -40,10 +40,16 @@ public class RuntimeSearchParamCache extends ReadOnlySearchParamCache {
getSearchParamMap(theResourceName).put(theName, theSearchParam); getSearchParamMap(theResourceName).put(theName, theSearchParam);
String uri = theSearchParam.getUri(); String uri = theSearchParam.getUri();
if (isNotBlank(uri)) { 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); ourLog.warn("Multiple search parameters have URL: {}", uri);
} else {
myUrlToParam.put(uri, theSearchParam);
} }
myUrlToParam.put(uri, theSearchParam);
} }
if (theSearchParam.getId() != null && theSearchParam.getId().hasIdPart()) { if (theSearchParam.getId() != null && theSearchParam.getId().hasIdPart()) {
String value = theSearchParam.getId().toUnqualifiedVersionless().getValue(); String value = theSearchParam.getId().toUnqualifiedVersionless().getValue();

View File

@ -156,6 +156,11 @@ public class ProviderConstants {
*/ */
public static final String OPERATION_REINDEX = "$reindex"; 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 * 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.context.FhirContext;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation; import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails; 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.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import org.hl7.fhir.instance.model.api.IBaseParameters; 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.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersInvalidException; import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.math.BigDecimal; 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, @OperationParam(name = ProviderConstants.OPERATION_REINDEX_PARAM_EVERYTHING, typeName = "boolean", min = 0, max = 1) IPrimitiveType<Boolean> theEverything,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
Boolean everything = theEverything != null && theEverything.getValue(); boolean everything = theEverything != null && theEverything.getValue();
@Nullable Integer batchSize = myMultiUrlProcessor.getBatchSize(theBatchSize); @Nullable Integer batchSize = myMultiUrlProcessor.getBatchSize(theBatchSize);
if (everything) { if (everything) {
return processEverything(batchSize, theRequestDetails); return processEverything(batchSize, theRequestDetails);
@ -70,9 +73,9 @@ public class ReindexProvider {
private IBaseParameters processEverything(Integer theBatchSize, RequestDetails theRequestDetails) { private IBaseParameters processEverything(Integer theBatchSize, RequestDetails theRequestDetails) {
try { try {
JobExecution jobExecution = myReindexJobSubmitter.submitEverythingJob(theBatchSize, theRequestDetails); JobExecution jobExecution = myReindexJobSubmitter.submitEverythingJob(theBatchSize, theRequestDetails);
IBaseParameters retval = ParametersUtil.newInstance(myFhirContext); IBaseParameters retVal = ParametersUtil.newInstance(myFhirContext);
ParametersUtil.addParameterToParametersLong(myFhirContext, retval, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, jobExecution.getJobId()); ParametersUtil.addParameterToParametersLong(myFhirContext, retVal, ProviderConstants.OPERATION_BATCH_RESPONSE_JOB_ID, jobExecution.getJobId());
return retval; return retVal;
} catch (JobParametersInvalidException e) { } catch (JobParametersInvalidException e) {
throw new InvalidRequestException("Invalid job parameters: " + e.getMessage(), 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); 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 @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) { public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
return myWrap.lookupCode(theValidationSupportContext, theSystem, theCode, 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); 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 @Override
public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) { public TranslateConceptResults translateConcept(TranslateCodeRequest theRequest) {
return myWrap.translateConcept(theRequest); return myWrap.translateConcept(theRequest);

View File

@ -90,6 +90,21 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple
return loadFromCache(myCache, key, t -> super.fetchAllNonBaseStructureDefinitions()); 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 @Override
public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) { public <T extends IBaseResource> T fetchResource(@Nullable Class<T> theClass, String theUri) {
return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri, return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri,

View File

@ -31,6 +31,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -59,8 +60,16 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override @Override
public ValueSetExpansionOutcome expandValueSet(ValidationSupportContext theValidationSupportContext, ValueSetExpansionOptions theExpansionOptions, @Nonnull IBaseResource theValueSetToExpand) { 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) { if (expansionR5 == null) {
return null; return null;
} }
@ -89,10 +98,10 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
throw new IllegalArgumentException("Can not handle version: " + myCtx.getVersion().getVersion()); 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; org.hl7.fhir.r5.model.ValueSet expansionR5;
switch (theValueSetToExpand.getStructureFhirVersionEnum()) { switch (theValueSetToExpand.getStructureFhirVersionEnum()) {
case DSTU2: { case DSTU2: {
@ -112,7 +121,7 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
break; break;
} }
case R5: { 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; break;
} }
case DSTU2_1: case DSTU2_1:
@ -124,17 +133,34 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
@Override @Override
public CodeValidationResult public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystemUrlAndVersion, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { org.hl7.fhir.r5.model.ValueSet expansion;
org.hl7.fhir.r5.model.ValueSet expansion = expandValueSetToCanonical(theValidationSupportContext, theValueSet, theCodeSystemUrlAndVersion, theCode); 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) { if (expansion == null) {
return null; return null;
} }
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion);
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystemUrlAndVersion, theCode, theDisplay, expansion, vsUrl);
} }
@Override @Override
@Nullable
public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) { public CodeValidationResult validateCode(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
IBaseResource vs; IBaseResource vs;
if (isNotBlank(theValueSetUrl)) { 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) { if (valueSetExpansionOutcome == null) {
return null; return null;
} }
if (valueSetExpansionOutcome.getError() != null) {
return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR)
.setMessage(valueSetExpansionOutcome.getError());
}
IBaseResource expansion = valueSetExpansionOutcome.getValueSet(); IBaseResource expansion = valueSetExpansionOutcome.getValueSet();
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion, theValueSetUrl);
return validateCodeInExpandedValueSet(theValidationSupportContext, theOptions, theCodeSystem, theCode, theDisplay, expansion);
} }
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; assert theExpansion != null;
boolean caseSensitive = true; boolean caseSensitive = true;
@ -290,13 +320,13 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
} }
String codeSystemUrlToValidate=null; String codeSystemUrlToValidate = null;
String codeSystemVersionToValidate=null; String codeSystemVersionToValidate = null;
if (theCodeSystemUrlAndVersionToValidate != null) { if (theCodeSystemUrlAndVersionToValidate != null) {
int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|"); int versionIndex = theCodeSystemUrlAndVersionToValidate.indexOf("|");
if (versionIndex > -1) { if (versionIndex > -1) {
codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex); codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate.substring(0, versionIndex);
codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex+1); codeSystemVersionToValidate = theCodeSystemUrlAndVersionToValidate.substring(versionIndex + 1);
} else { } else {
codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate; codeSystemUrlToValidate = theCodeSystemUrlAndVersionToValidate;
} }
@ -311,19 +341,31 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
if (codeMatches) { if (codeMatches) {
if (theOptions.isInferSystem() || (nextExpansionCode.getSystem().equals(codeSystemUrlToValidate) && (codeSystemVersionToValidate == null || codeSystemVersionToValidate.equals(nextExpansionCode.getSystemVersion())))) { 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))) { if (!theOptions.isValidateDisplay() || (isBlank(nextExpansionCode.getDisplay()) || isBlank(theDisplayToValidate) || nextExpansionCode.getDisplay().equals(theDisplayToValidate))) {
return new CodeValidationResult() CodeValidationResult codeValidationResult = new CodeValidationResult()
.setCode(theCodeToValidate) .setCode(theCodeToValidate)
.setDisplay(nextExpansionCode.getDisplay()) .setDisplay(nextExpansionCode.getDisplay())
.setCodeSystemName(codeSystemResourceName) .setCodeSystemName(codeSystemResourceName)
.setCodeSystemVersion(codeSystemResourceVersion); .setCodeSystemVersion(csVersion);
if (isNotBlank(theValueSetUrl)) {
codeValidationResult.setMessage("Code was validated against in-memory expansion of ValueSet: " + theValueSetUrl);
}
return codeValidationResult;
} else { } 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() return new CodeValidationResult()
.setSeverity(IssueSeverity.ERROR) .setSeverity(IssueSeverity.ERROR)
.setDisplay(nextExpansionCode.getDisplay()) .setDisplay(nextExpansionCode.getDisplay())
.setMessage("Concept Display \"" + theDisplayToValidate + "\" does not match expected \"" + nextExpansionCode.getDisplay() + "\"") .setMessage(message)
.setCodeSystemName(codeSystemResourceName) .setCodeSystemName(codeSystemResourceName)
.setCodeSystemVersion(codeSystemResourceVersion); .setCodeSystemVersion(csVersion);
} }
} }
} }
@ -338,6 +380,9 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
severity = ValidationMessage.IssueSeverity.ERROR; severity = ValidationMessage.IssueSeverity.ERROR;
message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'"; message = "Unknown code '" + (isNotBlank(theCodeSystemUrlAndVersionToValidate) ? theCodeSystemUrlAndVersionToValidate + "#" : "") + theCodeToValidate + "'";
} }
if (isNotBlank(theValueSetUrl)) {
message += " for in-memory expansion of ValueSet '" + theValueSetUrl + "'";
}
return new CodeValidationResult() return new CodeValidationResult()
.setSeverityCode(severity.toCode()) .setSeverityCode(severity.toCode())
@ -346,52 +391,27 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
@Override @Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) { 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 @Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu2Hl7Org(ValidationSupportContext theValidationSupportContext, ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) throws ExpansionCouldNotBeCompletedInternallyException {
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));
};
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 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 (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
return (output);
} }
@Nullable @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 parserRi = FhirContext.forCached(FhirVersionEnum.DSTU2_HL7ORG).newJsonParser();
IParser parserHapi = FhirContext.forDstu2Cached().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.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 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 (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
return (output);
} }
@Override @Override
@ -437,55 +457,28 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
} }
@Nullable @Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetDstu3(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.dstu3.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { 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 {
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));
};
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 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 (expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode));
return (output);
} }
@Nullable @Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetR4(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r4.model.ValueSet theInput, @Nullable String theWantSystemUrlAndVersion, @Nullable String theWantCode) { 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 {
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));
};
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 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 expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode);
return (output);
} }
@Nullable @Nullable
private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) { private org.hl7.fhir.r5.model.ValueSet expandValueSetR5(ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) throws ExpansionCouldNotBeCompletedInternallyException {
Function<String, org.hl7.fhir.r5.model.CodeSystem> codeSystemLoader = t -> (org.hl7.fhir.r5.model.CodeSystem) theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(t); return expandValueSetR5(theValidationSupportContext, theInput, null, null);
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);
} }
@Nullable @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<>(); Set<FhirVersionIndependentConcept> concepts = new HashSet<>();
try { expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode);
expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getInclude(), true, theWantSystemUrlAndVersion, theWantCode); expandValueSetR5IncludeOrExcludes(theValidationSupportContext, concepts, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode);
expandValueSetR5IncludeOrExclude(theValidationSupportContext, concepts, theCodeSystemLoader, theValueSetLoader, theInput.getCompose().getExclude(), false, theWantSystemUrlAndVersion, theWantCode);
} catch (ExpansionCouldNotBeCompletedInternallyException e) {
return null;
}
org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet(); org.hl7.fhir.r5.model.ValueSet retVal = new org.hl7.fhir.r5.model.ValueSet();
for (FhirVersionIndependentConcept next : concepts) { for (FhirVersionIndependentConcept next : concepts) {
@ -499,130 +492,308 @@ public class InMemoryTerminologyServerValidationSupport implements IValidationSu
return retVal; 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 wantSystemUrl = null;
String wantSystemVersion = null; String wantSystemVersion = null;
if (theWantSystemUrlAndVersion != null) { if (theWantSystemUrlAndVersion != null) {
int versionIndex = theWantSystemUrlAndVersion.indexOf("|"); int versionIndex = theWantSystemUrlAndVersion.indexOf("|");
if (versionIndex > -1) { if (versionIndex > -1) {
wantSystemUrl = theWantSystemUrlAndVersion.substring(0,versionIndex); wantSystemUrl = theWantSystemUrlAndVersion.substring(0, versionIndex);
wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex+1); wantSystemVersion = theWantSystemUrlAndVersion.substring(versionIndex + 1);
} else { } else {
wantSystemUrl = theWantSystemUrlAndVersion; 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<>(); List<FhirVersionIndependentConcept> nextCodeList = new ArrayList<>();
String includeOrExcludeConceptSystemUrl = nextInclude.getSystem(); String includeOrExcludeConceptSystemUrl = theInclude.getSystem();
String includeOrExcludeConceptSystemVersion = nextInclude.getVersion(); String includeOrExcludeConceptSystemVersion = theInclude.getVersion();
if (isNotBlank(includeOrExcludeConceptSystemUrl)) { CodeSystem includeOrExcludeSystemResource = null;
if (isNotBlank(includeOrExcludeConceptSystemUrl)) {
if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) { if (wantSystemUrl != null && !wantSystemUrl.equals(includeOrExcludeConceptSystemUrl)) {
continue; return false;
} }
if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) { if (wantSystemVersion != null && !wantSystemVersion.equals(includeOrExcludeConceptSystemVersion)) {
continue; return false;
} }
CodeSystem includeOrExcludeSystemResource; String loadedCodeSystemUrl;
if (includeOrExcludeConceptSystemVersion != null) { if (includeOrExcludeConceptSystemVersion != null) {
includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion); loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl + "|" + includeOrExcludeConceptSystemVersion;
} else { } else {
includeOrExcludeSystemResource = theCodeSystemLoader.apply(includeOrExcludeConceptSystemUrl); loadedCodeSystemUrl = includeOrExcludeConceptSystemUrl;
} }
Set<String> wantCodes; includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl);
if (nextInclude.getConcept().isEmpty()) {
wantCodes = null;
} else {
wantCodes = nextInclude
.getConcept()
.stream().map(t -> t.getCode()).collect(Collectors.toSet());
}
boolean ableToHandleCode = false; Set<String> wantCodes;
if (includeOrExcludeSystemResource == null || includeOrExcludeSystemResource.getContent() == CodeSystem.CodeSystemContentMode.NOTPRESENT) { if (theInclude.getConcept().isEmpty()) {
wantCodes = null;
} else {
wantCodes = theInclude
.getConcept()
.stream().map(t -> t.getCode()).collect(Collectors.toSet());
}
if (theWantCode != null) { boolean ableToHandleCode = false;
if (theValidationSupportContext.getRootValidationSupport().isCodeSystemSupported(theValidationSupportContext, includeOrExcludeConceptSystemUrl)) { String failureMessage = null;
LookupCodeResult lookup = theValidationSupportContext.getRootValidationSupport().lookupCode(theValidationSupportContext, includeOrExcludeConceptSystemUrl, theWantCode, null); FailureType failureType = FailureType.OTHER;
if (lookup != null && lookup.isFound()) {
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() CodeSystem.ConceptDefinitionComponent conceptDefinition = new CodeSystem.ConceptDefinitionComponent()
.addConcept() .addConcept()
.setCode(theWantCode) .setCode(theWantCode)
.setDisplay(lookup.getCodeDisplay()); .setDisplay(matchingEnumeratedConcept.get().getDisplay());
List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition); List<CodeSystem.ConceptDefinitionComponent> codesList = Collections.singletonList(conceptDefinition);
addCodes(includeOrExcludeConceptSystemUrl, includeOrExcludeConceptSystemVersion, codesList, nextCodeList, wantCodes); 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 { } else {
ableToHandleCode = true; if (isNotBlank(theInclude.getSystem()) && !theInclude.getConcept().isEmpty() && theInclude.getFilter().isEmpty() && theInclude.getValueSet().isEmpty()) {
} theInclude
.getConcept()
if (!ableToHandleCode) { .stream()
throw new ExpansionCouldNotBeCompletedInternallyException(); .map(t -> new FhirVersionIndependentConcept(theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion()))
} .forEach(t -> nextCodeList.add(t));
ableToHandleCode = true;
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();
} }
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 { } 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) { 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) { 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.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import org.apache.commons.compress.utils.Sets;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -14,10 +15,13 @@ import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -75,22 +79,39 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* </p> * </p>
*/ */
public void addCodeSystem(IBaseResource theCodeSystem) { public void addCodeSystem(IBaseResource theCodeSystem) {
String url = processResourceAndReturnUrl(theCodeSystem, "CodeSystem"); Set<String> urls = processResourceAndReturnUrls(theCodeSystem, "CodeSystem");
addToMap(theCodeSystem, myCodeSystems, url); addToMap(theCodeSystem, myCodeSystems, urls);
} }
private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) { private Set<String> processResourceAndReturnUrls(IBaseResource theResource, String theResourceName) {
Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null"); Validate.notNull(theResource, "the" + theResourceName + " must not be null");
RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theCodeSystem); RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theResource);
String actualResourceName = resourceDef.getName(); String actualResourceName = resourceDef.getName();
Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); 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); String url = urlValue.map(t -> (((IPrimitiveType<?>) t).getValueAsString())).orElse(null);
Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null"); Validate.notNull(url, "the" + theResourceName + ".getUrl() must not return null");
Validate.notBlank(url, "the" + theResourceName + ".getUrl() must return a value"); 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> * </p>
*/ */
public void addStructureDefinition(IBaseResource theStructureDefinition) { public void addStructureDefinition(IBaseResource theStructureDefinition) {
String url = processResourceAndReturnUrl(theStructureDefinition, "StructureDefinition"); Set<String> url = processResourceAndReturnUrls(theStructureDefinition, "StructureDefinition");
addToMap(theStructureDefinition, myStructureDefinitions, url); addToMap(theStructureDefinition, myStructureDefinitions, url);
} }
private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, String theUrl) { private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, Collection<String> theUrls) {
if (isNotBlank(theUrl)) { for (String urls : theUrls) {
theMap.put(theUrl, theResource); if (isNotBlank(urls)) {
theMap.put(urls, theResource);
int lastSlashIdx = theUrl.lastIndexOf('/'); int lastSlashIdx = urls.lastIndexOf('/');
if (lastSlashIdx != -1) { if (lastSlashIdx != -1) {
theMap.put(theUrl.substring(lastSlashIdx + 1), theResource); theMap.put(urls.substring(lastSlashIdx + 1), theResource);
int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1); int previousSlashIdx = urls.lastIndexOf('/', lastSlashIdx - 1);
if (previousSlashIdx != -1) { if (previousSlashIdx != -1) {
theMap.put(theUrl.substring(previousSlashIdx + 1), theResource); theMap.put(urls.substring(previousSlashIdx + 1), theResource);
}
} }
} }
} }
} }
@ -143,8 +165,8 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* </p> * </p>
*/ */
public void addValueSet(IBaseResource theValueSet) { public void addValueSet(IBaseResource theValueSet) {
String url = processResourceAndReturnUrl(theValueSet, "ValueSet"); Set<String> urls = processResourceAndReturnUrls(theValueSet, "ValueSet");
addToMap(theValueSet, myValueSets, url); addToMap(theValueSet, myValueSets, urls);
} }

View File

@ -37,19 +37,21 @@ public class UnknownCodeSystemWarningValidationSupport extends BaseValidationSup
@Nullable @Nullable
@Override @Override
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) { 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); IBaseResource codeSystem = theValidationSupportContext.getRootValidationSupport().fetchCodeSystem(theCodeSystem);
if (codeSystem != null) { if (codeSystem != null) {
return null; return null;
} }
String message = "Unknown code system: " + theCodeSystem; return new CodeValidationResult()
if (!myAllowNonExistentCodeSystem) { .setCode(theCode)
return new CodeValidationResult() .setSeverity(IssueSeverity.INFORMATION)
.setSeverity(IssueSeverity.ERROR) .setMessage("Code " + theCodeSystem + "#" + theCode + " was not checked because the CodeSystem is not available");
.setMessage(message);
}
throw new TerminologyServiceException(message);
} }
public void setAllowNonExistentCodeSystem(boolean theAllowNonExistentCodeSystem) { 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.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.CodeType;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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 InMemoryTerminologyServerValidationSupport mySvc;
private FhirContext myCtx = FhirContext.forR4(); private FhirContext myCtx = FhirContext.forR4();
private DefaultProfileValidationSupport myDefaultSupport; private DefaultProfileValidationSupport myDefaultSupport;
private ValidationSupportChain myChain; private ValidationSupportChain myChain;
private PrePopulatedValidationSupport myPrePopulated; private PrePopulatedValidationSupport myPrePopulated;
private CommonCodeSystemsTerminologyService myCommonCodeSystemsTermSvc;
@BeforeEach @BeforeEach
public void before( ){ public void before() {
mySvc = new InMemoryTerminologyServerValidationSupport(myCtx); mySvc = new InMemoryTerminologyServerValidationSupport(myCtx);
myDefaultSupport = new DefaultProfileValidationSupport(myCtx); myDefaultSupport = new DefaultProfileValidationSupport(myCtx);
myPrePopulated = new PrePopulatedValidationSupport(myCtx); myPrePopulated = new PrePopulatedValidationSupport(myCtx);
myChain = new ValidationSupportChain(mySvc,myPrePopulated, myDefaultSupport); myCommonCodeSystemsTermSvc = new CommonCodeSystemsTerminologyService(myCtx);
myChain = new ValidationSupportChain(mySvc, myPrePopulated, myDefaultSupport, myCommonCodeSystemsTermSvc);
// Force load // Force load
myDefaultSupport.fetchCodeSystem("http://foo"); myDefaultSupport.fetchCodeSystem("http://foo");
} }
@Test @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(); ValueSet vs = new ValueSet();
vs.setUrl("http://vs"); vs.setUrl("http://vs");
vs vs
@ -49,12 +85,75 @@ class InMemoryTerminologyServerValidationSupportTest {
ValidationSupportContext valCtx = new ValidationSupportContext(myChain); ValidationSupportContext valCtx = new ValidationSupportContext(myChain);
ConceptValidationOptions options = new ConceptValidationOptions(); 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()); assertTrue(outcome.isOk());
outcome = myChain.validateCodeInValueSet(valCtx, options, "http://cs", "code99", null, vs); 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 static class PrePopulatedValidationSupportDstu2 extends PrePopulatedValidationSupport {
private final Map<String, IBaseResource> myDstu2ValueSets; 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.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterAll; import org.hl7.fhir.r4.model.CodeSystem;
import org.junit.jupiter.api.Test; 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.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
public class DefaultProfileValidationSupportTest { public class DefaultProfileValidationSupportTest {
private static FhirContext ourCtx = FhirContext.forR4Cached();
private DefaultProfileValidationSupport mySvc = new DefaultProfileValidationSupport(ourCtx); private DefaultProfileValidationSupport mySvc = new DefaultProfileValidationSupport(ourCtx);
private static FhirContext ourCtx = FhirContext.forDstu3();
@Test @Test
public void testGetStructureDefinitionsWithRelativeUrls() { public void testGetStructureDefinitionsWithRelativeUrls() {
@ -26,10 +27,17 @@ public class DefaultProfileValidationSupportTest {
} }
@Test
@AfterAll public void testLoadCodeSystemWithVersion() {
public static void afterClassClearContext() { CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem("http://terminology.hl7.org/CodeSystem/v2-0291");
TestUtil.randomizeLocaleAndTimezone(); 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); Patient resource = loadResource("/dstu3/nl/nl-core-patient-01.json", Patient.class);
ValidationResult results = myVal.validateWithResult(resource); ValidationResult results = myVal.validateWithResult(resource);
List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results); 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 { private void loadNL() throws IOException {
@ -1180,7 +1180,7 @@ public class FhirInstanceValidatorDstu3Test {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output); logResultsAndReturnAll(output);
assertEquals( 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()); output.getMessages().get(0).getMessage());
} }

View File

@ -1200,7 +1200,7 @@ public class FhirInstanceValidatorR4Test extends BaseTest {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output); logResultsAndReturnAll(output);
assertEquals( 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()); output.getMessages().get(0).getMessage());
} }

View File

@ -363,7 +363,7 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(encoded); ValidationResult output = myVal.validateWithResult(encoded);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); 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()); 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); ValidationResult output = myVal.validateWithResult(encoded);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); 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); ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output);
assertEquals(2, res.size(), output.toString()); assertEquals(1, 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("A code with no system has no defined meaning. A system should be provided", res.get(0).getMessage());
} }
@Test @Test

View File

@ -67,7 +67,7 @@ public class QuestionnaireResponseValidatorR5Test {
private static final String CODE_ICC_SCHOOLTYPE_PT = "PT"; private static final String CODE_ICC_SCHOOLTYPE_PT = "PT";
private static final String ID_VS_SCHOOLTYPE = "ValueSet/schooltype"; private static final String ID_VS_SCHOOLTYPE = "ValueSet/schooltype";
private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/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 static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport(ourCtx);
private FhirInstanceValidator myInstanceVal; private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal; private FhirValidator myVal;
@ -184,7 +184,7 @@ public class QuestionnaireResponseValidatorR5Test {
ValidationResult errors = myVal.validateWithResult(qa); ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString()); 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() { public static void afterClassClearContext() {
myDefaultValidationSupport.flush(); myDefaultValidationSupport.flush();
myDefaultValidationSupport = null; myDefaultValidationSupport = null;
TestUtil.randomizeLocaleAndTimezone();
} }