Fix term uploads and validate against valueset recalculation (#1540)

* Start refactoring terminology delta operations

* Work on delta operations

* Work on concept saving

* Split term services into smaller services

* Work on term delta operations

* Work on term svcs

* Work on term operations

* More work on delta uploader

* Add a test

* Wrk on term service

* Fix compile error

* Some refactoring

* Test fix

* Test fix

* Test fixes

* Test fix

* Test fixes

* Test fixes

* Work on delta

* Work on tests#

* Test fixes

* Improve resequencing logic

* Build test

* More testing

* More build testing

* More work on tests

* CHange test logging

* Fix term service PID issue

* Update src/changes/changes.xml

Co-Authored-By: Diederik Muylwyk <diederik.muylwyk@gmail.com>

* Address review comment

* Some cleanup

* Test fix

* Fix some tests

* Work on upload command

* Test fixes

* Work on validation fixes

* Work on validation against pre-expansion

* Work on direct validation

* A bit of cleanup

* Add some javadoc

* Force another build

* Test fixes

* Fix tests

* Fix tests
This commit is contained in:
James Agnew 2019-10-15 21:31:29 -04:00 committed by GitHub
parent 8b2ab51bc6
commit 12a74e6cbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
92 changed files with 5773 additions and 2444 deletions

View File

@ -265,7 +265,7 @@ public class ValidatorExamples {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
// TODO: implement
return null;
}

View File

@ -102,6 +102,11 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
*/
boolean isCodeSystemSupported(FhirContext theContext, String theSystem);
/**
* Fetch the given ValueSet by URL
*/
IBaseResource fetchValueSet(FhirContext theContext, String theValueSetUrl);
/**
* Validates that the given code exists and if possible returns a display
* name. This method is called to check codes which are found in "example"
@ -112,7 +117,7 @@ public interface IContextValidationSupport<EVS_IN, EVS_OUT, SDT, CST, CDCT, IST>
* @param theDisplay The display name, if it should also be validated
* @return Returns a validation result object
*/
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
CodeValidationResult<CDCT, IST> validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
/**
* Look up a code using the system and code value

View File

@ -0,0 +1,18 @@
package ca.uhn.fhir.util;
import java.text.DecimalFormat;
public class FileUtil {
// Use "bytes" instead of just "b" because it reads easier in logs
private static final String[] UNITS = new String[]{"Bytes", "kB", "MB", "GB", "TB"};
public static String formatFileSize(long theBytes) {
if (theBytes <= 0) {
return "0 " + UNITS[0];
}
int digitGroups = (int) (Math.log10(theBytes) / Math.log10(1024));
digitGroups = Math.min(digitGroups, UNITS.length - 1);
return new DecimalFormat("###0.#").format(theBytes / Math.pow(1024, digitGroups)) + " " + UNITS[digitGroups];
}
}

View File

@ -30,7 +30,11 @@ import com.ctc.wstx.stax.WstxOutputFactory;
import org.apache.commons.text.StringEscapeUtils;
import org.codehaus.stax2.XMLOutputFactory2;
import org.codehaus.stax2.io.EscapingWriterFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.*;
@ -1830,23 +1834,30 @@ public class XmlUtil {
}
}
public static DocumentBuilderFactory newDocumentBuilderFactory() {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
docBuilderFactory.setXIncludeAware(false);
docBuilderFactory.setExpandEntityReferences(false);
public static Document parseDocument(String theInput) throws IOException, SAXException {
DocumentBuilder builder = null;
try {
docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
docBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
throwUnitTestExceptionIfConfiguredToDoSo();
} catch (Exception e) {
ourLog.warn("Failed to set feature on XML parser: " + e.toString());
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
docBuilderFactory.setXIncludeAware(false);
docBuilderFactory.setExpandEntityReferences(false);
try {
docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
docBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
throwUnitTestExceptionIfConfiguredToDoSo();
} catch (Exception e) {
ourLog.warn("Failed to set feature on XML parser: " + e.toString());
}
builder = docBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ConfigurationException(e);
}
return docBuilderFactory;
InputSource src = new InputSource(new StringReader(theInput));
return builder.parse(src);
}
}

View File

@ -0,0 +1,20 @@
package ca.uhn.fhir.util;
import org.junit.Test;
import static org.junit.Assert.*;
public class FileUtilTest {
@Test
public void formatFileSize() {
assertEquals("0 Bytes", FileUtil.formatFileSize(0));
assertEquals("1 Bytes", FileUtil.formatFileSize(1));
assertEquals("1.2 kB", FileUtil.formatFileSize(1234));
assertEquals("12.1 kB", FileUtil.formatFileSize(12345));
assertEquals("11.8 MB", FileUtil.formatFileSize(12345678));
assertEquals("103.5 GB", FileUtil.formatFileSize(111111111111L));
assertEquals("101.1 TB", FileUtil.formatFileSize(111111111111111L));
assertEquals("10105.5 TB", FileUtil.formatFileSize(11111111111111111L));
}
}

View File

@ -398,7 +398,7 @@ public abstract class BaseCommand implements Comparable<BaseCommand> {
}
protected IGenericClient newClientWithBaseUrl(CommandLine theCommandLine, String theBaseUrl, String theBasicAuthOptionName, String theBearerTokenOptionName) throws ParseException {
myFhirCtx.getRestfulClientFactory().setSocketTimeout(10 * 60 * 1000);
myFhirCtx.getRestfulClientFactory().setSocketTimeout((int) DateUtils.MILLIS_PER_HOUR);
IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl);
String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName);

View File

@ -91,7 +91,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -98,7 +98,7 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSet) {
return null;
}

View File

@ -27,18 +27,20 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.AttachmentUtil;
import ca.uhn.fhir.util.FileUtil;
import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.ICompositeType;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -46,8 +48,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class UploadTerminologyCommand extends BaseCommand {
static final String UPLOAD_TERMINOLOGY = "upload-terminology";
// TODO: Don't use qualified names for loggers in HAPI CLI.
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(UploadTerminologyCommand.class);
private static final long DEFAULT_TRANSFER_SIZE_LIMIT = 10 * FileUtils.ONE_MB;
private static long ourTransferSizeLimit = DEFAULT_TRANSFER_SIZE_LIMIT;
@Override
public String getCommandDescription() {
@ -105,23 +108,25 @@ public class UploadTerminologyCommand extends BaseCommand {
switch (mode) {
case SNAPSHOT:
invokeOperation(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM);
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_UPLOAD_EXTERNAL_CODE_SYSTEM);
break;
case ADD:
invokeOperation(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD);
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_ADD);
break;
case REMOVE:
invokeOperation(theCommandLine, termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE);
invokeOperation(termUrl, datafile, client, inputParameters, JpaConstants.OPERATION_APPLY_CODESYSTEM_DELTA_REMOVE);
break;
}
}
private void invokeOperation(CommandLine theCommandLine, String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException {
private void invokeOperation(String theTermUrl, String[] theDatafile, IGenericClient theClient, IBaseParameters theInputParameters, String theOperationName) throws ParseException {
ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8);
int compressedSourceBytesCount = 0;
int compressedFileCount = 0;
boolean haveCompressedContents = false;
try {
for (String nextDataFile : theDatafile) {
@ -133,19 +138,19 @@ public class UploadTerminologyCommand extends BaseCommand {
ZipEntry nextEntry = new ZipEntry(stripPath(nextDataFile));
zipOutputStream.putNextEntry(nextEntry);
IOUtils.copy(fileInputStream, zipOutputStream);
CountingInputStream countingInputStream = new CountingInputStream(fileInputStream);
IOUtils.copy(countingInputStream, zipOutputStream);
haveCompressedContents = true;
compressedSourceBytesCount += countingInputStream.getCount();
zipOutputStream.flush();
ourLog.info("Finished compressing {} into {}", nextEntry.getSize(), nextEntry.getCompressedSize());
ourLog.info("Finished compressing {}", nextDataFile);
} else {
ourLog.info("Adding file: {}", nextDataFile);
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx);
AttachmentUtil.setUrl(myFhirCtx, attachment, "file:" + nextDataFile);
AttachmentUtil.setData(myFhirCtx, attachment, IOUtils.toByteArray(fileInputStream));
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment);
String fileName = "file:" + nextDataFile;
addFileToRequestBundle(theInputParameters, fileName, IOUtils.toByteArray(fileInputStream));
}
}
@ -158,16 +163,16 @@ public class UploadTerminologyCommand extends BaseCommand {
}
if (haveCompressedContents) {
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx);
AttachmentUtil.setUrl(myFhirCtx, attachment, "file:/files.zip");
AttachmentUtil.setData(myFhirCtx, attachment, byteArrayOutputStream.toByteArray());
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment);
byte[] compressedBytes = byteArrayOutputStream.toByteArray();
ourLog.info("Compressed {} bytes in {} file(s) into {} bytes", FileUtil.formatFileSize(compressedSourceBytesCount), compressedFileCount, FileUtil.formatFileSize(compressedBytes.length));
addFileToRequestBundle(theInputParameters, "file:/files.zip", compressedBytes);
}
ourLog.info("Beginning upload - This may take a while...");
if (ourLog.isDebugEnabled() || "true".equals(System.getProperty("test"))) {
ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theInputParameters));
ourLog.info("Submitting parameters: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(theInputParameters));
}
IBaseParameters response;
@ -190,11 +195,50 @@ public class UploadTerminologyCommand extends BaseCommand {
ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response));
}
private void addFileToRequestBundle(IBaseParameters theInputParameters, String theFileName, byte[] theBytes) {
byte[] bytes = theBytes;
String fileName = theFileName;
if (bytes.length > ourTransferSizeLimit) {
ourLog.info("File size is greater than {} - Going to use a local file reference instead of a direct HTTP transfer. Note that this will only work when executing this command on the same server as the FHIR server itself.", FileUtil.formatFileSize(ourTransferSizeLimit));
String suffix = fileName.substring(fileName.lastIndexOf("."));
try {
File tempFile = File.createTempFile("hapi-fhir-cli", suffix);
tempFile.deleteOnExit();
try (OutputStream fileOutputStream = new FileOutputStream(tempFile, false)) {
fileOutputStream.write(bytes);
bytes = null;
fileName = "localfile:" + tempFile.getAbsolutePath();
}
} catch (IOException e) {
throw new CommandFailureException(e);
}
}
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx);
AttachmentUtil.setUrl(myFhirCtx, attachment, fileName);
if (bytes != null) {
AttachmentUtil.setData(myFhirCtx, attachment, bytes);
}
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment);
}
private enum ModeEnum {
SNAPSHOT, ADD, REMOVE
}
public static String stripPath(String thePath) {
@VisibleForTesting
static void setTransferSizeLimitForUnitTest(long theTransferSizeLimit) {
if (theTransferSizeLimit <= 0) {
ourTransferSizeLimit = DEFAULT_TRANSFER_SIZE_LIMIT;
}else {
ourTransferSizeLimit = theTransferSizeLimit;
}
}
static String stripPath(String thePath) {
String retVal = thePath;
if (retVal.contains("/")) {
retVal = retVal.substring(retVal.lastIndexOf("/"));

View File

@ -37,6 +37,12 @@
<logger name="ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<logger name="ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl" additivity="false" level="info">
<appender-ref ref="STDOUT" />
</logger>
<!--
Always log the migrator

View File

@ -1,11 +1,10 @@
package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.BaseTest;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.term.UploadStatistics;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import com.google.common.base.Charsets;
@ -185,6 +184,37 @@ public class UploadTerminologyCommandTest extends BaseTest {
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
/**
* When transferring large files, we use a local file to store the binary instead of
* using HTTP to transfer a giant base 64 encoded attachment. Hopefully we can
* replace this with a bulk data import at some point when that gets implemented.
*/
@Test
public void testSnapshotLargeFile() throws IOException {
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(10);
writeConceptAndHierarchyFiles();
when(myTermLoaderSvc.loadCustom(any(), anyList(), any())).thenReturn(new UploadStatistics(100, new IdType("CodeSystem/101")));
App.main(new String[]{
UploadTerminologyCommand.UPLOAD_TERMINOLOGY,
"-v", "r4",
"-m", "SNAPSHOT",
"-t", "http://localhost:" + myPort,
"-u", "http://foo",
"-d", myConceptsFileName,
"-d", myHierarchyFileName
});
verify(myTermLoaderSvc, times(1)).loadCustom(any(), myDescriptorListCaptor.capture(), any());
List<ITermLoaderSvc.FileDescriptor> listOfDescriptors = myDescriptorListCaptor.getValue();
assertEquals(1, listOfDescriptors.size());
assertThat(listOfDescriptors.get(0).getFilename(), matchesPattern(".*\\.zip$"));
assertThat(IOUtils.toByteArray(listOfDescriptors.get(0).getInputStream()).length, greaterThan(100));
}
private void writeConceptAndHierarchyFiles() throws IOException {
try (FileWriter w = new FileWriter(myConceptsFile, false)) {
@ -229,6 +259,8 @@ public class UploadTerminologyCommandTest extends BaseTest {
FileUtils.deleteQuietly(myConceptsFile);
FileUtils.deleteQuietly(myHierarchyFile);
FileUtils.deleteQuietly(myArchiveFile);
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(-1);
}
@Before

View File

@ -290,7 +290,7 @@ public class ValidatorExamples {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
// TODO: implement (or return null if your implementation does not support this function)
return null;
}

View File

@ -123,7 +123,7 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -74,6 +74,10 @@ public class DaoConfig {
)));
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
private static final int DEFAULT_EXPUNGE_BATCH_SIZE = 800;
// update setter javadoc if default changes
public static final int DEFAULT_MAX_EXPANSION_SIZE = 1000;
private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/**
@ -115,10 +119,7 @@ public class DaoConfig {
* update setter javadoc if default changes
*/
private boolean myIndexContainedResources = true;
/**
* update setter javadoc if default changes
*/
private int myMaximumExpansionSize = 5000;
private int myMaximumExpansionSize = DEFAULT_MAX_EXPANSION_SIZE;
private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC;
/**
@ -564,8 +565,12 @@ public class DaoConfig {
}
/**
* Sets the maximum number of codes that will be added to a valueset expansion before
* the operation will be failed as too costly
* Sets the maximum number of codes that will be added to an in-memory valueset expansion before
* the operation will be failed as too costly. Note that this setting applies only to
* in-memory expansions and does not apply to expansions that are being pre-calculated.
* <p>
* The default value for this setting is 1000.
* </p>
*/
public void setMaximumExpansionSize(int theMaximumExpansionSize) {
Validate.isTrue(theMaximumExpansionSize > 0, "theMaximumExpansionSize must be > 0");

View File

@ -41,8 +41,8 @@ public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system AND c.myCode = :code")
Optional<TermConcept> findByCodeSystemAndCode(@Param("code_system") TermCodeSystemVersion theCodeSystem, @Param("code") String theCode);
@Query("SELECT t FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<TermConcept> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT t.myId FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system")
List<TermConcept> findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem);
@ -50,8 +50,4 @@ public interface ITermConceptDao extends JpaRepository<TermConcept, Long> {
@Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null")
Page<TermConcept> findResourcesRequiringReindexing(Pageable thePageRequest);
@Query("UPDATE TermConcept t SET t.myIndexStatus = null")
@Modifying
int markAllForReindexing();
}

View File

@ -29,8 +29,8 @@ import org.springframework.data.repository.query.Param;
public interface ITermConceptDesignationDao extends JpaRepository<TermConceptDesignation, Long> {
@Query("SELECT t FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Slice<TermConceptDesignation> findByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid);
@Query("SELECT t.myId FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid);
@Query("SELECT COUNT(t) FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Integer countByCodeSystemVersion(@Param("csv_pid") Long thePid);

View File

@ -37,7 +37,6 @@ public interface ITermConceptParentChildLinkDao extends JpaRepository<TermConcep
@Query("SELECT t.myParentPid FROM TermConceptParentChildLink t WHERE t.myChildPid = :child_pid")
Collection<Long> findAllWithChild(@Param("child_pid") Long theConceptPid);
@Query("SELECT t FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<TermConceptParentChildLink> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT t.myPid FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
}

View File

@ -29,8 +29,8 @@ import org.springframework.data.repository.query.Param;
public interface ITermConceptPropertyDao extends JpaRepository<TermConceptProperty, Long> {
@Query("SELECT t FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Slice<TermConceptProperty> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT t.myId FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT COUNT(t) FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);

View File

@ -93,7 +93,7 @@ public class JpaValidationSupportDstu3 extends BaseJpaValidationSupport implemen
@Override
@Transactional(value = TxType.SUPPORTS)
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -99,7 +99,7 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> imple
}
}
if (allSystemsAreSuppportedByTerminologyService) {
return myTerminologySvc.expandValueSet(theSource);
return myTerminologySvc.expandValueSetInMemory(theSource, null);
}
HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport);
@ -111,12 +111,6 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> imple
return retVal;
// ValueSetExpansionComponent expansion = outcome.getValueset().getExpansion();
//
// ValueSet retVal = new ValueSet();
// retVal.getMeta().setLastUpdated(new Date());
// retVal.setExpansion(expansion);
// return retVal;
}
private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) {

View File

@ -85,7 +85,7 @@ public class JpaValidationSupportR4 extends BaseJpaValidationSupport implements
@Override
@Transactional(value = TxType.SUPPORTS)
public IValidationSupport.CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public IValidationSupport.CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theSystemUrl) {
return null;
}

View File

@ -87,7 +87,7 @@ public class JpaValidationSupportR5 extends BaseJpaValidationSupport implements
@Override
@Transactional(value = TxType.SUPPORTS)
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theSystemUrl) {
return null;
}

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.entity;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
@ -407,4 +408,7 @@ public class TermConcept implements Serializable {
}
public VersionIndependentConcept toVersionIndependentConcept() {
return new VersionIndependentConcept(myCodeSystem.getCodeSystem().getCodeSystemUri(), myCode);
}
}

View File

@ -29,7 +29,6 @@ import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.AttachmentUtil;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.ValidateUtil;
@ -40,7 +39,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
@ -86,7 +88,6 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
public IBaseParameters uploadSnapshot(
HttpServletRequest theServletRequest,
@OperationParam(name = PARAM_SYSTEM, min = 1, typeName = "uri") IPrimitiveType<String> theCodeSystemUrl,
@OperationParam(name = "localfile", min = 1, max = OperationParam.MAX_UNLIMITED, typeName = "string") List<IPrimitiveType<String>> theLocalFile,
@OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List<ICompositeType> theFiles,
RequestDetails theRequestDetails
) {
@ -97,66 +98,15 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
throw new InvalidRequestException("Missing mandatory parameter: " + PARAM_SYSTEM);
}
if (theLocalFile == null || theLocalFile.size() == 0) {
if (theFiles == null || theFiles.size() == 0) {
throw new InvalidRequestException("No 'localfile' or 'package' parameter, or package had no data");
}
for (ICompositeType next : theFiles) {
if (theFiles == null || theFiles.size() == 0) {
throw new InvalidRequestException("No '" + PARAM_FILE + "' parameter, or package had no data");
}
for (ICompositeType next : theFiles) {
ValidateUtil.isTrueOrThrowInvalidRequest(myCtx.getElementDefinition(next.getClass()).getName().equals("Attachment"), "Package must be of type Attachment");
}
}
try {
List<ITermLoaderSvc.FileDescriptor> localFiles = new ArrayList<>();
if (theLocalFile != null && theLocalFile.size() > 0) {
for (IPrimitiveType<String> nextLocalFile : theLocalFile) {
if (isNotBlank(nextLocalFile.getValue())) {
ourLog.info("Reading in local file: {}", nextLocalFile.getValue());
File nextFile = new File(nextLocalFile.getValue());
if (!nextFile.exists() || !nextFile.isFile()) {
throw new InvalidRequestException("Unknown file: " + nextFile.getName());
}
localFiles.add(new ITermLoaderSvc.FileDescriptor() {
@Override
public String getFilename() {
return nextFile.getAbsolutePath();
}
@Override
public InputStream getInputStream() {
try {
return new FileInputStream(nextFile);
} catch (FileNotFoundException theE) {
throw new InternalErrorException(theE);
}
}
});
}
}
}
if (theFiles != null) {
for (ICompositeType nextPackage : theFiles) {
final String url = AttachmentUtil.getOrCreateUrl(myCtx, nextPackage).getValueAsString();
if (isBlank(url)) {
throw new UnprocessableEntityException("Package is missing mandatory url element");
}
localFiles.add(new ITermLoaderSvc.FileDescriptor() {
@Override
public String getFilename() {
return url;
}
@Override
public InputStream getInputStream() {
byte[] data = AttachmentUtil.getOrCreateData(myCtx, nextPackage).getValue();
return new ByteArrayInputStream(data);
}
});
}
}
List<ITermLoaderSvc.FileDescriptor> localFiles = convertAttachmentsToFileDescriptors(theFiles);
String codeSystemUrl = theCodeSystemUrl.getValue();
codeSystemUrl = trim(codeSystemUrl);
@ -266,12 +216,30 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
private List<ITermLoaderSvc.FileDescriptor> convertAttachmentsToFileDescriptors(@OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List<ICompositeType> theFiles) {
List<ITermLoaderSvc.FileDescriptor> files = new ArrayList<>();
for (ICompositeType next : theFiles) {
byte[] nextData = AttachmentUtil.getOrCreateData(myCtx, next).getValue();
String nextUrl = AttachmentUtil.getOrCreateUrl(myCtx, next).getValue();
ValidateUtil.isTrueOrThrowInvalidRequest(nextData != null && nextData.length > 0, "Missing Attachment.data value");
ValidateUtil.isNotBlankOrThrowUnprocessableEntity(nextUrl, "Missing Attachment.url value");
files.add(new ITermLoaderSvc.ByteArrayFileDescriptor(nextUrl, nextData));
byte[] nextData;
if (nextUrl.startsWith("localfile:")) {
String nextLocalFile = nextUrl.substring("localfile:".length());
if (isNotBlank(nextLocalFile)) {
ourLog.info("Reading in local file: {}", nextLocalFile);
File nextFile = new File(nextLocalFile);
if (!nextFile.exists() || !nextFile.isFile()) {
throw new InvalidRequestException("Unknown file: " + nextFile.getName());
}
files.add(new FileBackedFileDescriptor(nextFile));
}
} else {
nextData = AttachmentUtil.getOrCreateData(myCtx, next).getValue();
ValidateUtil.isTrueOrThrowInvalidRequest(nextData != null && nextData.length > 0, "Missing Attachment.data value");
files.add(new ITermLoaderSvc.ByteArrayFileDescriptor(nextUrl, nextData));
}
}
return files;
}
@ -284,4 +252,25 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
}
private static class FileBackedFileDescriptor implements ITermLoaderSvc.FileDescriptor {
private final File myNextFile;
public FileBackedFileDescriptor(File theNextFile) {
myNextFile = theNextFile;
}
@Override
public String getFilename() {
return myNextFile.getAbsolutePath();
}
@Override
public InputStream getInputStream() {
try {
return new FileInputStream(myNextFile);
} catch (FileNotFoundException theE) {
throw new InternalErrorException(theE);
}
}
}
}

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.term;
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
@ -30,10 +31,11 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -59,7 +61,9 @@ import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome;
@ -75,6 +79,8 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NoRollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
@ -151,6 +157,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
private ITermDeferredStorageSvc myDeferredStorageSvc;
@Autowired(required = false)
private ITermCodeSystemStorageSvc myConceptStorageSvc;
private IContextValidationSupport myValidationSupport;
private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) {
String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri();
@ -193,7 +200,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (retVal) {
if (theSetToPopulate.size() >= myDaoConfig.getMaximumExpansionSize()) {
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize());
throw new InvalidRequestException(msg);
throw new ExpansionTooCostlyException(msg);
}
}
return retVal;
@ -232,7 +239,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
myTranslationWithReverseCache.invalidateAll();
}
public void deleteConceptMap(ResourceTable theResourceTable) {
// Get existing entity so it can be deleted.
Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(theResourceTable.getId());
@ -288,18 +294,18 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
deleteValueSet(theResourceTable);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public ValueSet expandValueSet(ValueSet theValueSetToExpand) {
public ValueSet expandValueSetInMemory(ValueSet theValueSetToExpand, VersionIndependentConcept theWantConceptOrNull) {
ValueSetExpansionComponentWithConceptAccumulator expansionComponent = new ValueSetExpansionComponentWithConceptAccumulator();
int maxCapacity = myDaoConfig.getMaximumExpansionSize();
ValueSetExpansionComponentWithConceptAccumulator expansionComponent = new ValueSetExpansionComponentWithConceptAccumulator(myContext, maxCapacity);
expansionComponent.setIdentifier(UUID.randomUUID().toString());
expansionComponent.setTimestamp(new Date());
AtomicInteger codeCounter = new AtomicInteger(0);
expandValueSet(theValueSetToExpand, expansionComponent, codeCounter);
expandValueSet(theValueSetToExpand, expansionComponent, codeCounter, theWantConceptOrNull);
expansionComponent.setTotal(codeCounter.get());
@ -327,7 +333,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (!optionalTermValueSet.isPresent()) {
ourLog.warn("ValueSet is not present in terminology tables. Will perform in-memory expansion without parameters. {}", getValueSetInfo(theValueSetToExpand));
return expandValueSet(theValueSetToExpand); // In-memory expansion.
return expandValueSetInMemory(theValueSetToExpand, null); // In-memory expansion.
}
TermValueSet termValueSet = optionalTermValueSet.get();
@ -335,7 +341,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (termValueSet.getExpansionStatus() != TermValueSetPreExpansionStatusEnum.EXPANDED) {
ourLog.warn("{} is present in terminology tables but not ready for persistence-backed invocation of operation $expand. Will perform in-memory expansion without parameters. Current status: {} | {}",
getValueSetInfo(theValueSetToExpand), termValueSet.getExpansionStatus().name(), termValueSet.getExpansionStatus().getDescription());
return expandValueSet(theValueSetToExpand); // In-memory expansion.
return expandValueSetInMemory(theValueSetToExpand, null); // In-memory expansion.
}
ValueSet.ValueSetExpansionComponent expansionComponent = new ValueSet.ValueSetExpansionComponent();
@ -431,11 +437,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0));
expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0), null);
}
@SuppressWarnings("ConstantConditions")
private void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter) {
private void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator, AtomicInteger theCodeCounter, VersionIndependentConcept theWantConceptOrNull) {
Set<String> addedCodes = new HashSet<>();
StopWatch sw = new StopWatch();
@ -449,7 +455,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
int queryIndex = i;
Boolean shouldContinue = myTxTemplate.execute(t -> {
boolean add = true;
return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter, queryIndex);
return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter, queryIndex, theWantConceptOrNull);
});
if (!shouldContinue) {
break;
@ -457,6 +463,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
}
}
// If the accumulator filled up, abort
if (theValueSetCodeAccumulator.getCapacityRemaining() != null && theValueSetCodeAccumulator.getCapacityRemaining() <= 0) {
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize());
throw new ExpansionTooCostlyException(msg);
}
// Handle excludes
ourLog.debug("Handling excludes");
for (ValueSet.ConceptSetComponent exclude : theValueSetToExpand.getCompose().getExclude()) {
@ -464,7 +476,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
int queryIndex = i;
Boolean shouldContinue = myTxTemplate.execute(t -> {
boolean add = false;
return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, exclude, add, theCodeCounter, queryIndex);
return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, exclude, add, theCodeCounter, queryIndex, null);
});
if (!shouldContinue) {
break;
@ -515,8 +527,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return sb.toString();
}
protected List<VersionIndependentConcept> expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4) {
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSet(theValueSetToExpandR4).getExpansion();
protected List<VersionIndependentConcept> expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4, VersionIndependentConcept theWantConceptOrNull) {
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSetInMemory(theValueSetToExpandR4, theWantConceptOrNull).getExpansion();
ArrayList<VersionIndependentConcept> retVal = new ArrayList<>();
for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent nextContains : expandedR4.getContains()) {
@ -529,13 +541,18 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
/**
* @return Returns true if there are potentially more results to process.
*/
private Boolean expandValueSetHandleIncludeOrExclude(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex) {
private Boolean expandValueSetHandleIncludeOrExclude(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, ValueSet.ConceptSetComponent theIncludeOrExclude, boolean theAdd, AtomicInteger theCodeCounter, int theQueryIndex, VersionIndependentConcept theWantConceptOrNull) {
String system = theIncludeOrExclude.getSystem();
boolean hasSystem = isNotBlank(system);
boolean hasValueSet = theIncludeOrExclude.getValueSet().size() > 0;
if (hasSystem) {
if (theWantConceptOrNull != null && !system.equals(theWantConceptOrNull.getSystem())) {
return false;
}
ourLog.info("Starting {} expansion around CodeSystem: {}", (theAdd ? "inclusion" : "exclusion"), system);
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(system);
@ -561,6 +578,10 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery());
if (theWantConceptOrNull != null) {
bool.must(qb.keyword().onField("myCode").matching(theWantConceptOrNull.getCode()).createQuery());
}
/*
* Filters
*/
@ -603,6 +624,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
AtomicInteger count = new AtomicInteger(0);
int maxResultsPerBatch = 10000;
/*
* If the accumulator is bounded, we may reduce the size of the query to
* Lucene in order to be more efficient.
*/
if (theAdd) {
Integer accumulatorCapacityRemaining = theValueSetCodeAccumulator.getCapacityRemaining();
if (accumulatorCapacityRemaining != null) {
maxResultsPerBatch = Math.min(maxResultsPerBatch, accumulatorCapacityRemaining + 1);
}
if (maxResultsPerBatch <= 0) {
return false;
}
}
jpaQuery.setMaxResults(maxResultsPerBatch);
jpaQuery.setFirstResult(theQueryIndex * maxResultsPerBatch);
@ -618,7 +654,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
count.incrementAndGet();
countForBatch.incrementAndGet();
TermConcept concept = (TermConcept) next;
addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, concept, theAdd, theCodeCounter);
try {
addCodeIfNotAlreadyAdded(theValueSetCodeAccumulator, theAddedCodes, concept, theAdd, theCodeCounter);
} catch (ExpansionTooCostlyException e) {
return false;
}
}
ourLog.info("Batch expansion for {} with starting index of {} produced {} results in {}ms", (theAdd ? "inclusion" : "exclusion"), firstResult, countForBatch, swForBatch.getMillis());
@ -641,20 +681,26 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (!theIncludeOrExclude.getConcept().isEmpty()) {
for (ValueSet.ConceptReferenceComponent next : theIncludeOrExclude.getConcept()) {
String nextCode = next.getCode();
if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) {
CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode);
if (code != null) {
if (theAdd && theAddedCodes.add(system + "|" + nextCode)) {
theValueSetCodeAccumulator.includeConcept(system, nextCode, code.getDisplay());
}
if (!theAdd && theAddedCodes.remove(system + "|" + nextCode)) {
theValueSetCodeAccumulator.excludeConcept(system, nextCode);
if (theWantConceptOrNull == null || theWantConceptOrNull.getCode().equals(nextCode)) {
if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) {
CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode);
if (code != null) {
if (theAdd && theAddedCodes.add(system + "|" + nextCode)) {
theValueSetCodeAccumulator.includeConcept(system, nextCode, code.getDisplay());
}
if (!theAdd && theAddedCodes.remove(system + "|" + nextCode)) {
theValueSetCodeAccumulator.excludeConcept(system, nextCode);
}
}
}
}
}
} else {
List<CodeSystem.ConceptDefinitionComponent> concept = codeSystemFromContext.getConcept();
concept = concept
.stream()
.filter(t -> theWantConceptOrNull == null || t.getCode().equals(theWantConceptOrNull.getCode()))
.collect(Collectors.toList());
addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, system, concept, theAdd);
}
@ -1217,7 +1263,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return myCodeSystemDao.findByCodeSystemUri(theSystem);
}
@Override
public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
myApplicationContext = theApplicationContext;
@ -1226,7 +1271,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
@PostConstruct
public void start() {
myValueSetResourceDao = myApplicationContext.getBean(IFhirResourceDaoValueSet.class);
myTxTemplate = new TransactionTemplate(myTransactionManager);
if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
myValidationSupport = myApplicationContext.getBean(IContextValidationSupport.class);
}
RuleBasedTransactionAttribute rules = new RuleBasedTransactionAttribute();
rules.getRollbackRules().add(new NoRollbackRuleAttribute(ExpansionTooCostlyException.class));
myTxTemplate = new TransactionTemplate(myTransactionManager, rules);
}
@PostConstruct
@ -1492,6 +1542,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return new IFhirResourceDaoCodeSystem.SubsumesResult(subsumes);
}
protected abstract ValueSet toCanonicalValueSet(IBaseResource theValueSet);
protected IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
return txTemplate.execute(t -> {
@ -1753,11 +1805,36 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return retVal;
}
protected void throwInvalidValueSet(String theValueSet) {
void throwInvalidValueSet(String theValueSet) {
throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(theValueSet));
}
Optional<VersionIndependentConcept> validateCodeInValueSet(String theValueSetUrl, String theCodeSystem, String theCode) {
IBaseResource valueSet = myValidationSupport.fetchValueSet(myContext, theValueSetUrl);
// If we don't have a PID, this came from some source other than the JPA
// database, so we don't need to check if it's pre-expanded or not
if (valueSet instanceof IAnyResource) {
Long pid = IDao.RESOURCE_PID.get((IAnyResource) valueSet);
if (pid != null) {
if (isValueSetPreExpandedForCodeValidation(valueSet)) {
ValidateCodeResult outcome = validateCodeIsInPreExpandedValueSet(valueSet, theCodeSystem, theCode, null, null, null);
if (outcome != null && outcome.isResult()) {
return Optional.of(new VersionIndependentConcept(theCodeSystem, theCode));
}
}
}
}
ValueSet canonicalValueSet = toCanonicalValueSet(valueSet);
VersionIndependentConcept wantConcept = new VersionIndependentConcept(theCodeSystem, theCode);
List<VersionIndependentConcept> expansionOutcome = expandValueSetAndReturnVersionIndependentConcepts(canonicalValueSet, wantConcept);
return expansionOutcome
.stream()
.filter(t -> t.getSystem().equals(theCodeSystem) && t.getCode().equals(theCode))
.findFirst();
}
public static class PreExpandValueSetsJob implements Job {
@Autowired
@ -1881,5 +1958,4 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
}
}

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import javax.annotation.Nullable;
import java.util.Collection;
public interface IValueSetConceptAccumulator {
@ -32,4 +33,9 @@ public interface IValueSetConceptAccumulator {
void excludeConcept(String theSystem, String theCode);
@Nullable
default Integer getCapacityRemaining() {
return null;
}
}

View File

@ -451,7 +451,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Parent/Child links
{
String descriptor = "parent/child links";
Supplier<Slice<TermConceptParentChildLink>> loader = () -> myConceptParentChildLinkDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Slice<Long>> loader = () -> myConceptParentChildLinkDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptParentChildLinkDao);
}
@ -459,7 +459,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Properties
{
String descriptor = "concept properties";
Supplier<Slice<TermConceptProperty>> loader = () -> myConceptPropertyDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Slice<Long>> loader = () -> myConceptPropertyDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptPropertyDao);
}
@ -467,7 +467,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Designations
{
String descriptor = "concept designations";
Supplier<Slice<TermConceptDesignation>> loader = () -> myConceptDesignationDao.findByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Slice<Long>> loader = () -> myConceptDesignationDao.findIdsByCodeSystemVersion(page1000, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDesignationDao);
}
@ -477,7 +477,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
String descriptor = "concepts";
// For some reason, concepts are much slower to delete, so use a smaller batch size
PageRequest page100 = PageRequest.of(0, 100);
Supplier<Slice<TermConcept>> loader = () -> myConceptDao.findByCodeSystemVersion(page100, theCodeSystemVersionPid);
Supplier<Slice<Long>> loader = () -> myConceptDao.findIdsByCodeSystemVersion(page100, theCodeSystemVersionPid);
Supplier<Integer> counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDao);
}
@ -718,14 +718,14 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
}
private <T> void doDelete(String theDescriptor, Supplier<Slice<T>> theLoader, Supplier<Integer> theCounter, JpaRepository<T, ?> theDao) {
private <T> void doDelete(String theDescriptor, Supplier<Slice<Long>> theLoader, Supplier<Integer> theCounter, JpaRepository<T, Long> theDao) {
int count;
ourLog.info(" * Deleting {}", theDescriptor);
int totalCount = theCounter.get();
StopWatch sw = new StopWatch();
count = 0;
while (true) {
Slice<T> link = theLoader.get();
Slice<Long> link = theLoader.get();
if (!link.hasContent()) {
break;
}
@ -733,7 +733,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.execute(t -> {
theDao.deleteAll(link);
link.forEach(id -> theDao.deleteById(id));
return null;
});

View File

@ -183,6 +183,15 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
return;
}
for (int i = 0; i < 10; i++) {
if (!isDeferredConcepts() &&
!isConceptLinksToSaveLater() &&
!isDeferredValueSets() &&
!isDeferredConceptMaps()) {
return;
}
TransactionTemplate tt = new TransactionTemplate(myTransactionMgr);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
if (isDeferredConceptsOrConceptLinksToSaveLater()) {
@ -206,6 +215,7 @@ public class TermDeferredStorageSvcImpl implements ITermDeferredStorageSvc {
}
}
}
@Override
public boolean isStorageQueueEmpty() {

View File

@ -72,6 +72,11 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl {
throw new UnsupportedOperationException();
}
@Override
protected ValueSet toCanonicalValueSet(IBaseResource theValueSet) {
throw new UnsupportedOperationException();
}
@Override
public IBaseResource expandValueSet(IBaseResource theValueSetToExpand) {
throw new UnsupportedOperationException();

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3;
@ -103,8 +102,8 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = super.expandValueSet(valueSetToExpandR4).getExpansion();
valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = super.expandValueSetInMemory(valueSetToExpandR4, null).getExpansion();
return VersionConvertor_30_40.convertValueSetExpansionComponent(expandedR4);
} catch (FHIRException e) {
throw new InternalErrorException(e);
@ -117,21 +116,28 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(valueSetToExpandR4);
valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSetInMemory(valueSetToExpandR4, null);
return VersionConvertor_30_40.convertValueSet(expandedR4);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
@Override
protected org.hl7.fhir.r4.model.ValueSet toCanonicalValueSet(IBaseResource theValueSet) throws FHIRException {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet((ValueSet) theValueSet);
return valueSetToExpandR4;
}
@Override
public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) {
ValueSet valueSetToExpand = (ValueSet) theInput;
try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand);
valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(valueSetToExpandR4, theOffset, theCount);
return VersionConvertor_30_40.convertValueSet(expandedR4);
} catch (FHIRException e) {
@ -145,7 +151,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand);
valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand);
super.expandValueSet(valueSetToExpandR4, theValueSetCodeAccumulator);
} catch (FHIRException e) {
throw new InternalErrorException(e);
@ -162,13 +168,13 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
try {
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(vs);
valueSetToExpandR4 = toCanonicalValueSet(vs);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
return expandValueSetAndReturnVersionIndependentConcepts(valueSetToExpandR4);
return expandValueSetAndReturnVersionIndependentConcepts(valueSetToExpandR4, null);
}
@Override
@ -262,7 +268,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
org.hl7.fhir.r4.model.ValueSet valueSetR4;
try {
valueSetR4 = VersionConvertor_30_40.convertValueSet(valueSet);
valueSetR4 = toCanonicalValueSet(valueSet);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
@ -277,25 +283,30 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return txTemplate.execute(t->{
Optional<TermConcept> codeOpt = myTerminologySvc.findCode(theCodeSystem, theCode);
if (codeOpt.isPresent()) {
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
TermConcept code = codeOpt.get();
def.setCode(code.getCode());
def.setDisplay(code.getDisplay());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def);
retVal.setProperties(code.toValidationProperties());
retVal.setCodeSystemName(code.getCodeSystemVersion().getCodeSystem().getName());
return retVal;
}
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode);
});
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c->c.toVersionIndependentConcept()));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
def.setCode(code.getCode());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def);
return retVal;
}
return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override

View File

@ -3,9 +3,9 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR4;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.util.CoverageIgnore;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -21,6 +21,7 @@ import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -90,13 +91,13 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
super.throwInvalidValueSet(theValueSet);
}
return expandValueSetAndReturnVersionIndependentConcepts(vs);
return expandValueSetAndReturnVersionIndependentConcepts(vs, null);
}
@Override
public IBaseResource expandValueSet(IBaseResource theInput) {
ValueSet valueSetToExpand = (ValueSet) theInput;
return super.expandValueSet(valueSetToExpand);
return super.expandValueSetInMemory(valueSetToExpand, null);
}
@Override
@ -111,11 +112,12 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator);
}
@Transactional(dontRollbackOn = {ExpansionTooCostlyException.class})
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSet valueSetToExpand = new ValueSet();
valueSetToExpand.getCompose().addInclude(theInclude);
ValueSet expanded = super.expandValueSet(valueSetToExpand);
ValueSet expanded = super.expandValueSetInMemory(valueSetToExpand, null);
return new ValueSetExpander.ValueSetExpansionOutcome(expanded);
}
@ -214,25 +216,37 @@ public class TermReadSvcR4 extends BaseTermReadSvcImpl implements ITermReadSvcR4
return null;
}
@Override
protected ValueSet toCanonicalValueSet(IBaseResource theValueSet) {
return (ValueSet) theValueSet;
}
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return txTemplate.execute(t-> {
Optional<TermConcept> codeOpt = findCode(theCodeSystem, theCode);
if (codeOpt.isPresent()) {
TermConcept code = codeOpt.get();
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c->c.toVersionIndependentConcept()));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
def.setCode(code.getCode());
def.setDisplay(code.getDisplay());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def);
retVal.setProperties(code.toValidationProperties());
return retVal;
}
return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode);
});
}
@Override

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
import ca.uhn.fhir.util.CoverageIgnore;
@ -91,26 +90,26 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
super.throwInvalidValueSet(theValueSet);
}
return expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR5));
return expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR5), null);
}
@Override
public IBaseResource expandValueSet(IBaseResource theInput) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet((ValueSet) theInput);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = toCanonicalValueSet(theInput);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSetInMemory(valueSetToExpand, null);
return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR4);
}
@Override
public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet((ValueSet) theInput);
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = toCanonicalValueSet(theInput);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSet(valueSetToExpand, theOffset, theCount);
return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR4);
}
@Override
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet((ValueSet) theValueSetToExpand);
org.hl7.fhir.r4.model.ValueSet valueSetToExpand = toCanonicalValueSet(theValueSetToExpand);
super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator);
}
@ -118,7 +117,7 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSet valueSetToExpand = new ValueSet();
valueSetToExpand.getCompose().addInclude(theInclude);
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetToExpand));
org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSetInMemory(org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetToExpand), null);
return new ValueSetExpander.ValueSetExpansionOutcome(org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(expandedR4));
}
@ -221,23 +220,30 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
@CoverageIgnore
@Override
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return txTemplate.execute(t -> {
Optional<TermConcept> codeOpt = findCode(theCodeSystem, theCode);
if (codeOpt.isPresent()) {
TermConcept code = codeOpt.get();
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
def.setCode(code.getCode());
def.setDisplay(code.getDisplay());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def);
retVal.setProperties(code.toValidationProperties());
return retVal;
}
public IValidationSupport.CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
Optional<VersionIndependentConcept> codeOpt = Optional.empty();
boolean haveValidated = false;
return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode);
});
if (isNotBlank(theValueSetUrl)) {
codeOpt = super.validateCodeInValueSet(theValueSetUrl, theCodeSystem, theCode);
haveValidated = true;
}
if (!haveValidated) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
codeOpt = txTemplate.execute(t -> findCode(theCodeSystem, theCode).map(c->c.toVersionIndependentConcept()));
}
if (codeOpt != null && codeOpt.isPresent()) {
VersionIndependentConcept code = codeOpt.get();
ConceptDefinitionComponent def = new ConceptDefinitionComponent();
def.setCode(code.getCode());
IValidationSupport.CodeValidationResult retVal = new IValidationSupport.CodeValidationResult(def);
return retVal;
}
return new IValidationSupport.CodeValidationResult(IssueSeverity.ERROR, "Unknown code {" + theCodeSystem + "}" + theCode);
}
@Override
@ -249,7 +255,7 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
public ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet;
org.hl7.fhir.r4.model.ValueSet valueSetR4 = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSet);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet);
Coding coding = (Coding) theCoding;
org.hl7.fhir.r4.model.Coding codingR4 = null;
@ -269,11 +275,16 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
return super.validateCodeIsInPreExpandedValueSet(valueSetR4, theSystem, theCode, theDisplay, codingR4, codeableConceptR4);
}
@Override
protected org.hl7.fhir.r4.model.ValueSet toCanonicalValueSet(IBaseResource theValueSet) throws org.hl7.fhir.exceptions.FHIRException {
return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet((ValueSet) theValueSet);
}
@Override
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet;
org.hl7.fhir.r4.model.ValueSet valueSetR4 = org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSet);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = toCanonicalValueSet(valueSet);
return super.isValueSetPreExpandedForCodeValidation(valueSetR4);
}
}

View File

@ -20,22 +20,40 @@ package ca.uhn.fhir.jpa.term;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import ca.uhn.fhir.jpa.term.ex.ExpansionTooCostlyException;
import ca.uhn.fhir.model.api.annotation.Block;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nullable;
import java.util.Collection;
@Block()
public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetConceptAccumulator {
private final int myMaxResults = 50000;
private final int myMaxCapacity;
private final FhirContext myContext;
private int myConceptsCount;
public ValueSetExpansionComponentWithConceptAccumulator() {
/**
* Constructor
*
* @param theMaxCapacity The maximum number of results this accumulator will accept before throwing
* an {@link InternalErrorException}
*/
ValueSetExpansionComponentWithConceptAccumulator(FhirContext theContext, int theMaxCapacity) {
myContext = theContext;
myMaxCapacity = theMaxCapacity;
myConceptsCount = 0;
}
@Nullable
@Override
public Integer getCapacityRemaining() {
return myMaxCapacity - myConceptsCount;
}
@Override
public void includeConcept(String theSystem, String theCode, String theDisplay) {
incrementConceptsCount();
@ -76,8 +94,9 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
}
private void incrementConceptsCount() {
if (++myConceptsCount > myMaxResults) {
throw new InternalErrorException("Expansion produced too many (>= " + myMaxResults + ") results");
if (++myConceptsCount > myMaxCapacity) {
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myMaxCapacity);
throw new ExpansionTooCostlyException(msg);
}
}
}

View File

@ -53,7 +53,7 @@ import java.util.Set;
*/
public interface ITermReadSvc {
ValueSet expandValueSet(ValueSet theValueSetToExpand);
ValueSet expandValueSetInMemory(ValueSet theValueSetToExpand, VersionIndependentConcept theWantConceptOrNull);
ValueSet expandValueSet(ValueSet theValueSetToExpand, int theOffset, int theCount);

View File

@ -0,0 +1,11 @@
package ca.uhn.fhir.jpa.term.ex;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class ExpansionTooCostlyException extends InternalErrorException {
public ExpansionTooCostlyException(String theMessage) {
super(theMessage);
}
}

View File

@ -4,7 +4,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.BaseTest;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.jpa.bulk.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.util.JpaConstants;

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -722,7 +723,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
try {
myObservationDao.search(params).size();
fail();
} catch (InvalidRequestException e) {
} catch (InternalErrorException e) {
assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage());
}
}

View File

@ -11,6 +11,7 @@ import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -837,7 +838,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
try {
myObservationDao.search(params).size();
fail();
} catch (InvalidRequestException e) {
} catch (InternalErrorException e) {
assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage());
}
}

View File

@ -2,7 +2,9 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -34,6 +36,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
@ -43,7 +46,182 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
@Autowired
private IValidatorModule myValidatorModule;
@Autowired
private ITermReadSvc myTerminologySvc;
private ITermReadSvc myTermReadSvc;
@Autowired
private ITermCodeSystemStorageSvc myTermCodeSystemStorageSvcc;
/**
* Create a loinc valueset that expands to more results than the expander is willing to do
* in memory, and make sure we can still validate correctly, even if we're using
* the in-memory expander
*/
@Test
public void testValidateCode_InMemoryExpansionAgainstHugeValueSet() throws Exception {
myDaoConfig.setPreExpandValueSets(false);
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
assertFalse(myTermReadSvc.isValueSetPreExpandedForCodeValidation(vs));
// Load the profile, which is just the Vital Signs profile modified to accept all loinc codes
// and not just certain ones
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
// Add a bunch of codes
CustomTerminologySet codesToAdd = new CustomTerminologySet();
for (int i = 0; i < 100; i++) {
codesToAdd.addRootConcept("CODE" + i, "Display " + i);
}
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://loinc.org", codesToAdd);
myDaoConfig.setMaximumExpansionSize(50);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
// Invalid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("non-existing-code").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult, and a code from this value set is required) (codes = http://loinc.org#non-existing-code)", oo.getIssueFirstRep().getDiagnostics());
// Valid code with no system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem(null).setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult, and a code from this value set is required) (codes = null#CODE3)", oo.getIssueFirstRep().getDiagnostics());
// Valid code with wrong system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://foo").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "Code http://foo/CODE3 was not validated because the code system is not present", oo.getIssueFirstRep().getDiagnostics());
// Code that exists but isn't in the valueset
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult, and a code from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/observation-category#vital-signs)", oo.getIssueFirstRep().getDiagnostics());
// Invalid code in built-in VS/CS
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "Unknown code: http://terminology.hl7.org/CodeSystem/observation-category / FOO", oo.getIssueFirstRep().getDiagnostics());
}
/**
* Create a loinc valueset that expands to more results than the expander is willing to do
* in memory, and make sure we can still validate correctly, even if we're using
* the in-memory expander
*/
@Test
public void testValidateCode_PreExpansionAgainstHugeValueSet() throws Exception {
myDaoConfig.setPreExpandValueSets(true);
// Add a bunch of codes
CustomTerminologySet codesToAdd = new CustomTerminologySet();
for (int i = 0; i < 100; i++) {
codesToAdd.addRootConcept("CODE" + i, "Display " + i);
}
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://loinc.org", codesToAdd);
// Create a valueset
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
myTermReadSvc.preExpandDeferredValueSetsToTerminologyTables();
await().until(()->myTermReadSvc.isValueSetPreExpandedForCodeValidation(vs));
// Load the profile, which is just the Vital Signs profile modified to accept all loinc codes
// and not just certain ones
StructureDefinition profile = loadResourceFromClasspath(StructureDefinition.class, "/r4/profile-vitalsigns-all-loinc.json");
myStructureDefinitionDao.create(profile, mySrd);
Observation obs = new Observation();
obs.getMeta().addProfile("http://example.com/fhir/StructureDefinition/vitalsigns-2");
obs.getText().setDivAsString("<div>Hello</div>");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs");
obs.setSubject(new Reference("Patient/123"));
obs.addPerformer(new Reference("Practitioner/123"));
obs.setEffective(DateTimeType.now());
obs.setStatus(ObservationStatus.FINAL);
obs.setValue(new StringType("This is the value"));
OperationOutcome oo;
// Valid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "No issues detected during validation", oo.getIssueFirstRep().getDiagnostics());
// Invalid code
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("non-existing-code").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult, and a code from this value set is required) (codes = http://loinc.org#non-existing-code)", oo.getIssueFirstRep().getDiagnostics());
// Valid code with no system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem(null).setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult, and a code from this value set is required) (codes = null#CODE3)", oo.getIssueFirstRep().getDiagnostics());
// Valid code with wrong system
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://foo").setCode("CODE3").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "Code http://foo/CODE3 was not validated because the code system is not present", oo.getIssueFirstRep().getDiagnostics());
// Code that exists but isn't in the valueset
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("vital-signs").setDisplay("Display 3");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "None of the codes provided are in the value set http://example.com/fhir/ValueSet/observation-vitalsignresult (http://example.com/fhir/ValueSet/observation-vitalsignresult, and a code from this value set is required) (codes = http://terminology.hl7.org/CodeSystem/observation-category#vital-signs)", oo.getIssueFirstRep().getDiagnostics());
// Invalid code in built-in VS/CS
obs.getText().setStatus(Narrative.NarrativeStatus.GENERATED);
obs.getCode().getCodingFirstRep().setSystem("http://loinc.org").setCode("CODE3").setDisplay("Display 3");
obs.getCategoryFirstRep().addCoding().setSystem("http://terminology.hl7.org/CodeSystem/observation-category").setCode("FOO");
oo = validateAndReturnOutcome(obs);
assertEquals(encode(oo), "Unknown code: http://terminology.hl7.org/CodeSystem/observation-category / FOO", oo.getIssueFirstRep().getDiagnostics());
}
private OperationOutcome validateAndReturnOutcome(Observation theObs) {
try {
MethodOutcome outcome = myObservationDao.validate(theObs, null, null, null, ValidationModeEnum.CREATE, null, mySrd);
return (OperationOutcome) outcome.getOperationOutcome();
} catch (PreconditionFailedException e) {
return (OperationOutcome) e.getOperationOutcome();
}
}
@Test
public void testValidateStructureDefinition() throws Exception {
@ -280,7 +458,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE);
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
}
@Test

View File

@ -2,6 +2,8 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -23,6 +25,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
@After
public void after() {
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
myDaoConfig.setMaximumExpansionSize(new DaoConfig().getMaximumExpansionSize());
}
@AfterClass
@ -230,37 +233,26 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
}
@Test
@Ignore
public void testExpandByIdentifier() {
ValueSet expanded = myValueSetDao.expandByIdentifier("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", "11378");
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
//@formatter:off
assertThat(resp, stringContainsInOrder(
"<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>"));
//@formatter:on
public void testExpandByValueSet_ExceedsMaxSize() {
// Add a bunch of codes
CustomTerminologySet codesToAdd = new CustomTerminologySet();
for (int i = 0; i < 100; i++) {
codesToAdd.addRootConcept("CODE" + i, "Display " + i);
}
myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://loinc.org", codesToAdd);
myDaoConfig.setMaximumExpansionSize(50);
assertThat(resp, not(containsString("<code value=\"8450-9\"/>")));
}
ValueSet vs = new ValueSet();
vs.setUrl("http://example.com/fhir/ValueSet/observation-vitalsignresult");
vs.getCompose().addInclude().setSystem("http://loinc.org");
myValueSetDao.create(vs);
/**
* This type of expansion doesn't really make sense..
*/
@Test
@Ignore
public void testExpandByValueSet() throws IOException {
ValueSet toExpand = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
ValueSet expanded = myValueSetDao.expand(toExpand, "11378");
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded);
ourLog.info(resp);
//@formatter:off
assertThat(resp, stringContainsInOrder(
"<code value=\"11378-7\"/>",
"<display value=\"Systolic blood pressure at First encounter\"/>"));
//@formatter:on
assertThat(resp, not(containsString("<code value=\"8450-9\"/>")));
try {
myValueSetDao.expand(vs, null);
fail();
} catch (InternalErrorException e) {
assertEquals("Expansion of ValueSet produced too many codes (maximum 50) - Operation aborted!", e.getMessage());
}
}

View File

@ -108,7 +108,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.execute();
fail();
} catch (InvalidRequestException e) {
assertEquals("HTTP 400 Bad Request: No 'localfile' or 'package' parameter, or package had no data", e.getMessage());
assertEquals("HTTP 400 Bad Request: No 'file' parameter, or package had no data", e.getMessage());
}
}
@ -163,7 +163,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.onType(CodeSystem.class)
.named("upload-external-code-system")
.withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.SCT_URI))
.andParameter("localfile", new StringType(tempFile.getAbsolutePath()))
.andParameter(TerminologyUploaderProvider.PARAM_FILE, new Attachment().setUrl("localfile:"+tempFile.getAbsolutePath()))
.execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.BaseTest;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.rest.api.server.RequestDetails;

View File

@ -0,0 +1,187 @@
package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion;
import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.model.codesystems.HttpVerb;
import org.junit.After;
import org.junit.Before;
import org.springframework.test.context.TestPropertySource;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull;
import java.io.IOException;
@TestPropertySource(properties = {
"scheduling_disabled=true"
})
public abstract class BaseTermR4Test extends BaseJpaR4Test {
IIdType myExtensionalCsId;
IIdType myExtensionalVsId;
Long myExtensionalCsIdOnResourceTable;
Long myExtensionalVsIdOnResourceTable;
@Before
public void before() {
myDaoConfig.setAllowExternalReferences(true);
}
@After
public void after() {
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE);
}
IIdType createCodeSystem() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setUrl(CS_URL);
codeSystem.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
codeSystem.setName("SYSTEM NAME");
codeSystem.setVersion("SYSTEM VERSION");
IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualified();
ResourceTable table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalArgumentException::new);
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
TermConcept parent;
parent = new TermConcept(cs, "ParentWithNoChildrenA");
cs.getConcepts().add(parent);
parent = new TermConcept(cs, "ParentWithNoChildrenB");
cs.getConcepts().add(parent);
parent = new TermConcept(cs, "ParentWithNoChildrenC");
cs.getConcepts().add(parent);
TermConcept parentA = new TermConcept(cs, "ParentA");
cs.getConcepts().add(parentA);
TermConcept childAA = new TermConcept(cs, "childAA");
parentA.addChild(childAA, TermConceptParentChildLink.RelationshipTypeEnum.ISA);
TermConcept childAAA = new TermConcept(cs, "childAAA");
childAAA.addPropertyString("propA", "valueAAA");
childAAA.addPropertyString("propB", "foo");
childAA.addChild(childAAA, TermConceptParentChildLink.RelationshipTypeEnum.ISA);
TermConcept childAAB = new TermConcept(cs, "childAAB");
childAAB.addPropertyString("propA", "valueAAB");
childAAB.addPropertyString("propB", "foo");
childAAB.addDesignation()
.setUseSystem("D1S")
.setUseCode("D1C")
.setUseDisplay("D1D")
.setValue("D1V");
childAA.addChild(childAAB, TermConceptParentChildLink.RelationshipTypeEnum.ISA);
TermConcept childAB = new TermConcept(cs, "childAB");
parentA.addChild(childAB, TermConceptParentChildLink.RelationshipTypeEnum.ISA);
TermConcept parentB = new TermConcept(cs, "ParentB");
cs.getConcepts().add(parentB);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
return id;
}
void loadAndPersistCodeSystemAndValueSet() throws IOException {
loadAndPersistCodeSystem();
loadAndPersistValueSet(HttpVerb.POST);
}
void loadAndPersistCodeSystemAndValueSetWithDesignations(HttpVerb theVerb) throws IOException {
loadAndPersistCodeSystemWithDesignations(theVerb);
loadAndPersistValueSet(theVerb);
}
void loadAndPersistCodeSystemAndValueSetWithDesignationsAndExclude(HttpVerb theVerb) throws IOException {
loadAndPersistCodeSystemWithDesignations(theVerb);
loadAndPersistValueSetWithExclude(theVerb);
}
void loadAndPersistCodeSystem() throws IOException {
CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs.xml");
codeSystem.setId("CodeSystem/cs");
persistCodeSystem(codeSystem, HttpVerb.POST);
}
void loadAndPersistCodeSystemWithDesignations(HttpVerb theVerb) throws IOException {
CodeSystem codeSystem = loadResourceFromClasspath(CodeSystem.class, "/extensional-case-3-cs-with-designations.xml");
codeSystem.setId("CodeSystem/cs");
persistCodeSystem(codeSystem, theVerb);
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void persistCodeSystem(CodeSystem theCodeSystem, HttpVerb theVerb) {
switch (theVerb) {
case POST:
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myExtensionalCsId = myCodeSystemDao.create(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
}
});
break;
case PUT:
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myExtensionalCsId = myCodeSystemDao.update(theCodeSystem, mySrd).getId().toUnqualifiedVersionless();
}
});
break;
default:
throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb);
}
myExtensionalCsIdOnResourceTable = myCodeSystemDao.readEntity(myExtensionalCsId, null).getId();
}
void loadAndPersistValueSet(HttpVerb theVerb) throws IOException {
ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs.xml");
valueSet.setId("ValueSet/vs");
persistValueSet(valueSet, theVerb);
}
private void loadAndPersistValueSetWithExclude(HttpVerb theVerb) throws IOException {
ValueSet valueSet = loadResourceFromClasspath(ValueSet.class, "/extensional-case-3-vs-with-exclude.xml");
valueSet.setId("ValueSet/vs");
persistValueSet(valueSet, theVerb);
}
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
private void persistValueSet(ValueSet theValueSet, HttpVerb theVerb) {
switch (theVerb) {
case POST:
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myExtensionalVsId = myValueSetDao.create(theValueSet, mySrd).getId().toUnqualifiedVersionless();
}
});
break;
case PUT:
new TransactionTemplate(myTxManager).execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(@Nonnull TransactionStatus theStatus) {
myExtensionalVsId = myValueSetDao.update(theValueSet, mySrd).getId().toUnqualifiedVersionless();
}
});
break;
default:
throw new IllegalArgumentException("HTTP verb is not supported: " + theVerb);
}
myExtensionalVsIdOnResourceTable = myValueSetDao.readEntity(myExtensionalVsId, null).getId();
}
}

View File

@ -38,12 +38,10 @@ import static org.junit.Assert.*;
public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(TerminologySvcImplDstu3Test.class);
@Rule
public final ExpectedException expectedException = ExpectedException.none();
private static final String CS_URL = "http://example.com/my_code_system";
private static final String CS_URL_2 = "http://example.com/my_code_system2";
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@After
public void after() {
@ -118,7 +116,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermConcept parentA = new TermConcept(cs, "CS2");
cs.getConcepts().add(parentA);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2, "SYSTEM NAME", "SYSTEM VERSION" , cs, table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL_2, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
}
@ -187,7 +185,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
code2.getDisplay());
cs.getConcepts().add(code4);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), LOINC_URI, "SYSTEM NAME", "SYSTEM VERSION" , cs, table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), LOINC_URI, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
});
}
@ -203,7 +201,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
TermCodeSystemVersion cs = new TermCodeSystemVersion();
cs.setResource(table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION" , cs, table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
// Update
cs = new TermCodeSystemVersion();
@ -212,7 +210,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
id = myCodeSystemDao.update(codeSystem, null, true, true, mySrd).getId().toUnqualified();
table = myResourceTableDao.findById(id.getIdPartAsLong()).orElseThrow(IllegalArgumentException::new);
cs.setResource(table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION" , cs, table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), CS_URL, "SYSTEM NAME", "SYSTEM VERSION", cs, table);
// Try to update to a different resource
codeSystem = new CodeSystem();
@ -246,7 +244,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
ValueSet.ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(CS_URL);
include.addConcept().setCode("childAAB");
ValueSet outcome = myTermSvc.expandValueSet(vs);
ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null);
List<String> codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("childAAB"));
@ -280,7 +278,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("propA")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("valueAAA");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("childAAA"));
@ -293,7 +291,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("propB")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("foo");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("childAAA", "childAAB"));
@ -306,7 +304,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("propA")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("valueAAA");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, empty());
}
@ -333,7 +331,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("3rdParty");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4"));
@ -350,7 +348,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("3rdparty");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4"));
}
@ -377,7 +375,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("LOINC");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("47239-9"));
@ -394,7 +392,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("loinc");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("47239-9"));
}
@ -417,7 +415,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("3rdParty");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("47239-9"));
@ -430,7 +428,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("3rdparty");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("47239-9"));
}
@ -453,7 +451,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("LOINC");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4"));
@ -466,7 +464,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("copyright")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("loinc");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4"));
}
@ -490,7 +488,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Don't know how to handle op=ISA on property copyright");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -513,7 +511,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Invalid filter, property copyright is LOINC-specific and cannot be used with system: http://example.com/my_code_system");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -535,7 +533,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Don't know how to handle value=bogus on property copyright");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -560,7 +558,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
@ -577,7 +575,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3"));
@ -594,7 +592,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9"));
@ -611,7 +609,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9"));
}
@ -638,7 +636,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
}
@ -661,7 +659,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9"));
@ -674,7 +672,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-4", "47239-9"));
@ -687,7 +685,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
assertEquals(0, outcome.getExpansion().getContains().size());
// Include
@ -699,7 +697,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
assertEquals(0, outcome.getExpansion().getContains().size());
}
@ -721,7 +719,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("ancestor")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9"));
}
@ -745,7 +743,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Don't know how to handle op=ISA on property ancestor");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -768,7 +766,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Invalid filter, property ancestor is LOINC-specific and cannot be used with system: http://example.com/my_code_system");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -793,7 +791,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9"));
@ -810,7 +808,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9"));
@ -827,7 +825,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-4", "47239-9"));
@ -844,7 +842,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-4", "47239-9"));
}
@ -871,7 +869,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-4", "47239-9"));
}
@ -894,7 +892,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
assertEquals(0, outcome.getExpansion().getContains().size());
// Include
@ -906,7 +904,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
@ -919,7 +917,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3"));
@ -932,7 +930,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3"));
}
@ -955,7 +953,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("child")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3"));
}
@ -979,7 +977,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Don't know how to handle op=ISA on property child");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -1002,7 +1000,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Invalid filter, property child is LOINC-specific and cannot be used with system: http://example.com/my_code_system");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -1027,7 +1025,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9"));
@ -1044,7 +1042,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9"));
@ -1061,7 +1059,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-4", "47239-9"));
@ -1078,7 +1076,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-4", "47239-9"));
}
@ -1105,7 +1103,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-4", "47239-9"));
}
@ -1128,7 +1126,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
assertEquals(0, outcome.getExpansion().getContains().size());
// Include
@ -1140,7 +1138,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
@ -1153,7 +1151,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3"));
@ -1166,7 +1164,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3"));
}
@ -1189,7 +1187,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("descendant")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3"));
}
@ -1213,7 +1211,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Don't know how to handle op=ISA on property descendant");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -1236,7 +1234,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Invalid filter, property descendant is LOINC-specific and cannot be used with system: http://example.com/my_code_system");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -1261,7 +1259,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-4", "47239-9"));
@ -1278,7 +1276,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3"));
@ -1295,7 +1293,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9"));
@ -1312,7 +1310,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "43343-3", "43343-4", "47239-9"));
}
@ -1339,7 +1337,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
}
@ -1362,7 +1360,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("50015-7");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3"));
@ -1375,7 +1373,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-3");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-4", "47239-9"));
@ -1388,7 +1386,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("43343-4");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
assertEquals(0, outcome.getExpansion().getContains().size());
// Include
@ -1400,7 +1398,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.EQUAL)
.setValue("47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
assertEquals(0, outcome.getExpansion().getContains().size());
}
@ -1422,7 +1420,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("parent")
.setOp(ValueSet.FilterOperator.IN)
.setValue("50015-7,43343-3,43343-4,47239-9");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9"));
}
@ -1446,7 +1444,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Don't know how to handle op=ISA on property parent");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -1469,7 +1467,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
expectedException.expect(InvalidRequestException.class);
expectedException.expectMessage("Invalid filter, property parent is LOINC-specific and cannot be used with system: http://example.com/my_code_system");
myTermSvc.expandValueSet(vs);
myTermSvc.expandValueSetInMemory(vs, null);
}
@Test
@ -1494,7 +1492,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue(".*\\^Donor$");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4", "47239-9"));
}
@ -1521,7 +1519,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("HELLO")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue("12345-1|12345-2");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7", "47239-9"));
}
@ -1544,7 +1542,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue(".*\\^Donor$");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
@ -1557,7 +1555,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue("\\^Donor$");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
@ -1570,7 +1568,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue("\\^Dono$");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, empty());
@ -1583,7 +1581,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue("^Donor$");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, empty());
@ -1596,7 +1594,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue("\\^Dono");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("50015-7"));
@ -1609,7 +1607,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.setProperty("SYSTEM")
.setOp(ValueSet.FilterOperator.REGEX)
.setValue("^Ser$");
outcome = myTermSvc.expandValueSet(vs);
outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("43343-3", "43343-4"));
@ -1624,7 +1622,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
ValueSet vs = new ValueSet();
ValueSet.ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(CS_URL);
ValueSet outcome = myTermSvc.expandValueSet(vs);
ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("ParentWithNoChildrenA", "ParentWithNoChildrenB", "ParentWithNoChildrenC", "ParentA", "childAAA", "childAAB", "childAA", "childAB", "ParentB"));
@ -1741,7 +1739,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
ValueSet.ConceptSetComponent include = vs.getCompose().addInclude();
include.setSystem(CS_URL);
include.addConcept().setCode("childAAB");
ValueSet outcome = myTermSvc.expandValueSet(vs);
ValueSet outcome = myTermSvc.expandValueSetInMemory(vs, null);
codes = toCodesContains(outcome.getExpansion().getContains());
assertThat(codes, containsInAnyOrder("childAAB"));
@ -1782,7 +1780,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
child.addChild(parent, RelationshipTypeEnum.ISA);
try {
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "SYSTEM NAME", "SYSTEM VERSION" , cs, table);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(table.getId(), "http://foo", "SYSTEM NAME", "SYSTEM VERSION", cs, table);
fail();
} catch (InvalidRequestException e) {
assertEquals("CodeSystem contains circular reference around code parent", e.getMessage());
@ -1894,7 +1892,7 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
@Test
public void testCustomValueSetExpansion() {
CodeSystem cs= new CodeSystem();
CodeSystem cs = new CodeSystem();
cs.setUrl("http://codesystems-r-us");
cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
IIdType csId = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless();
@ -1904,10 +1902,10 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
version.getConcepts().add(new TermConcept(version, "B"));
version.getConcepts().add(new TermConcept(version, "C"));
version.getConcepts().add(new TermConcept(version, "D"));
runInTransaction(()->{
runInTransaction(() -> {
ResourceTable resTable = myEntityManager.find(ResourceTable.class, csId.getIdPartAsLong());
version.setResource(resTable);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", "SYSTEM VERSION" , version, resTable);
myTermCodeSystemStorageSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", "SYSTEM VERSION", version, resTable);
});
org.hl7.fhir.dstu3.model.ValueSet vs = new org.hl7.fhir.dstu3.model.ValueSet();
@ -1927,21 +1925,22 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
.map(t -> t.getCode())
.sorted()
.collect(Collectors.toList());
assertEquals(Lists.newArrayList("A","C"), expansionCodes);
assertEquals(Lists.newArrayList("A", "C"), expansionCodes);
}
@Test
@Ignore
public void testValidateCodeWithProperties() {
createCodeSystem();
IValidationSupport.CodeValidationResult code = myValidationSupport.validateCode(myFhirCtx, CS_URL, "childAAB", null);
IValidationSupport.CodeValidationResult code = myValidationSupport.validateCode(myFhirCtx, CS_URL, "childAAB", null, null);
assertEquals(true, code.isOk());
assertEquals(2, code.getProperties().size());
}
public static List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) {
public static List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) {
List<String> retVal = new ArrayList<>();
for (ValueSet.ValueSetExpansionContainsComponent next : theContains) {

File diff suppressed because it is too large Load Diff

View File

@ -361,11 +361,6 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
return refreshCacheRetrier.runWithRetry();
}
@VisibleForTesting
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
mySearchParamProvider = theSearchParamProvider;
}
@PostConstruct
public void registerScheduledJob() {
ScheduledJobDefinition jobDetail = new ScheduledJobDefinition();

View File

@ -1,7 +1,7 @@
package org.hl7.fhir.dstu2016may.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import org.apache.commons.io.Charsets;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu2016may.model.*;
@ -10,7 +10,10 @@ import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpander;
import org.hl7.fhir.dstu2016may.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -258,18 +261,45 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
if (isNotBlank(theValueSetUrl)) {
HapiWorkerContext workerContext = new HapiWorkerContext(theContext, this);
ValueSetExpander expander = new ValueSetExpanderSimple(workerContext, workerContext);
try {
ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl);
if (valueSet != null) {
ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet);
Optional<ValueSet.ValueSetExpansionContainsComponent> haveMatch = expanded
.getValueset()
.getExpansion()
.getContains()
.stream()
.filter(t -> (theCodeSystem == null || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode))
.findFirst();
if (haveMatch.isPresent()) {
return new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(theCode)));
}
}
} catch (Exception e) {
return new CodeValidationResult(OperationOutcome.IssueSeverity.WARNING, e.getMessage());
}
CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive);
return null;
}
if (retVal != null) {
return retVal;
if (theCodeSystem != null) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
}
CodeValidationResult retVal = testIfConceptIsInList(theCode, cs.getConcept(), caseSensitive);
if (retVal != null) {
return retVal;
}
}
}
@ -278,7 +308,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
@Override
public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
return validateCode(theContext, theSystem, theCode, null).asLookupCodeResult(theSystem, theCode);
return validateCode(theContext, theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode);
}
}

View File

@ -29,7 +29,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
@ -49,7 +48,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = XmlUtil.newDocumentBuilderFactory();
myValidationSupport = theValidationSupport;
}
@ -139,9 +137,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
document = XmlUtil.parseDocument(theInput);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();

View File

@ -177,7 +177,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override
public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) {
CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay);
CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, null);
if (result == null) {
return null;
}
@ -231,7 +231,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
/*
* The following valueset is a special case, since the BCP codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getId())) {
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) {
ValueSet expansion = new ValueSet();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) {
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) {

View File

@ -51,6 +51,7 @@ public interface IValidationSupport
* Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri);
/**
@ -95,10 +96,13 @@ public interface IValidationSupport
* The code, e.g. "<code>1234-5</code>"
* @param theDisplay
* The display name, if it should also be validated
* @param theValueSetUrl When validating that a code exists as a part of a specific ValueSet, the ValueSet URI
* will be provided in this parameter value. If <code>null</code>, the validation should simply
* confirm that the code exists.
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, OperationOutcome.IssueSeverity> {

View File

@ -28,9 +28,9 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
* Constructor
*/
public PrePopulatedValidationSupport() {
myStructureDefinitions = new HashMap<String, StructureDefinition>();
myValueSets = new HashMap<String, ValueSet>();
myCodeSystems = new HashMap<String, CodeSystem>();
myStructureDefinitions = new HashMap<>();
myValueSets = new HashMap<>();
myCodeSystems = new HashMap<>();
}
/**
@ -134,7 +134,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -134,13 +134,13 @@ public class ValidationSupportChain implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) {
return next.validateCode(theCtx, theCodeSystem, theCode, theDisplay);
if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
return next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
}
}
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay);
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
}
@Override

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.dstu3.hapi.ctx;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -12,6 +13,8 @@ import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpander;
import org.hl7.fhir.dstu3.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -280,18 +283,45 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
if (isNotBlank(theValueSetUrl)) {
HapiWorkerContext workerContext = new HapiWorkerContext(theContext, this);
ValueSetExpander expander = new ValueSetExpanderSimple(workerContext, workerContext);
try {
ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl);
if (valueSet != null) {
ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet, null);
Optional<ValueSet.ValueSetExpansionContainsComponent> haveMatch = expanded
.getValueset()
.getExpansion()
.getContains()
.stream()
.filter(t -> (theCodeSystem == null || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode))
.findFirst();
if (haveMatch.isPresent()) {
return new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(theCode)));
}
}
} catch (Exception e) {
return new CodeValidationResult(IssueSeverity.WARNING, e.getMessage());
}
CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive);
return null;
}
if (retVal != null) {
return retVal;
if (theCodeSystem != null) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
}
CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive);
if (retVal != null) {
return retVal;
}
}
}
@ -300,7 +330,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
@Override
public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
return validateCode(theContext, theSystem, theCode, null).asLookupCodeResult(theSystem, theCode);
return validateCode(theContext, theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode);
}
@Override

View File

@ -275,7 +275,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override
public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) {
IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay);
IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, null);
if (result == null) {
return null;
}
@ -303,7 +303,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
}
}
boolean caseSensitive = true;
if (isNotBlank(theSystem)) {
CodeSystem system = fetchCodeSystem(theSystem);
@ -326,45 +325,50 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
/*
* The following valueset is a special case, since the BCP codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getId())) {
ValueSet expansion = new ValueSet();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) {
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) {
expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay());
}
}
expandedValueSet = new ValueSetExpansionOutcome(expansion);
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theSystem);
definition.setDisplay(theCode);
return new ValidationResult(definition);
}
/*
* We'll just accept all mimetypes, since this is pretty much impossible to exhaustively
* validate.
* The following valueset is a special case, since the mime types codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(wantCode);
definition.setDisplay(wantCode);
ValidationResult retVal = new ValidationResult(definition);
return retVal;
definition.setCode(theSystem);
definition.setDisplay(theCode);
return new ValidationResult(definition);
}
if (expandedValueSet == null) {
if (theVs != null && isNotBlank(theVs.getUrl())) {
IValidationSupport.CodeValidationResult outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl());
if (outcome != null && outcome.isOk()) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theCode);
definition.setDisplay(outcome.getDisplay());
return new ValidationResult(definition);
}
} else {
expandedValueSet = expand(theVs, null);
}
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
String nextCode = next.getCode();
if (!caseSensitive) {
nextCode = nextCode.toUpperCase();
}
if (expandedValueSet != null) {
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
String nextCode = next.getCode();
if (!caseSensitive) {
nextCode = nextCode.toUpperCase();
}
if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition);
return retVal;
if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition);
return retVal;
}
}
}
}

View File

@ -45,6 +45,7 @@ public interface IValidationSupport
* @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri);
@Override
@ -69,10 +70,13 @@ public interface IValidationSupport
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated
* @param theValueSetUrl When validating that a code exists as a part of a specific ValueSet, the ValueSet URI
* will be provided in this parameter value. If <code>null</code>, the validation should simply
* confirm that the code exists.
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
/**
* Generate a snapshot from the given differential profile.

View File

@ -14,6 +14,9 @@ import org.junit.*;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException;
import org.xml.sax.SAXException;
import java.io.IOException;
public class XmlUtilDstu3Test {
@ -72,11 +75,11 @@ public class XmlUtilDstu3Test {
}
@Test
public void testApplyUnsupportedFeature() {
assertNotNull(XmlUtil.newDocumentBuilderFactory());
public void testApplyUnsupportedFeature() throws IOException, SAXException {
assertNotNull(XmlUtil.parseDocument("<document></document>"));
XmlUtil.setThrowExceptionForUnitTest(new ParserConfigurationException("AA"));
assertNotNull(XmlUtil.newDocumentBuilderFactory());
assertNotNull(XmlUtil.parseDocument("<document></document>"));
}
@AfterClass

View File

@ -117,6 +117,10 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<!-- Testing -->
<dependency>

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r4.hapi.ctx;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -13,6 +14,7 @@ import org.hl7.fhir.r4.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.io.IOException;
@ -294,18 +296,44 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
if (isNotBlank(theValueSetUrl)) {
ValueSetExpander expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, this));
try {
ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl);
if (valueSet != null) {
ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet, null);
Optional<ValueSet.ValueSetExpansionContainsComponent> haveMatch = expanded
.getValueset()
.getExpansion()
.getContains()
.stream()
.filter(t -> (theCodeSystem == null || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode))
.findFirst();
if (haveMatch.isPresent()) {
return new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(theCode)));
}
}
} catch (Exception e) {
return new CodeValidationResult(IssueSeverity.WARNING, e.getMessage());
}
CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive);
return null;
}
if (retVal != null) {
return retVal;
if (theCodeSystem != null) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
}
CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive);
if (retVal != null) {
return retVal;
}
}
}
@ -314,7 +342,7 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
@Override
public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
return validateCode(theContext, theSystem, theCode, null).asLookupCodeResult(theSystem, theCode);
return validateCode(theContext, theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode);
}
}

View File

@ -177,7 +177,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) {
CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay);
CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, null);
if (result == null) {
return null;
}
@ -227,42 +227,50 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
/*
* The following valueset is a special case, since the BCP codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getId())) {
ValueSet expansion = new ValueSet();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) {
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) {
expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay());
}
}
expandedValueSet = new ValueSetExpansionOutcome(expansion);
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theSystem);
definition.setDisplay(theCode);
return new ValidationResult(definition);
}
/*
* The following valueset is a special case, since the mime types codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getId())) {
ValueSet expansion = new ValueSet();
expansion.getExpansion().addContains().setCode(theCode).setSystem(theSystem).setDisplay(theDisplay);
expandedValueSet = new ValueSetExpansionOutcome(expansion);
if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theSystem);
definition.setDisplay(theCode);
return new ValidationResult(definition);
}
if (expandedValueSet == null) {
if (theVs != null && isNotBlank(theVs.getUrl())) {
CodeValidationResult outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl());
if (outcome != null && outcome.isOk()) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theCode);
definition.setDisplay(outcome.getDisplay());
return new ValidationResult(definition);
}
} else {
expandedValueSet = expand(theVs, null);
}
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
String nextCode = next.getCode();
if (!caseSensitive) {
nextCode = nextCode.toUpperCase();
}
if (expandedValueSet != null) {
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
String nextCode = next.getCode();
if (!caseSensitive) {
nextCode = nextCode.toUpperCase();
}
if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition);
return retVal;
if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition);
return retVal;
}
}
}
}
@ -330,12 +338,12 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
}
@Override
public void setLogger(ILoggingService theLogger) {
public ILoggingService getLogger() {
throw new UnsupportedOperationException();
}
@Override
public ILoggingService getLogger() {
public void setLogger(ILoggingService theLogger) {
throw new UnsupportedOperationException();
}
@ -349,6 +357,11 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
throw new UnsupportedOperationException();
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public boolean isNoTerminologyServer() {
return false;
@ -384,11 +397,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public String getLinkForUrl(String corePath, String url) {
throw new UnsupportedOperationException();

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.r4.hapi.ctx;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.StructureDefinition;
@ -46,6 +47,7 @@ public interface IValidationSupport
* @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri);
/**
@ -90,10 +92,13 @@ public interface IValidationSupport
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated
* @param theValueSetUrl When validating that a code exists as a part of a specific ValueSet, the ValueSet URI
* will be provided in this parameter value. If <code>null</code>, the validation should simply
* confirm that the code exists.
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {

View File

@ -1,20 +1,21 @@
package org.hl7.fhir.r4.test.support;
import ca.uhn.fhir.util.XmlUtil;
import com.google.common.base.Charsets;
import com.google.gson.*;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.utilities.CSFile;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -172,30 +173,8 @@ public class TestingUtilities {
}
private static Document loadXml(InputStream fn) throws Exception {
DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory();
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(fn);
}
public static String checkJsonIsSame(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException {
String result = compareJson(f1, f2);
if (result != null && SHOW_DIFF) {
String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe");
List<String> command = new ArrayList<String>();
command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\"");
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile("c:\\temp"));
builder.start();
}
return result;
}
private static String compareJson(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException {
JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f1));
JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f2));
return compareObjects("", o1, o2);
String input = IOUtils.toString(fn, Charsets.UTF_8);
return XmlUtil.parseDocument(input);
}
private static String compareObjects(String path, JsonObject o1, JsonObject o2) {
@ -250,9 +229,9 @@ public class TestingUtilities {
JsonArray a2 = (JsonArray) n2;
if (a1.size() != a2.size())
return "array properties differ at " + path + ": count " + Integer.toString(a1.size()) + "/" + Integer.toString(a2.size());
return "array properties differ at " + path + ": count " + a1.size() + "/" + a2.size();
for (int i = 0; i < a1.size(); i++) {
String s = compareNodes(path + "[" + Integer.toString(i) + "]", a1.get(i), a2.get(i));
String s = compareNodes(path + "[" + i + "]", a1.get(i), a2.get(i));
if (!Utilities.noString(s))
return s;
}

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.r5.hapi.ctx;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -14,6 +15,7 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
import org.hl7.fhir.r5.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.io.IOException;
@ -26,296 +28,322 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DefaultProfileValidationSupport implements IValidationSupport {
private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/";
private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION = "http://hl7.org/fhir/StructureDefinition/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class);
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DefaultProfileValidationSupport.class);
private Map<String, CodeSystem> myCodeSystems;
private Map<String, StructureDefinition> myStructureDefinitions;
private Map<String, ValueSet> myValueSets;
private Map<String, CodeSystem> myCodeSystems;
private Map<String, StructureDefinition> myStructureDefinitions;
private Map<String, ValueSet> myValueSets;
private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set<String> theWantCodes, List<ConceptDefinitionComponent> theConcepts) {
for (ConceptDefinitionComponent next : theConcepts) {
if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) {
theRetVal
.addContains()
.setSystem(theInclude.getSystem())
.setCode(next.getCode())
.setDisplay(next.getDisplay());
}
addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept());
}
}
private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set<String> theWantCodes, List<ConceptDefinitionComponent> theConcepts) {
for (ConceptDefinitionComponent next : theConcepts) {
if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) {
theRetVal
.addContains()
.setSystem(theInclude.getSystem())
.setCode(next.getCode())
.setDisplay(next.getDisplay());
}
addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept());
}
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet());
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet());
Set<String> wantCodes = new HashSet<>();
for (ConceptReferenceComponent next : theInclude.getConcept()) {
wantCodes.add(next.getCode());
}
Set<String> wantCodes = new HashSet<>();
for (ConceptReferenceComponent next : theInclude.getConcept()) {
wantCodes.add(next.getCode());
}
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
if (system != null) {
List<ConceptDefinitionComponent> concepts = system.getConcept();
addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts);
}
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
if (system != null) {
List<ConceptDefinitionComponent> concepts = system.getConcept();
addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts);
}
for (UriType next : theInclude.getValueSet()) {
ValueSet vs = myValueSets.get(defaultString(next.getValueAsString()));
if (vs != null) {
for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) {
ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude);
retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains());
}
}
}
for (UriType next : theInclude.getValueSet()) {
ValueSet vs = myValueSets.get(defaultString(next.getValueAsString()));
if (vs != null) {
for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) {
ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude);
retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains());
}
}
}
return retVal;
}
return retVal;
}
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
ArrayList<IBaseResource> retVal = new ArrayList<>();
retVal.addAll(myCodeSystems.values());
retVal.addAll(myStructureDefinitions.values());
retVal.addAll(myValueSets.values());
return retVal;
}
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
ArrayList<IBaseResource> retVal = new ArrayList<>();
retVal.addAll(myCodeSystems.values());
retVal.addAll(myStructureDefinitions.values());
retVal.addAll(myValueSets.values());
return retVal;
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return new ArrayList<>(provideStructureDefinitionMap(theContext).values());
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return new ArrayList<>(provideStructureDefinitionMap(theContext).values());
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true);
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true);
}
private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) {
synchronized (this) {
Map<String, CodeSystem> codeSystems = myCodeSystems;
Map<String, ValueSet> valueSets = myValueSets;
if (codeSystems == null || valueSets == null) {
codeSystems = new HashMap<>();
valueSets = new HashMap<>();
private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) {
synchronized (this) {
Map<String, CodeSystem> codeSystems = myCodeSystems;
Map<String, ValueSet> valueSets = myValueSets;
if (codeSystems == null || valueSets == null) {
codeSystems = new HashMap<>();
valueSets = new HashMap<>();
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/valuesets.xml");
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/v2-tables.xml");
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/v3-codesystems.xml");
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/valuesets.xml");
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/v2-tables.xml");
loadCodeSystems(theContext, codeSystems, valueSets, "/org/hl7/fhir/r5/model/valueset/v3-codesystems.xml");
myCodeSystems = codeSystems;
myValueSets = valueSets;
}
myCodeSystems = codeSystems;
myValueSets = valueSets;
}
// System can take the form "http://url|version"
String system = theSystem;
if (system.contains("|")) {
String version = system.substring(system.indexOf('|') + 1);
if (version.matches("^[0-9.]+$")) {
system = system.substring(0, system.indexOf('|'));
}
}
// System can take the form "http://url|version"
String system = theSystem;
if (system.contains("|")) {
String version = system.substring(system.indexOf('|') + 1);
if (version.matches("^[0-9.]+$")) {
system = system.substring(0, system.indexOf('|'));
}
}
if (codeSystem) {
return codeSystems.get(system);
} else {
return valueSets.get(system);
}
}
}
if (codeSystem) {
return codeSystems.get(system);
} else {
return valueSets.get(system);
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
Validate.notBlank(theUri, "theUri must not be null or blank");
@SuppressWarnings("unchecked")
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
Validate.notBlank(theUri, "theUri must not be null or blank");
if (theClass.equals(StructureDefinition.class)) {
return (T) fetchStructureDefinition(theContext, theUri);
}
if (theClass.equals(StructureDefinition.class)) {
return (T) fetchStructureDefinition(theContext, theUri);
}
if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) {
return (T) fetchValueSet(theContext, theUri);
}
if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) {
return (T) fetchValueSet(theContext, theUri);
}
return null;
}
return null;
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
String url = theUrl;
if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
// no change
} else if (url.indexOf('/') == -1) {
url = URL_PREFIX_STRUCTURE_DEFINITION + url;
} else if (StringUtils.countMatches(url, '/') == 1) {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
}
return provideStructureDefinitionMap(theContext).get(url);
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
String url = theUrl;
if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
// no change
} else if (url.indexOf('/') == -1) {
url = URL_PREFIX_STRUCTURE_DEFINITION + url;
} else if (StringUtils.countMatches(url, '/') == 1) {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
}
return provideStructureDefinitionMap(theContext).get(url);
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false);
}
@Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false);
}
public void flush() {
myCodeSystems = null;
myStructureDefinitions = null;
}
public void flush() {
myCodeSystems = null;
myStructureDefinitions = null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
CodeSystem cs = fetchCodeSystem(theContext, theSystem);
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
CodeSystem cs = fetchCodeSystem(theContext, theSystem);
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) {
return null;
}
@Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) {
return null;
}
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
InputStreamReader reader = null;
if (inputStream != null) {
try {
reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8);
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
InputStreamReader reader = null;
if (inputStream != null) {
try {
reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8);
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof CodeSystem) {
CodeSystem nextValueSet = (CodeSystem) next.getResource();
nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl();
if (isNotBlank(system)) {
theCodeSystems.put(system, nextValueSet);
}
} else if (next.getResource() instanceof ValueSet) {
ValueSet nextValueSet = (ValueSet) next.getResource();
nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl();
if (isNotBlank(system)) {
theValueSets.put(system, nextValueSet);
}
}
}
} finally {
try {
if (reader != null) {
reader.close();
}
inputStream.close();
} catch (IOException e) {
ourLog.warn("Failure closing stream", e);
}
}
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
}
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof CodeSystem) {
CodeSystem nextValueSet = (CodeSystem) next.getResource();
nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl();
if (isNotBlank(system)) {
theCodeSystems.put(system, nextValueSet);
}
} else if (next.getResource() instanceof ValueSet) {
ValueSet nextValueSet = (ValueSet) next.getResource();
nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl();
if (isNotBlank(system)) {
theValueSets.put(system, nextValueSet);
}
}
}
} finally {
try {
if (reader != null) {
reader.close();
}
inputStream.close();
} catch (IOException e) {
ourLog.warn("Failure closing stream", e);
}
}
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
}
private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) {
ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
if (valuesetText != null) {
InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8);
private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) {
ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
if (valuesetText != null) {
InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8);
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof StructureDefinition) {
StructureDefinition nextSd = (StructureDefinition) next.getResource();
nextSd.getText().setDivAsString("");
String system = nextSd.getUrl();
if (isNotBlank(system)) {
theCodeSystems.put(system, nextSd);
}
}
}
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
}
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof StructureDefinition) {
StructureDefinition nextSd = (StructureDefinition) next.getResource();
nextSd.getText().setDivAsString("");
String system = nextSd.getUrl();
if (isNotBlank(system)) {
theCodeSystems.put(system, nextSd);
}
}
}
} else {
ourLog.warn("Unable to load resource: {}", theClasspath);
}
}
private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) {
Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions;
if (structureDefinitions == null) {
structureDefinitions = new HashMap<>();
private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) {
Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions;
if (structureDefinitions == null) {
structureDefinitions = new HashMap<>();
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-resources.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-types.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-others.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/extension/extension-definitions.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-resources.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-types.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-others.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/extension/extension-definitions.xml");
myStructureDefinitions = structureDefinitions;
}
return structureDefinitions;
}
myStructureDefinitions = structureDefinitions;
}
return structureDefinitions;
}
private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) {
String code = theCode;
if (theCaseSensitive == false) {
code = code.toUpperCase();
}
private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) {
String code = theCode;
if (theCaseSensitive == false) {
code = code.toUpperCase();
}
return testIfConceptIsInListInner(theCodeSystem, conceptList, theCaseSensitive, code);
}
return testIfConceptIsInListInner(theCodeSystem, conceptList, theCaseSensitive, code);
}
private CodeValidationResult testIfConceptIsInListInner(CodeSystem theCodeSystem, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) {
CodeValidationResult retVal = null;
for (ConceptDefinitionComponent next : conceptList) {
String nextCandidate = next.getCode();
if (theCaseSensitive == false) {
nextCandidate = nextCandidate.toUpperCase();
}
if (nextCandidate.equals(code)) {
retVal = new CodeValidationResult(next);
break;
}
private CodeValidationResult testIfConceptIsInListInner(CodeSystem theCodeSystem, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) {
CodeValidationResult retVal = null;
for (ConceptDefinitionComponent next : conceptList) {
String nextCandidate = next.getCode();
if (theCaseSensitive == false) {
nextCandidate = nextCandidate.toUpperCase();
}
if (nextCandidate.equals(code)) {
retVal = new CodeValidationResult(next);
break;
}
// recurse
retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive);
if (retVal != null) {
break;
}
}
// recurse
retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive);
if (retVal != null) {
break;
}
}
if (retVal != null) {
retVal.setCodeSystemName(theCodeSystem.getName());
retVal.setCodeSystemVersion(theCodeSystem.getVersion());
}
if (retVal != null) {
retVal.setCodeSystemName(theCodeSystem.getName());
retVal.setCodeSystemVersion(theCodeSystem.getVersion());
}
return retVal;
}
return retVal;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
if (isNotBlank(theValueSetUrl)) {
ValueSetExpander expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, this));
try {
ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl);
if (valueSet != null) {
ValueSetExpander.ValueSetExpansionOutcome expanded = expander.expand(valueSet, null);
Optional<ValueSet.ValueSetExpansionContainsComponent> haveMatch = expanded
.getValueset()
.getExpansion()
.getContains()
.stream()
.filter(t -> (theCodeSystem == null || t.getSystem().equals(theCodeSystem)) && t.getCode().equals(theCode))
.findFirst();
if (haveMatch.isPresent()) {
return new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(theCode)));
}
}
} catch (Exception e) {
return new CodeValidationResult(IssueSeverity.WARNING, e.getMessage());
}
CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive);
return null;
}
if (retVal != null) {
return retVal;
}
}
if (theCodeSystem != null) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
if (cs != null) {
boolean caseSensitive = true;
if (cs.hasCaseSensitive()) {
caseSensitive = cs.getCaseSensitive();
}
return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode);
}
CodeValidationResult retVal = testIfConceptIsInList(cs, theCode, cs.getConcept(), caseSensitive);
@Override
public IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
return validateCode(theContext, theSystem, theCode, null).asLookupCodeResult(theSystem, theCode);
}
if (retVal != null) {
return retVal;
}
}
}
return new CodeValidationResult(IssueSeverity.WARNING, "Unknown code: " + theCodeSystem + " / " + theCode);
}
@Override
public IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
return validateCode(theContext, theSystem, theCode, null, null).asLookupCodeResult(theSystem, theCode);
}
}

View File

@ -176,7 +176,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) {
CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay);
CodeValidationResult result = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, null);
if (result == null) {
return null;
}
@ -226,42 +226,50 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
/*
* The following valueset is a special case, since the BCP codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getId())) {
ValueSet expansion = new ValueSet();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) {
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) {
expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay());
}
}
expandedValueSet = new ValueSetExpansionOutcome(expansion);
if (theVs != null && "http://hl7.org/fhir/ValueSet/languages".equals(theVs.getUrl())) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theSystem);
definition.setDisplay(theCode);
return new ValidationResult(definition);
}
/*
* The following valueset is a special case, since the mime types codesystem is very difficult to expand
*/
if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getId())) {
ValueSet expansion = new ValueSet();
expansion.getExpansion().addContains().setCode(theCode).setSystem(theSystem).setDisplay(theDisplay);
expandedValueSet = new ValueSetExpansionOutcome(expansion);
if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theSystem);
definition.setDisplay(theCode);
return new ValidationResult(definition);
}
if (expandedValueSet == null) {
if (theVs != null && isNotBlank(theVs.getUrl())) {
CodeValidationResult outcome = myValidationSupport.validateCode(myCtx, theSystem, theCode, theDisplay, theVs.getUrl());
if (outcome != null && outcome.isOk()) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(theCode);
definition.setDisplay(outcome.getDisplay());
return new ValidationResult(definition);
}
} else {
expandedValueSet = expand(theVs, null);
}
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
String nextCode = next.getCode();
if (!caseSensitive) {
nextCode = nextCode.toUpperCase();
}
if (expandedValueSet != null) {
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
String nextCode = next.getCode();
if (!caseSensitive) {
nextCode = nextCode.toUpperCase();
}
if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition);
return retVal;
if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition);
return retVal;
}
}
}
}
@ -269,6 +277,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]");
}
@Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String code, ValueSet vs) {
return validateCode(theOptions, null, code, null, vs);

View File

@ -46,6 +46,7 @@ public interface IValidationSupport
* @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet)
*/
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri);
/**
@ -94,10 +95,13 @@ public interface IValidationSupport
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated
* @param theValueSetUrl When validating that a code exists as a part of a specific ValueSet, the ValueSet URI
* will be provided in this parameter value. If <code>null</code>, the validation should simply
* confirm that the code exists.
* @return Returns a validation result object
*/
@Override
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay);
CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl);
class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {

View File

@ -0,0 +1,19 @@
package ca.uhn.fhir.test;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
public class BaseTest {
protected String loadResource(String theClasspath) throws IOException {
InputStream stream = BaseTest.class.getResourceAsStream(theClasspath);
if (stream==null) {
throw new IllegalArgumentException("Unable to find resource: " + theClasspath);
}
return IOUtils.toString(stream, Charsets.UTF_8);
}
}

View File

@ -28,7 +28,6 @@ import java.util.List;
public class ValidatorWrapper {
private static final Logger ourLog = LoggerFactory.getLogger(ValidatorWrapper.class);
private final DocumentBuilderFactory myDocBuilderFactory;
private IResourceValidator.BestPracticeWarningLevel myBestPracticeWarningLevel;
private boolean myAnyExtensionsAllowed;
private boolean myErrorForUnknownProfiles;
@ -39,7 +38,7 @@ public class ValidatorWrapper {
* Constructor
*/
public ValidatorWrapper() {
myDocBuilderFactory = XmlUtil.newDocumentBuilderFactory();
super();
}
public ValidatorWrapper setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel theBestPracticeWarningLevel) {
@ -95,9 +94,7 @@ public class ValidatorWrapper {
if (encoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(input));
document = builder.parse(src);
document = XmlUtil.parseDocument(input);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();

View File

@ -17,6 +17,8 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
@SuppressWarnings("unchecked")
public class CachingValidationSupport implements IValidationSupport {
@ -73,8 +75,9 @@ public class CachingValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
return myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay);
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS");
return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl));
}
@Override

View File

@ -3,7 +3,6 @@ package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.XmlUtil;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import com.github.benmanes.caffeine.cache.Caffeine;
@ -38,7 +37,6 @@ import org.w3c.dom.NodeList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilderFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
@ -51,7 +49,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
@ -75,7 +72,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* @param theValidationSupport The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = XmlUtil.newDocumentBuilderFactory();
myValidationSupport = theValidationSupport;
}

View File

@ -181,7 +181,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -91,7 +91,7 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -150,13 +150,13 @@ public class ValidationSupportChain implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size());
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay);
if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
if (result != null) {
ourLog.debug("Chain item {} returned outcome {}", next, result.isOk());
return result;
@ -165,7 +165,7 @@ public class ValidationSupportChain implements IValidationSupport {
ourLog.debug("Chain item {} does not support code system {}", next, theCodeSystem);
}
}
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay);
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
}
@Override

View File

@ -41,13 +41,9 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
@ -63,7 +59,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
@ -85,7 +80,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* @param theValidationSupport The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = XmlUtil.newDocumentBuilderFactory();
myValidationSupport = theValidationSupport;
}
@ -280,9 +274,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
document = XmlUtil.parseDocument(theInput);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();

View File

@ -91,7 +91,7 @@ public class ValidationSupportChain implements IValidationSupport {
@Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) {
if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
return next.validateCode(theCtx, theCodeSystem, theCode, theDisplay);
}
}

View File

@ -16,6 +16,8 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
@SuppressWarnings("unchecked")
public class CachingValidationSupport implements IValidationSupport {
@ -81,9 +83,9 @@ public class CachingValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
String key = "validateCode " + theCodeSystem + " " + theCode;
return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay));
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS");
return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl));
}
@Override

View File

@ -188,7 +188,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -92,7 +92,7 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -141,13 +141,13 @@ public class ValidationSupportChain implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size());
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay);
if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
if (result != null) {
ourLog.debug("Chain item {} returned outcome {}", next, result.isOk());
return result;
@ -156,7 +156,7 @@ public class ValidationSupportChain implements IValidationSupport {
ourLog.debug("Chain item {} does not support code system {}", next, theCodeSystem);
}
}
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay);
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
}
@Override

View File

@ -16,6 +16,8 @@ import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
@SuppressWarnings("unchecked")
public class CachingValidationSupport implements IValidationSupport {
@ -81,9 +83,9 @@ public class CachingValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
String key = "validateCode " + theCodeSystem + " " + theCode;
return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay));
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS");
return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl));
}
@Override

View File

@ -188,7 +188,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -92,7 +92,7 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
return null;
}

View File

@ -139,13 +139,13 @@ public class ValidationSupportChain implements IValidationSupport {
}
@Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay, String theValueSetUrl) {
ourLog.debug("Validating code {} in chain with {} items", theCode, myChain.size());
for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay);
if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
CodeValidationResult result = next.validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
if (result != null) {
ourLog.debug("Chain item {} returned outcome {}", next, result.isOk());
return result;
@ -154,7 +154,7 @@ public class ValidationSupportChain implements IValidationSupport {
ourLog.debug("Chain item {} does not support code system {}", next, theCodeSystem);
}
}
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay);
return myChain.get(0).validateCode(theCtx, theCodeSystem, theCode, theDisplay, theValueSetUrl);
}
@Override

View File

@ -53,6 +53,7 @@ import org.mockito.stubbing.Answer;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.GZIPInputStream;
@ -158,12 +159,14 @@ public class FhirInstanceValidatorDstu3Test {
return retVal;
}
});
when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
@Override
public CodeValidationResult answer(InvocationOnMock theInvocation) {
FhirContext ctx = (FhirContext) theInvocation.getArguments()[0];
String system = (String) theInvocation.getArguments()[1];
String code = (String) theInvocation.getArguments()[2];
FhirContext ctx = theInvocation.getArgument(0, FhirContext.class);
String system = theInvocation.getArgument(1, String.class);
String code = theInvocation.getArgument(2, String.class);
String display = theInvocation.getArgument(3, String.class);
String valueSetUrl = theInvocation.getArgument(4, String.class);
CodeValidationResult retVal;
if (myValidConcepts.contains(system + "___" + code)) {
retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code)));
@ -172,9 +175,9 @@ public class FhirInstanceValidatorDstu3Test {
Optional<ConceptDefinitionComponent> found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst();
retVal = found.map(t->new CodeValidationResult(t)).orElse(null);
} else {
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, (String) theInvocation.getArguments()[2]);
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, display, valueSetUrl);
}
ourLog.debug("validateCode({}, {}, {}) : {}", new Object[] {system, code, theInvocation.getArguments()[2], retVal});
ourLog.debug("validateCode({}, {}, {}, {}) : {}", system, code, display, valueSetUrl, retVal);
return retVal;
}
});
@ -241,7 +244,7 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {}:{} {} - {}",
new Object[] {index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage()});
index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage());
index++;
retVal.add(next);
@ -255,7 +258,7 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {} - {}", new Object[] {index, next.getSeverity(), next.getLocationString(), next.getMessage()});
ourLog.info("Result {}: {} - {} - {}", index, next.getSeverity(), next.getLocationString(), next.getMessage());
index++;
if (next.getSeverity() != ResultSeverityEnum.INFORMATION) {
@ -536,7 +539,7 @@ public class FhirInstanceValidatorDstu3Test {
String name = "profiles-resources";
ourLog.info("Uploading " + name);
String vsContents;
vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/profile/" + name + ".xml"), "UTF-8");
vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/org/hl7/fhir/dstu3/model/profile/" + name + ".xml"), StandardCharsets.UTF_8);
TreeSet<String> ids = new TreeSet<String>();
@ -566,7 +569,7 @@ public class FhirInstanceValidatorDstu3Test {
@Test
public void testValidateBundleWithNoType() throws Exception {
String vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/dstu3/bundle-with-no-type.json"), "UTF-8");
String vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/dstu3/bundle-with-no-type.json"), StandardCharsets.UTF_8);
ValidationResult output = myVal.validateWithResult(vsContents);
logResultsAndReturnNonInformationalOnes(output);
@ -579,7 +582,7 @@ public class FhirInstanceValidatorDstu3Test {
String name = "profiles-resources";
ourLog.info("Uploading " + name);
String inputString;
inputString = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/brian_reinhold_bundle.json"), "UTF-8");
inputString = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/brian_reinhold_bundle.json"), StandardCharsets.UTF_8);
Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, inputString);
FHIRPathEngine fp = new FHIRPathEngine(new HapiWorkerContext(ourCtx, myDefaultValidationSupport));
@ -631,7 +634,7 @@ public class FhirInstanceValidatorDstu3Test {
String name = "profiles-resources";
ourLog.info("Uploading " + name);
String vsContents;
vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/crucible-condition.xml"), "UTF-8");
vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/crucible-condition.xml"), StandardCharsets.UTF_8);
ValidationResult output = myVal.validateWithResult(vsContents);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
@ -639,7 +642,7 @@ public class FhirInstanceValidatorDstu3Test {
@Test
public void testValidateDocument() throws Exception {
String vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/sample-document.xml"), "UTF-8");
String vsContents = IOUtils.toString(FhirInstanceValidatorDstu3Test.class.getResourceAsStream("/sample-document.xml"), StandardCharsets.UTF_8);
ValidationResult output = myVal.validateWithResult(vsContents);
logResultsAndReturnNonInformationalOnes(output);

View File

@ -69,7 +69,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
@ -196,9 +196,9 @@ public class QuestionnaireResponseValidatorDstu3Test {
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true);
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true);
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent().setCode("code0")));
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), any()))
.thenReturn(new CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code"));
CodeSystem codeSystem = new CodeSystem();
@ -250,7 +250,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Validation failed for 'http://codesystems.com/system2#code3'"));
assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system2 / code3"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
}
@ -771,7 +771,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -827,7 +827,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -987,9 +987,9 @@ public class QuestionnaireResponseValidatorDstu3Test {
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true);
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true);
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent().setCode("code0")));
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), any()))
.thenReturn(new CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code"));
CodeSystem codeSystem = new CodeSystem();
@ -1009,7 +1009,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class))).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(), any())).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
QuestionnaireResponse qa;
ValidationResult errors;

View File

@ -40,7 +40,7 @@ public class QuestionnaireValidatorDstu3Test {
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);

View File

@ -102,7 +102,7 @@ public class FhirInstanceValidatorR4Test {
myVal.setValidateAgainstStandardSchematron(false);
myMockSupport = mock(IValidationSupport.class);
CachingValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(myMockSupport, myDefaultValidationSupport));
CachingValidationSupport validationSupport = new CachingValidationSupport(new ValidationSupportChain(myDefaultValidationSupport, myMockSupport));
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
@ -149,19 +149,21 @@ public class FhirInstanceValidatorR4Test {
return retVal;
}
});
when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
@Override
public CodeValidationResult answer(InvocationOnMock theInvocation) throws Throwable {
FhirContext ctx = (FhirContext) theInvocation.getArguments()[0];
String system = (String) theInvocation.getArguments()[1];
String code = (String) theInvocation.getArguments()[2];
public CodeValidationResult answer(InvocationOnMock theInvocation) {
FhirContext ctx = theInvocation.getArgument(0, FhirContext.class);
String system = theInvocation.getArgument(1, String.class);
String code = theInvocation.getArgument(2, String.class);
String display = theInvocation.getArgument(3, String.class);
String valueSetUrl = theInvocation.getArgument(4, String.class);
CodeValidationResult retVal;
if (myValidConcepts.contains(system + "___" + code)) {
retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code)));
} else {
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, (String) theInvocation.getArguments()[2]);
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, display, valueSetUrl);
}
ourLog.debug("validateCode({}, {}, {}) : {}", new Object[]{system, code, (String) theInvocation.getArguments()[2], retVal});
ourLog.debug("validateCode({}, {}, {}, {}) : {}", system, code, display, valueSetUrl, retVal);
return retVal;
}
});
@ -580,7 +582,7 @@ public class FhirInstanceValidatorR4Test {
public void testValidateProfileWithExtension() throws IOException, FHIRException {
PrePopulatedValidationSupport valSupport = new PrePopulatedValidationSupport();
DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport();
CachingValidationSupport support = new CachingValidationSupport(new ValidationSupportChain(valSupport, defaultSupport));
CachingValidationSupport support = new CachingValidationSupport(new ValidationSupportChain(defaultSupport, valSupport));
// Prepopulate SDs
valSupport.addStructureDefinition(loadStructureDefinition(defaultSupport, "/dstu3/myconsent-profile.xml"));

View File

@ -62,7 +62,7 @@ public class QuestionnaireResponseValidatorR4Test {
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
@ -191,9 +191,9 @@ public class QuestionnaireResponseValidatorR4Test {
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true);
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true);
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()))
.thenReturn(new IValidationSupport.CodeValidationResult(new CodeSystem.ConceptDefinitionComponent().setCode("code0")));
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(),any()))
.thenReturn(new IValidationSupport.CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code"));
CodeSystem codeSystem = new CodeSystem();
@ -248,7 +248,7 @@ public class QuestionnaireResponseValidatorR4Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Validation failed for 'http://codesystems.com/system2#code3'"));
assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system2 / code3"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
}
@ -345,7 +345,7 @@ public class QuestionnaireResponseValidatorR4Test {
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -402,7 +402,7 @@ public class QuestionnaireResponseValidatorR4Test {
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -522,7 +522,7 @@ public class QuestionnaireResponseValidatorR4Test {
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class))).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class), any())).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
QuestionnaireResponse qa;
ValidationResult errors;

View File

@ -42,7 +42,7 @@ public class QuestionnaireValidatorR4Test {
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);

View File

@ -139,19 +139,21 @@ public class FhirInstanceValidatorR5Test {
return retVal;
}
});
when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
when(myMockSupport.validateCode(nullable(FhirContext.class), nullable(String.class), nullable(String.class), nullable(String.class), nullable(String.class))).thenAnswer(new Answer<CodeValidationResult>() {
@Override
public CodeValidationResult answer(InvocationOnMock theInvocation) throws Throwable {
FhirContext ctx = (FhirContext) theInvocation.getArguments()[0];
String system = (String) theInvocation.getArguments()[1];
String code = (String) theInvocation.getArguments()[2];
public CodeValidationResult answer(InvocationOnMock theInvocation) {
FhirContext ctx = theInvocation.getArgument(0, FhirContext.class);
String system = theInvocation.getArgument(1, String.class);
String code = theInvocation.getArgument(2, String.class);
String display = theInvocation.getArgument(3, String.class);
String valueSetUrl = theInvocation.getArgument(4, String.class);
CodeValidationResult retVal;
if (myValidConcepts.contains(system + "___" + code)) {
retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code)));
} else {
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, (String) theInvocation.getArguments()[2]);
retVal = myDefaultValidationSupport.validateCode(ctx, system, code, display, valueSetUrl);
}
ourLog.debug("validateCode({}, {}, {}) : {}", new Object[]{system, code, (String) theInvocation.getArguments()[2], retVal});
ourLog.debug("validateCode({}, {}, {}, {}) : {}", system, code, display, valueSetUrl, retVal);
return retVal;
}
});

View File

@ -62,7 +62,7 @@ public class QuestionnaireResponseValidatorR5Test {
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
ValidationSupportChain validationSupport = new ValidationSupportChain(myDefaultValidationSupport, myValSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
@ -191,9 +191,9 @@ public class QuestionnaireResponseValidatorR5Test {
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true);
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true);
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any(), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent().setCode("code0")));
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any()))
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any(), any()))
.thenReturn(new CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code"));
CodeSystem codeSystem = new CodeSystem();
@ -248,7 +248,7 @@ public class QuestionnaireResponseValidatorR5Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Validation failed for 'http://codesystems.com/system2#code3'"));
assertThat(errors.toString(), containsString("Unknown code: http://codesystems.com/system2 / code3"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
}
@ -345,7 +345,7 @@ public class QuestionnaireResponseValidatorR5Test {
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -402,7 +402,7 @@ public class QuestionnaireResponseValidatorR5Test {
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
@ -522,7 +522,7 @@ public class QuestionnaireResponseValidatorR5Test {
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class))).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class), any())).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
QuestionnaireResponse qa;
ValidationResult errors;

View File

@ -51,12 +51,31 @@
to Elastic in the future.
]]>
</action>
<action type="fix">
<![CDATA[
<b>New Feature</b>:
A new set of operations have been added to the JPA server that allow CodeSystem deltas to be
uploaded. A CodeSystem Delta consists of a set of codes and relationships that are added or
removed incrementally to the live CodeSystem without requiring a downtime or a complete
upload of the contents. In addition, the HAPI FHIR CLI
<code>upload-terminology</code> command has been modified to support this new functionality.
]]>
</action>
<action type="add" issue="1489">
<![CDATA[
<b>Improvement</b>:
<b>Performance Improvement</b>:
A significant performance improvement was made to the parsers (particularly the Json Parser)
when serializing resources. This work yields improvements of 20-50% in raw encode speed when
encoding large resources. Thanks to David Maplesden for the pull request!
]]>
</action>
<action type="add" issue="1489">
<![CDATA[
<b>Performance Improvement</b>:
When running inside a JPA server, The DSTU3+ validator now performs code validations
by directly testing ValueSet membership against a pre-calculated copy of the ValueSet,
instead of first expanding the ValueSet and then examining the expanded contents.
This can yield a significant improvement in validation speed in many cases.
]]>
</action>
<action type="add" issue="1357">
@ -315,9 +334,6 @@
that was too short to hold the longest name from the final R4 definitions. This has been
corrected to account for names up to 40 characters long.
</action>
<action type="fix">
A new command has been added to the HAPI FHIR CLI that allows external (not-present) codesystem deltas to be manually uploaded
</action>
<action type="fix">
The subscription triggering operation was not able to handle commas within search URLs being
used to trigger resources for subscription checking. This has been corrected.
@ -359,6 +375,14 @@
as search requests with no search parameters, leading to confusing search results. These will now
result in an HTTP 400 error with a meaningful error message.
</action>
<action type="change">
The
<![CDATA[<code>IValidationSupport#validateCode(...)</code>]]>
method has been modified to add an additional parameter (String theValueSetUrl).
Most users will be unaffected by this change as HAPI FHIR provides a number of
built-in implementations of this interface, but any direct user implementations
of this interface will need to add the new parameter.
</action>
<action type="add" issue="1541">
The server CapabilityStatement (/metadata) endpoint now respects the Cache-Control header. Thanks
to Jens Villadsen for the pull request!