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 @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 // TODO: implement
return null; 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); 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 * 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" * 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 * @param theDisplay The display name, if it should also be validated
* @return Returns a validation result object * @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 * 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.apache.commons.text.StringEscapeUtils;
import org.codehaus.stax2.XMLOutputFactory2; import org.codehaus.stax2.XMLOutputFactory2;
import org.codehaus.stax2.io.EscapingWriterFactory; 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.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.*; import javax.xml.stream.*;
@ -1830,23 +1834,30 @@ public class XmlUtil {
} }
} }
public static DocumentBuilderFactory newDocumentBuilderFactory() { public static Document parseDocument(String theInput) throws IOException, SAXException {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = null;
docBuilderFactory.setNamespaceAware(true);
docBuilderFactory.setXIncludeAware(false);
docBuilderFactory.setExpandEntityReferences(false);
try { try {
docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false); docBuilderFactory.setNamespaceAware(true);
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); docBuilderFactory.setXIncludeAware(false);
docBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); docBuilderFactory.setExpandEntityReferences(false);
docBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); try {
throwUnitTestExceptionIfConfiguredToDoSo(); docBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (Exception e) { docBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
ourLog.warn("Failed to set feature on XML parser: " + e.toString()); 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 { 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); IGenericClient retVal = myFhirCtx.newRestfulGenericClient(theBaseUrl);
String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName); String basicAuthHeaderValue = getAndParseOptionBasicAuthHeader(theCommandLine, theBasicAuthOptionName);

View File

@ -91,7 +91,7 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

@ -98,7 +98,7 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal
} }
@Override @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; 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.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.util.AttachmentUtil; import ca.uhn.fhir.util.AttachmentUtil;
import ca.uhn.fhir.util.FileUtil;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; 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.IBaseParameters;
import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.instance.model.api.ICompositeType;
import java.io.ByteArrayOutputStream; import java.io.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -46,8 +48,9 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class UploadTerminologyCommand extends BaseCommand { public class UploadTerminologyCommand extends BaseCommand {
static final String UPLOAD_TERMINOLOGY = "upload-terminology"; 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 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 @Override
public String getCommandDescription() { public String getCommandDescription() {
@ -105,23 +108,25 @@ public class UploadTerminologyCommand extends BaseCommand {
switch (mode) { switch (mode) {
case SNAPSHOT: 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; break;
case ADD: 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; break;
case REMOVE: 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; 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); ParametersUtil.addParameterToParametersUri(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_SYSTEM, theTermUrl);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8); ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream, Charsets.UTF_8);
int compressedSourceBytesCount = 0;
int compressedFileCount = 0;
boolean haveCompressedContents = false; boolean haveCompressedContents = false;
try { try {
for (String nextDataFile : theDatafile) { for (String nextDataFile : theDatafile) {
@ -133,19 +138,19 @@ public class UploadTerminologyCommand extends BaseCommand {
ZipEntry nextEntry = new ZipEntry(stripPath(nextDataFile)); ZipEntry nextEntry = new ZipEntry(stripPath(nextDataFile));
zipOutputStream.putNextEntry(nextEntry); zipOutputStream.putNextEntry(nextEntry);
IOUtils.copy(fileInputStream, zipOutputStream); CountingInputStream countingInputStream = new CountingInputStream(fileInputStream);
IOUtils.copy(countingInputStream, zipOutputStream);
haveCompressedContents = true; haveCompressedContents = true;
compressedSourceBytesCount += countingInputStream.getCount();
zipOutputStream.flush(); zipOutputStream.flush();
ourLog.info("Finished compressing {} into {}", nextEntry.getSize(), nextEntry.getCompressedSize()); ourLog.info("Finished compressing {}", nextDataFile);
} else { } else {
ourLog.info("Adding file: {}", nextDataFile); ourLog.info("Adding file: {}", nextDataFile);
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx); String fileName = "file:" + nextDataFile;
AttachmentUtil.setUrl(myFhirCtx, attachment, "file:" + nextDataFile); addFileToRequestBundle(theInputParameters, fileName, IOUtils.toByteArray(fileInputStream));
AttachmentUtil.setData(myFhirCtx, attachment, IOUtils.toByteArray(fileInputStream));
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment);
} }
} }
@ -158,16 +163,16 @@ public class UploadTerminologyCommand extends BaseCommand {
} }
if (haveCompressedContents) { if (haveCompressedContents) {
ICompositeType attachment = AttachmentUtil.newInstance(myFhirCtx); byte[] compressedBytes = byteArrayOutputStream.toByteArray();
AttachmentUtil.setUrl(myFhirCtx, attachment, "file:/files.zip"); ourLog.info("Compressed {} bytes in {} file(s) into {} bytes", FileUtil.formatFileSize(compressedSourceBytesCount), compressedFileCount, FileUtil.formatFileSize(compressedBytes.length));
AttachmentUtil.setData(myFhirCtx, attachment, byteArrayOutputStream.toByteArray());
ParametersUtil.addParameterToParameters(myFhirCtx, theInputParameters, TerminologyUploaderProvider.PARAM_FILE, attachment); addFileToRequestBundle(theInputParameters, "file:/files.zip", compressedBytes);
} }
ourLog.info("Beginning upload - This may take a while..."); ourLog.info("Beginning upload - This may take a while...");
if (ourLog.isDebugEnabled() || "true".equals(System.getProperty("test"))) { 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; IBaseParameters response;
@ -190,11 +195,50 @@ public class UploadTerminologyCommand extends BaseCommand {
ourLog.info("Response:\n{}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(response)); 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 { private enum ModeEnum {
SNAPSHOT, ADD, REMOVE 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; String retVal = thePath;
if (retVal.contains("/")) { if (retVal.contains("/")) {
retVal = retVal.substring(retVal.lastIndexOf("/")); retVal = retVal.substring(retVal.lastIndexOf("/"));

View File

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

View File

@ -1,11 +1,10 @@
package ca.uhn.fhir.cli; package ca.uhn.fhir.cli;
import ca.uhn.fhir.context.FhirContext; 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.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.term.UploadStatistics; import ca.uhn.fhir.jpa.term.UploadStatistics;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; 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.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.test.utilities.JettyUtil;
import com.google.common.base.Charsets; 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)); 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 { private void writeConceptAndHierarchyFiles() throws IOException {
try (FileWriter w = new FileWriter(myConceptsFile, false)) { try (FileWriter w = new FileWriter(myConceptsFile, false)) {
@ -229,6 +259,8 @@ public class UploadTerminologyCommandTest extends BaseTest {
FileUtils.deleteQuietly(myConceptsFile); FileUtils.deleteQuietly(myConceptsFile);
FileUtils.deleteQuietly(myHierarchyFile); FileUtils.deleteQuietly(myHierarchyFile);
FileUtils.deleteQuietly(myArchiveFile); FileUtils.deleteQuietly(myArchiveFile);
UploadTerminologyCommand.setTransferSizeLimitForUnitTest(-1);
} }
@Before @Before

View File

@ -290,7 +290,7 @@ public class ValidatorExamples {
} }
@Override @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) // TODO: implement (or return null if your implementation does not support this function)
return null; return null;
} }

View File

@ -123,7 +123,7 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

@ -74,6 +74,10 @@ public class DaoConfig {
))); )));
private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class); private static final Logger ourLog = LoggerFactory.getLogger(DaoConfig.class);
private static final int DEFAULT_EXPUNGE_BATCH_SIZE = 800; 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; private IndexEnabledEnum myIndexMissingFieldsEnabled = IndexEnabledEnum.DISABLED;
/** /**
@ -115,10 +119,7 @@ public class DaoConfig {
* update setter javadoc if default changes * update setter javadoc if default changes
*/ */
private boolean myIndexContainedResources = true; private boolean myIndexContainedResources = true;
/** private int myMaximumExpansionSize = DEFAULT_MAX_EXPANSION_SIZE;
* update setter javadoc if default changes
*/
private int myMaximumExpansionSize = 5000;
private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION; private Integer myMaximumSearchResultCountInTransaction = DEFAULT_MAXIMUM_SEARCH_RESULT_COUNT_IN_TRANSACTION;
private ResourceEncodingEnum myResourceEncoding = ResourceEncodingEnum.JSONC; 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 * 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 * 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) { public void setMaximumExpansionSize(int theMaximumExpansionSize) {
Validate.isTrue(theMaximumExpansionSize > 0, "theMaximumExpansionSize must be > 0"); 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") @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); Optional<TermConcept> findByCodeSystemAndCode(@Param("code_system") TermCodeSystemVersion theCodeSystem, @Param("code") String theCode);
@Query("SELECT t FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid") @Query("SELECT t.myId FROM TermConcept t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<TermConcept> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid); Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system") @Query("SELECT c FROM TermConcept c WHERE c.myCodeSystem = :code_system")
List<TermConcept> findByCodeSystemVersion(@Param("code_system") TermCodeSystemVersion theCodeSystem); 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") @Query("SELECT t FROM TermConcept t WHERE t.myIndexStatus = null")
Page<TermConcept> findResourcesRequiringReindexing(Pageable thePageRequest); 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> { public interface ITermConceptDesignationDao extends JpaRepository<TermConceptDesignation, Long> {
@Query("SELECT t FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid") @Query("SELECT t.myId FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Slice<TermConceptDesignation> findByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid); Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("csv_pid") Long thePid);
@Query("SELECT COUNT(t) FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid") @Query("SELECT COUNT(t) FROM TermConceptDesignation t WHERE t.myCodeSystemVersion.myId = :csv_pid")
Integer countByCodeSystemVersion(@Param("csv_pid") Long thePid); 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") @Query("SELECT t.myParentPid FROM TermConceptParentChildLink t WHERE t.myChildPid = :child_pid")
Collection<Long> findAllWithChild(@Param("child_pid") Long theConceptPid); Collection<Long> findAllWithChild(@Param("child_pid") Long theConceptPid);
@Query("SELECT t FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid") @Query("SELECT t.myPid FROM TermConceptParentChildLink t WHERE t.myCodeSystem.myId = :cs_pid")
Slice<TermConceptParentChildLink> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid); 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> { public interface ITermConceptPropertyDao extends JpaRepository<TermConceptProperty, Long> {
@Query("SELECT t FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid") @Query("SELECT t.myId FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Slice<TermConceptProperty> findByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid); Slice<Long> findIdsByCodeSystemVersion(Pageable thePage, @Param("cs_pid") Long thePid);
@Query("SELECT COUNT(t) FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid") @Query("SELECT COUNT(t) FROM TermConceptProperty t WHERE t.myCodeSystemVersion.myId = :cs_pid")
Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid); Integer countByCodeSystemVersion(@Param("cs_pid") Long thePid);

View File

@ -93,7 +93,7 @@ public class JpaValidationSupportDstu3 extends BaseJpaValidationSupport implemen
@Override @Override
@Transactional(value = TxType.SUPPORTS) @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; return null;
} }

View File

@ -99,7 +99,7 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> imple
} }
} }
if (allSystemsAreSuppportedByTerminologyService) { if (allSystemsAreSuppportedByTerminologyService) {
return myTerminologySvc.expandValueSet(theSource); return myTerminologySvc.expandValueSetInMemory(theSource, null);
} }
HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport); HapiWorkerContext workerContext = new HapiWorkerContext(getContext(), myValidationSupport);
@ -111,12 +111,6 @@ public class FhirResourceDaoValueSetR4 extends FhirResourceDaoR4<ValueSet> imple
return retVal; 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) { private ValueSet doExpand(ValueSet theSource, int theOffset, int theCount) {

View File

@ -85,7 +85,7 @@ public class JpaValidationSupportR4 extends BaseJpaValidationSupport implements
@Override @Override
@Transactional(value = TxType.SUPPORTS) @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; return null;
} }

View File

@ -87,7 +87,7 @@ public class JpaValidationSupportR5 extends BaseJpaValidationSupport implements
@Override @Override
@Transactional(value = TxType.SUPPORTS) @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; 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.context.support.IContextValidationSupport;
import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum; import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor; import ca.uhn.fhir.jpa.search.DeferConceptIndexingInterceptor;
import ca.uhn.fhir.jpa.term.VersionIndependentConcept;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder; 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.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.AttachmentUtil; import ca.uhn.fhir.util.AttachmentUtil;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
@ -40,7 +39,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.servlet.http.HttpServletRequest; 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.ArrayList;
import java.util.List; import java.util.List;
@ -86,7 +88,6 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
public IBaseParameters uploadSnapshot( public IBaseParameters uploadSnapshot(
HttpServletRequest theServletRequest, HttpServletRequest theServletRequest,
@OperationParam(name = PARAM_SYSTEM, min = 1, typeName = "uri") IPrimitiveType<String> theCodeSystemUrl, @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, @OperationParam(name = PARAM_FILE, min = 0, max = OperationParam.MAX_UNLIMITED, typeName = "attachment") List<ICompositeType> theFiles,
RequestDetails theRequestDetails RequestDetails theRequestDetails
) { ) {
@ -97,66 +98,15 @@ public class TerminologyUploaderProvider extends BaseJpaProvider {
throw new InvalidRequestException("Missing mandatory parameter: " + PARAM_SYSTEM); throw new InvalidRequestException("Missing mandatory parameter: " + PARAM_SYSTEM);
} }
if (theLocalFile == null || theLocalFile.size() == 0) { if (theFiles == null || theFiles.size() == 0) {
if (theFiles == null || theFiles.size() == 0) { throw new InvalidRequestException("No '" + PARAM_FILE + "' parameter, or package had no data");
throw new InvalidRequestException("No 'localfile' or 'package' parameter, or package had no data"); }
} for (ICompositeType next : theFiles) {
for (ICompositeType next : theFiles) {
ValidateUtil.isTrueOrThrowInvalidRequest(myCtx.getElementDefinition(next.getClass()).getName().equals("Attachment"), "Package must be of type Attachment"); ValidateUtil.isTrueOrThrowInvalidRequest(myCtx.getElementDefinition(next.getClass()).getName().equals("Attachment"), "Package must be of type Attachment");
} }
}
try { try {
List<ITermLoaderSvc.FileDescriptor> localFiles = new ArrayList<>(); List<ITermLoaderSvc.FileDescriptor> localFiles = convertAttachmentsToFileDescriptors(theFiles);
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);
}
});
}
}
String codeSystemUrl = theCodeSystemUrl.getValue(); String codeSystemUrl = theCodeSystemUrl.getValue();
codeSystemUrl = trim(codeSystemUrl); 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) { 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<>(); List<ITermLoaderSvc.FileDescriptor> files = new ArrayList<>();
for (ICompositeType next : theFiles) { for (ICompositeType next : theFiles) {
byte[] nextData = AttachmentUtil.getOrCreateData(myCtx, next).getValue();
String nextUrl = AttachmentUtil.getOrCreateUrl(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"); 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; 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.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.*;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; 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.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
import ca.uhn.fhir.jpa.model.sched.ScheduledJobDefinition; 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.ITermCodeSystemStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.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.jpa.util.ScrollableResultsIterator;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -59,7 +61,9 @@ import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.BooleanJunction; import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder; import org.hibernate.search.query.dsl.QueryBuilder;
import org.hl7.fhir.exceptions.FHIRException; 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.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.codesystems.ConceptSubsumptionOutcome; 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.TransactionDefinition;
import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional; 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 org.springframework.transaction.support.TransactionTemplate;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -151,6 +157,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
private ITermDeferredStorageSvc myDeferredStorageSvc; private ITermDeferredStorageSvc myDeferredStorageSvc;
@Autowired(required = false) @Autowired(required = false)
private ITermCodeSystemStorageSvc myConceptStorageSvc; private ITermCodeSystemStorageSvc myConceptStorageSvc;
private IContextValidationSupport myValidationSupport;
private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) { private void addCodeIfNotAlreadyAdded(IValueSetConceptAccumulator theValueSetCodeAccumulator, Set<String> theAddedCodes, TermConcept theConcept, boolean theAdd, AtomicInteger theCodeCounter) {
String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri(); String codeSystem = theConcept.getCodeSystemVersion().getCodeSystem().getCodeSystemUri();
@ -193,7 +200,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (retVal) { if (retVal) {
if (theSetToPopulate.size() >= myDaoConfig.getMaximumExpansionSize()) { if (theSetToPopulate.size() >= myDaoConfig.getMaximumExpansionSize()) {
String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize()); String msg = myContext.getLocalizer().getMessage(BaseTermReadSvcImpl.class, "expansionTooLarge", myDaoConfig.getMaximumExpansionSize());
throw new InvalidRequestException(msg); throw new ExpansionTooCostlyException(msg);
} }
} }
return retVal; return retVal;
@ -232,7 +239,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
myTranslationWithReverseCache.invalidateAll(); myTranslationWithReverseCache.invalidateAll();
} }
public void deleteConceptMap(ResourceTable theResourceTable) { public void deleteConceptMap(ResourceTable theResourceTable) {
// Get existing entity so it can be deleted. // Get existing entity so it can be deleted.
Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(theResourceTable.getId()); Optional<TermConceptMap> optionalExistingTermConceptMapById = myConceptMapDao.findTermConceptMapByResourcePid(theResourceTable.getId());
@ -288,18 +294,18 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
deleteValueSet(theResourceTable); deleteValueSet(theResourceTable);
} }
@Override @Override
@Transactional(propagation = Propagation.REQUIRED) @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.setIdentifier(UUID.randomUUID().toString());
expansionComponent.setTimestamp(new Date()); expansionComponent.setTimestamp(new Date());
AtomicInteger codeCounter = new AtomicInteger(0); AtomicInteger codeCounter = new AtomicInteger(0);
expandValueSet(theValueSetToExpand, expansionComponent, codeCounter); expandValueSet(theValueSetToExpand, expansionComponent, codeCounter, theWantConceptOrNull);
expansionComponent.setTotal(codeCounter.get()); expansionComponent.setTotal(codeCounter.get());
@ -327,7 +333,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (!optionalTermValueSet.isPresent()) { if (!optionalTermValueSet.isPresent()) {
ourLog.warn("ValueSet is not present in terminology tables. Will perform in-memory expansion without parameters. {}", getValueSetInfo(theValueSetToExpand)); 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(); TermValueSet termValueSet = optionalTermValueSet.get();
@ -335,7 +341,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
if (termValueSet.getExpansionStatus() != TermValueSetPreExpansionStatusEnum.EXPANDED) { 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: {} | {}", 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()); 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(); ValueSet.ValueSetExpansionComponent expansionComponent = new ValueSet.ValueSetExpansionComponent();
@ -431,11 +437,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
@Override @Override
@Transactional(propagation = Propagation.REQUIRED) @Transactional(propagation = Propagation.REQUIRED)
public void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { public void expandValueSet(ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {
expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0)); expandValueSet(theValueSetToExpand, theValueSetCodeAccumulator, new AtomicInteger(0), null);
} }
@SuppressWarnings("ConstantConditions") @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<>(); Set<String> addedCodes = new HashSet<>();
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
@ -449,7 +455,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
int queryIndex = i; int queryIndex = i;
Boolean shouldContinue = myTxTemplate.execute(t -> { Boolean shouldContinue = myTxTemplate.execute(t -> {
boolean add = true; boolean add = true;
return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter, queryIndex); return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, include, add, theCodeCounter, queryIndex, theWantConceptOrNull);
}); });
if (!shouldContinue) { if (!shouldContinue) {
break; 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 // Handle excludes
ourLog.debug("Handling excludes"); ourLog.debug("Handling excludes");
for (ValueSet.ConceptSetComponent exclude : theValueSetToExpand.getCompose().getExclude()) { for (ValueSet.ConceptSetComponent exclude : theValueSetToExpand.getCompose().getExclude()) {
@ -464,7 +476,7 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
int queryIndex = i; int queryIndex = i;
Boolean shouldContinue = myTxTemplate.execute(t -> { Boolean shouldContinue = myTxTemplate.execute(t -> {
boolean add = false; boolean add = false;
return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, exclude, add, theCodeCounter, queryIndex); return expandValueSetHandleIncludeOrExclude(theValueSetCodeAccumulator, addedCodes, exclude, add, theCodeCounter, queryIndex, null);
}); });
if (!shouldContinue) { if (!shouldContinue) {
break; break;
@ -515,8 +527,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return sb.toString(); return sb.toString();
} }
protected List<VersionIndependentConcept> expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4) { protected List<VersionIndependentConcept> expandValueSetAndReturnVersionIndependentConcepts(org.hl7.fhir.r4.model.ValueSet theValueSetToExpandR4, VersionIndependentConcept theWantConceptOrNull) {
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSet(theValueSetToExpandR4).getExpansion(); org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = expandValueSetInMemory(theValueSetToExpandR4, theWantConceptOrNull).getExpansion();
ArrayList<VersionIndependentConcept> retVal = new ArrayList<>(); ArrayList<VersionIndependentConcept> retVal = new ArrayList<>();
for (org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent nextContains : expandedR4.getContains()) { 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. * @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(); String system = theIncludeOrExclude.getSystem();
boolean hasSystem = isNotBlank(system); boolean hasSystem = isNotBlank(system);
boolean hasValueSet = theIncludeOrExclude.getValueSet().size() > 0; boolean hasValueSet = theIncludeOrExclude.getValueSet().size() > 0;
if (hasSystem) { if (hasSystem) {
if (theWantConceptOrNull != null && !system.equals(theWantConceptOrNull.getSystem())) {
return false;
}
ourLog.info("Starting {} expansion around CodeSystem: {}", (theAdd ? "inclusion" : "exclusion"), system); ourLog.info("Starting {} expansion around CodeSystem: {}", (theAdd ? "inclusion" : "exclusion"), system);
TermCodeSystem cs = myCodeSystemDao.findByCodeSystemUri(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()); bool.must(qb.keyword().onField("myCodeSystemVersionPid").matching(csv.getPid()).createQuery());
if (theWantConceptOrNull != null) {
bool.must(qb.keyword().onField("myCode").matching(theWantConceptOrNull.getCode()).createQuery());
}
/* /*
* Filters * Filters
*/ */
@ -603,6 +624,21 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
AtomicInteger count = new AtomicInteger(0); AtomicInteger count = new AtomicInteger(0);
int maxResultsPerBatch = 10000; 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.setMaxResults(maxResultsPerBatch);
jpaQuery.setFirstResult(theQueryIndex * maxResultsPerBatch); jpaQuery.setFirstResult(theQueryIndex * maxResultsPerBatch);
@ -618,7 +654,11 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
count.incrementAndGet(); count.incrementAndGet();
countForBatch.incrementAndGet(); countForBatch.incrementAndGet();
TermConcept concept = (TermConcept) next; 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()); 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()) { if (!theIncludeOrExclude.getConcept().isEmpty()) {
for (ValueSet.ConceptReferenceComponent next : theIncludeOrExclude.getConcept()) { for (ValueSet.ConceptReferenceComponent next : theIncludeOrExclude.getConcept()) {
String nextCode = next.getCode(); String nextCode = next.getCode();
if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) { if (theWantConceptOrNull == null || theWantConceptOrNull.getCode().equals(nextCode)) {
CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode); if (isNoneBlank(system, nextCode) && !theAddedCodes.contains(system + "|" + nextCode)) {
if (code != null) { CodeSystem.ConceptDefinitionComponent code = findCode(codeSystemFromContext.getConcept(), nextCode);
if (theAdd && theAddedCodes.add(system + "|" + nextCode)) { if (code != null) {
theValueSetCodeAccumulator.includeConcept(system, nextCode, code.getDisplay()); if (theAdd && theAddedCodes.add(system + "|" + nextCode)) {
} theValueSetCodeAccumulator.includeConcept(system, nextCode, code.getDisplay());
if (!theAdd && theAddedCodes.remove(system + "|" + nextCode)) { }
theValueSetCodeAccumulator.excludeConcept(system, nextCode); if (!theAdd && theAddedCodes.remove(system + "|" + nextCode)) {
theValueSetCodeAccumulator.excludeConcept(system, nextCode);
}
} }
} }
} }
} }
} else { } else {
List<CodeSystem.ConceptDefinitionComponent> concept = codeSystemFromContext.getConcept(); 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); addConceptsToList(theValueSetCodeAccumulator, theAddedCodes, system, concept, theAdd);
} }
@ -1217,7 +1263,6 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return myCodeSystemDao.findByCodeSystemUri(theSystem); return myCodeSystemDao.findByCodeSystemUri(theSystem);
} }
@Override @Override
public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException { public void setApplicationContext(ApplicationContext theApplicationContext) throws BeansException {
myApplicationContext = theApplicationContext; myApplicationContext = theApplicationContext;
@ -1226,7 +1271,12 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
@PostConstruct @PostConstruct
public void start() { public void start() {
myValueSetResourceDao = myApplicationContext.getBean(IFhirResourceDaoValueSet.class); 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 @PostConstruct
@ -1492,6 +1542,8 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return new IFhirResourceDaoCodeSystem.SubsumesResult(subsumes); return new IFhirResourceDaoCodeSystem.SubsumesResult(subsumes);
} }
protected abstract ValueSet toCanonicalValueSet(IBaseResource theValueSet);
protected IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { protected IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
return txTemplate.execute(t -> { return txTemplate.execute(t -> {
@ -1753,11 +1805,36 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc, ApplicationCo
return retVal; return retVal;
} }
void throwInvalidValueSet(String theValueSet) {
protected void throwInvalidValueSet(String theValueSet) {
throw new ResourceNotFoundException("Unknown ValueSet: " + UrlUtil.escapeUrlParam(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 { public static class PreExpandValueSetsJob implements Job {
@Autowired @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 ca.uhn.fhir.jpa.entity.TermConceptDesignation;
import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
public interface IValueSetConceptAccumulator { public interface IValueSetConceptAccumulator {
@ -32,4 +33,9 @@ public interface IValueSetConceptAccumulator {
void excludeConcept(String theSystem, String theCode); 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 // Parent/Child links
{ {
String descriptor = "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); Supplier<Integer> counter = () -> myConceptParentChildLinkDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptParentChildLinkDao); doDelete(descriptor, loader, counter, myConceptParentChildLinkDao);
} }
@ -459,7 +459,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Properties // Properties
{ {
String descriptor = "concept 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); Supplier<Integer> counter = () -> myConceptPropertyDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptPropertyDao); doDelete(descriptor, loader, counter, myConceptPropertyDao);
} }
@ -467,7 +467,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
// Designations // Designations
{ {
String descriptor = "concept 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); Supplier<Integer> counter = () -> myConceptDesignationDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDesignationDao); doDelete(descriptor, loader, counter, myConceptDesignationDao);
} }
@ -477,7 +477,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
String descriptor = "concepts"; String descriptor = "concepts";
// For some reason, concepts are much slower to delete, so use a smaller batch size // For some reason, concepts are much slower to delete, so use a smaller batch size
PageRequest page100 = PageRequest.of(0, 100); 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); Supplier<Integer> counter = () -> myConceptDao.countByCodeSystemVersion(theCodeSystemVersionPid);
doDelete(descriptor, loader, counter, myConceptDao); 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; int count;
ourLog.info(" * Deleting {}", theDescriptor); ourLog.info(" * Deleting {}", theDescriptor);
int totalCount = theCounter.get(); int totalCount = theCounter.get();
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
count = 0; count = 0;
while (true) { while (true) {
Slice<T> link = theLoader.get(); Slice<Long> link = theLoader.get();
if (!link.hasContent()) { if (!link.hasContent()) {
break; break;
} }
@ -733,7 +733,7 @@ public class TermCodeSystemStorageSvcImpl implements ITermCodeSystemStorageSvc {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.execute(t -> { txTemplate.execute(t -> {
theDao.deleteAll(link); link.forEach(id -> theDao.deleteById(id));
return null; return null;
}); });

View File

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

View File

@ -72,6 +72,11 @@ public class TermReadSvcDstu2 extends BaseTermReadSvcImpl {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
protected ValueSet toCanonicalValueSet(IBaseResource theValueSet) {
throw new UnsupportedOperationException();
}
@Override @Override
public IBaseResource expandValueSet(IBaseResource theValueSetToExpand) { public IBaseResource expandValueSet(IBaseResource theValueSetToExpand) {
throw new UnsupportedOperationException(); 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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; 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.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3; import ca.uhn.fhir.jpa.term.api.ITermReadSvcDstu3;
@ -103,8 +102,8 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
try { try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand); valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand);
org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = super.expandValueSet(valueSetToExpandR4).getExpansion(); org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent expandedR4 = super.expandValueSetInMemory(valueSetToExpandR4, null).getExpansion();
return VersionConvertor_30_40.convertValueSetExpansionComponent(expandedR4); return VersionConvertor_30_40.convertValueSetExpansionComponent(expandedR4);
} catch (FHIRException e) { } catch (FHIRException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
@ -117,21 +116,28 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
try { try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; 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); org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSetInMemory(valueSetToExpandR4, null);
return VersionConvertor_30_40.convertValueSet(expandedR4); return VersionConvertor_30_40.convertValueSet(expandedR4);
} catch (FHIRException e) { } catch (FHIRException e) {
throw new InternalErrorException(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 @Override
public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) { public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) {
ValueSet valueSetToExpand = (ValueSet) theInput; ValueSet valueSetToExpand = (ValueSet) theInput;
try { try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; 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); org.hl7.fhir.r4.model.ValueSet expandedR4 = super.expandValueSet(valueSetToExpandR4, theOffset, theCount);
return VersionConvertor_30_40.convertValueSet(expandedR4); return VersionConvertor_30_40.convertValueSet(expandedR4);
} catch (FHIRException e) { } catch (FHIRException e) {
@ -145,7 +151,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
try { try {
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(valueSetToExpand); valueSetToExpandR4 = toCanonicalValueSet(valueSetToExpand);
super.expandValueSet(valueSetToExpandR4, theValueSetCodeAccumulator); super.expandValueSet(valueSetToExpandR4, theValueSetCodeAccumulator);
} catch (FHIRException e) { } catch (FHIRException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
@ -162,13 +168,13 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4; org.hl7.fhir.r4.model.ValueSet valueSetToExpandR4;
try { try {
valueSetToExpandR4 = VersionConvertor_30_40.convertValueSet(vs); valueSetToExpandR4 = toCanonicalValueSet(vs);
} catch (FHIRException e) { } catch (FHIRException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
return expandValueSetAndReturnVersionIndependentConcepts(valueSetToExpandR4); return expandValueSetAndReturnVersionIndependentConcepts(valueSetToExpandR4, null);
} }
@Override @Override
@ -262,7 +268,7 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
org.hl7.fhir.r4.model.ValueSet valueSetR4; org.hl7.fhir.r4.model.ValueSet valueSetR4;
try { try {
valueSetR4 = VersionConvertor_30_40.convertValueSet(valueSet); valueSetR4 = toCanonicalValueSet(valueSet);
} catch (FHIRException e) { } catch (FHIRException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
@ -277,25 +283,30 @@ public class TermReadSvcDstu3 extends BaseTermReadSvcImpl implements IValidation
@CoverageIgnore @CoverageIgnore
@Override @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) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); Optional<VersionIndependentConcept> codeOpt = Optional.empty();
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); boolean haveValidated = false;
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;
}
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 @Override

View File

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

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.term;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; 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.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5; import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.CoverageIgnore;
@ -91,26 +90,26 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
super.throwInvalidValueSet(theValueSet); 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 @Override
public IBaseResource expandValueSet(IBaseResource theInput) { 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 valueSetToExpand = toCanonicalValueSet(theInput);
org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSet(valueSetToExpand); org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSetInMemory(valueSetToExpand, null);
return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR4); return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR4);
} }
@Override @Override
public IBaseResource expandValueSet(IBaseResource theInput, int theOffset, int theCount) { 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); org.hl7.fhir.r4.model.ValueSet valueSetR4 = super.expandValueSet(valueSetToExpand, theOffset, theCount);
return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR4); return org.hl7.fhir.convertors.conv40_50.ValueSet.convertValueSet(valueSetR4);
} }
@Override @Override
public void expandValueSet(IBaseResource theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) { 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); super.expandValueSet(valueSetToExpand, theValueSetCodeAccumulator);
} }
@ -118,7 +117,7 @@ public class TermReadSvcR5 extends BaseTermReadSvcImpl implements IValidationSup
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSet valueSetToExpand = new ValueSet(); ValueSet valueSetToExpand = new ValueSet();
valueSetToExpand.getCompose().addInclude(theInclude); 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)); 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 @CoverageIgnore
@Override @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) {
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); Optional<VersionIndependentConcept> codeOpt = Optional.empty();
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); boolean haveValidated = false;
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;
}
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 @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) { public ValidateCodeResult validateCodeIsInPreExpandedValueSet(IBaseResource theValueSet, String theSystem, String theCode, String theDisplay, IBaseDatatype theCoding, IBaseDatatype theCodeableConcept) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet; 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; Coding coding = (Coding) theCoding;
org.hl7.fhir.r4.model.Coding codingR4 = null; 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); 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 @Override
public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) { public boolean isValueSetPreExpandedForCodeValidation(IBaseResource theValueSet) {
ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null"); ValidateUtil.isNotNullOrThrowUnprocessableEntity(theValueSet, "ValueSet must not be null");
ValueSet valueSet = (ValueSet) theValueSet; 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); return super.isValueSetPreExpandedForCodeValidation(valueSetR4);
} }
} }

View File

@ -20,22 +20,40 @@ package ca.uhn.fhir.jpa.term;
* #L% * #L%
*/ */
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.entity.TermConceptDesignation; 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.model.api.annotation.Block;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
@Block() @Block()
public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetConceptAccumulator { public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.ValueSetExpansionComponent implements IValueSetConceptAccumulator {
private final int myMaxResults = 50000; private final int myMaxCapacity;
private final FhirContext myContext;
private int myConceptsCount; 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; myConceptsCount = 0;
} }
@Nullable
@Override
public Integer getCapacityRemaining() {
return myMaxCapacity - myConceptsCount;
}
@Override @Override
public void includeConcept(String theSystem, String theCode, String theDisplay) { public void includeConcept(String theSystem, String theCode, String theDisplay) {
incrementConceptsCount(); incrementConceptsCount();
@ -76,8 +94,9 @@ public class ValueSetExpansionComponentWithConceptAccumulator extends ValueSet.V
} }
private void incrementConceptsCount() { private void incrementConceptsCount() {
if (++myConceptsCount > myMaxResults) { if (++myConceptsCount > myMaxCapacity) {
throw new InternalErrorException("Expansion produced too many (>= " + myMaxResults + ") results"); 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 { public interface ITermReadSvc {
ValueSet expandValueSet(ValueSet theValueSetToExpand); ValueSet expandValueSetInMemory(ValueSet theValueSetToExpand, VersionIndependentConcept theWantConceptOrNull);
ValueSet expandValueSet(ValueSet theValueSetToExpand, int theOffset, int theCount); 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.IInterceptorService;
import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.interceptor.executor.InterceptorService; 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.bulk.IBulkDataExportSvc;
import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.entity.TermConcept;
import ca.uhn.fhir.jpa.model.util.JpaConstants; 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.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier; 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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -722,7 +723,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
try { try {
myObservationDao.search(params).size(); myObservationDao.search(params).size();
fail(); fail();
} catch (InvalidRequestException e) { } catch (InternalErrorException e) {
assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage()); 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.parser.IParser;
import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.TokenParamModifier; 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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -837,7 +838,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
try { try {
myObservationDao.search(params).size(); myObservationDao.search(params).size();
fail(); fail();
} catch (InvalidRequestException e) { } catch (InternalErrorException e) {
assertEquals("Expansion of ValueSet produced too many codes (maximum 1) - Operation aborted!", e.getMessage()); 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.dao.DaoConfig;
import ca.uhn.fhir.jpa.entity.TermCodeSystemVersion; 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.api.ITermReadSvc;
import ca.uhn.fhir.jpa.term.custom.CustomTerminologySet;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum;
@ -34,6 +36,7 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Collections; import java.util.Collections;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -43,7 +46,182 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
@Autowired @Autowired
private IValidatorModule myValidatorModule; private IValidatorModule myValidatorModule;
@Autowired @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 @Test
public void testValidateStructureDefinition() throws Exception { public void testValidateStructureDefinition() throws Exception {
@ -280,7 +458,8 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning); val.setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel.Warning);
myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences());
myDaoConfig.setMaximumExpansionSize(DaoConfig.DEFAULT_MAX_EXPANSION_SIZE);
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
} }
@Test @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.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult; 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 ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
@ -23,6 +25,7 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
@After @After
public void after() { public void after() {
myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets()); myDaoConfig.setPreExpandValueSets(new DaoConfig().isPreExpandValueSets());
myDaoConfig.setMaximumExpansionSize(new DaoConfig().getMaximumExpansionSize());
} }
@AfterClass @AfterClass
@ -230,37 +233,26 @@ public class FhirResourceDaoR4ValueSetTest extends BaseJpaR4Test {
} }
@Test @Test
@Ignore public void testExpandByValueSet_ExceedsMaxSize() {
public void testExpandByIdentifier() { // Add a bunch of codes
ValueSet expanded = myValueSetDao.expandByIdentifier("http://www.healthintersections.com.au/fhir/ValueSet/extensional-case-2", "11378"); CustomTerminologySet codesToAdd = new CustomTerminologySet();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(expanded); for (int i = 0; i < 100; i++) {
ourLog.info(resp); codesToAdd.addRootConcept("CODE" + i, "Display " + i);
//@formatter:off }
assertThat(resp, stringContainsInOrder( myTermCodeSystemStorageSvc.applyDeltaCodeSystemsAdd("http://loinc.org", codesToAdd);
"<code value=\"11378-7\"/>", myDaoConfig.setMaximumExpansionSize(50);
"<display value=\"Systolic blood pressure at First encounter\"/>"));
//@formatter:on
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);
/** try {
* This type of expansion doesn't really make sense.. myValueSetDao.expand(vs, null);
*/ fail();
@Test } catch (InternalErrorException e) {
@Ignore assertEquals("Expansion of ValueSet produced too many codes (maximum 50) - Operation aborted!", e.getMessage());
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\"/>")));
} }

View File

@ -108,7 +108,7 @@ public class TerminologyUploaderProviderR4Test extends BaseResourceProviderR4Tes
.execute(); .execute();
fail(); fail();
} catch (InvalidRequestException e) { } 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) .onType(CodeSystem.class)
.named("upload-external-code-system") .named("upload-external-code-system")
.withParameter(Parameters.class, TerminologyUploaderProvider.PARAM_SYSTEM, new UriType(ITermLoaderSvc.SCT_URI)) .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(); .execute();
String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam); String resp = myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(respParam);

View File

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

View File

@ -1,7 +1,7 @@
package org.hl7.fhir.dstu2016may.hapi.validation; package org.hl7.fhir.dstu2016may.hapi.validation;
import ca.uhn.fhir.context.FhirContext; 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.io.IOUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu2016may.model.*; 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.ConceptReferenceComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; 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.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -258,18 +261,45 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
@Override @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) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); if (isNotBlank(theValueSetUrl)) {
if (cs != null) { HapiWorkerContext workerContext = new HapiWorkerContext(theContext, this);
boolean caseSensitive = true; ValueSetExpander expander = new ValueSetExpanderSimple(workerContext, workerContext);
if (cs.hasCaseSensitive()) { try {
caseSensitive = cs.getCaseSensitive(); 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) { if (theCodeSystem != null) {
return retVal; 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 @Override
public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { 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 static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private BestPracticeWarningLevel myBestPracticeWarningLevel; private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion; private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@ -49,7 +48,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* The validation support * The validation support
*/ */
public FhirInstanceValidator(IValidationSupport theValidationSupport) { public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = XmlUtil.newDocumentBuilderFactory();
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
@ -139,9 +137,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
if (theEncoding == EncodingEnum.XML) { if (theEncoding == EncodingEnum.XML) {
Document document; Document document;
try { try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder(); document = XmlUtil.parseDocument(theInput);
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) { } catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2); ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage(); ValidationMessage m = new ValidationMessage();

View File

@ -177,7 +177,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
@Override @Override
public ValidationResult validateCode(String theSystem, String theCode, String theDisplay) { 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) { if (result == null) {
return 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 * 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(); ValueSet expansion = new ValueSet();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) { for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) {
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) { for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) {

View File

@ -51,6 +51,7 @@ public interface IValidationSupport
* Canonical Uri of the ValueSet * Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet) * @return The valueset (must not be null, but can be an empty ValueSet)
*/ */
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri); ValueSet fetchValueSet(FhirContext theContext, String uri);
/** /**
@ -95,10 +96,13 @@ public interface IValidationSupport
* The code, e.g. "<code>1234-5</code>" * The code, e.g. "<code>1234-5</code>"
* @param theDisplay * @param theDisplay
* The display name, if it should also be validated * 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 * @return Returns a validation result object
*/ */
@Override @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> { class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, OperationOutcome.IssueSeverity> {

View File

@ -28,9 +28,9 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
* Constructor * Constructor
*/ */
public PrePopulatedValidationSupport() { public PrePopulatedValidationSupport() {
myStructureDefinitions = new HashMap<String, StructureDefinition>(); myStructureDefinitions = new HashMap<>();
myValueSets = new HashMap<String, ValueSet>(); myValueSets = new HashMap<>();
myCodeSystems = new HashMap<String, CodeSystem>(); myCodeSystems = new HashMap<>();
} }
/** /**
@ -134,7 +134,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

@ -134,13 +134,13 @@ public class ValidationSupportChain implements IValidationSupport {
} }
@Override @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) { for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) { if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
return next.validateCode(theCtx, theCodeSystem, theCode, theDisplay); 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 @Override

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.dstu3.hapi.ctx; package org.hl7.fhir.dstu3.hapi.ctx;
import ca.uhn.fhir.context.FhirContext; 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.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; 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.ConceptReferenceComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; 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.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -280,18 +283,45 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
@Override @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) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); if (isNotBlank(theValueSetUrl)) {
if (cs != null) { HapiWorkerContext workerContext = new HapiWorkerContext(theContext, this);
boolean caseSensitive = true; ValueSetExpander expander = new ValueSetExpanderSimple(workerContext, workerContext);
if (cs.hasCaseSensitive()) { try {
caseSensitive = cs.getCaseSensitive(); 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) { if (theCodeSystem != null) {
return retVal; 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 @Override
public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { 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 @Override

View File

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

View File

@ -45,6 +45,7 @@ public interface IValidationSupport
* @param uri Canonical Uri of the ValueSet * @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet) * @return The valueset (must not be null, but can be an empty ValueSet)
*/ */
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri); ValueSet fetchValueSet(FhirContext theContext, String uri);
@Override @Override
@ -69,10 +70,13 @@ public interface IValidationSupport
* @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>" * @param theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>" * @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated * @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 * @return Returns a validation result object
*/ */
@Override @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. * 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.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import org.xml.sax.SAXException;
import java.io.IOException;
public class XmlUtilDstu3Test { public class XmlUtilDstu3Test {
@ -72,11 +75,11 @@ public class XmlUtilDstu3Test {
} }
@Test @Test
public void testApplyUnsupportedFeature() { public void testApplyUnsupportedFeature() throws IOException, SAXException {
assertNotNull(XmlUtil.newDocumentBuilderFactory()); assertNotNull(XmlUtil.parseDocument("<document></document>"));
XmlUtil.setThrowExceptionForUnitTest(new ParserConfigurationException("AA")); XmlUtil.setThrowExceptionForUnitTest(new ParserConfigurationException("AA"));
assertNotNull(XmlUtil.newDocumentBuilderFactory()); assertNotNull(XmlUtil.parseDocument("<document></document>"));
} }
@AfterClass @AfterClass

View File

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

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r4.hapi.ctx;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants; 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.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.ConceptSetComponent;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r4.terminologies.ValueSetExpander; import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import org.hl7.fhir.r4.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.io.IOException; import java.io.IOException;
@ -294,18 +296,44 @@ public class DefaultProfileValidationSupport implements IValidationSupport {
} }
@Override @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) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); if (isNotBlank(theValueSetUrl)) {
if (cs != null) { ValueSetExpander expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, this));
boolean caseSensitive = true; try {
if (cs.hasCaseSensitive()) { ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl);
caseSensitive = cs.getCaseSensitive(); 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) { if (theCodeSystem != null) {
return retVal; 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 @Override
public LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { 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 @Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) { 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) { if (result == null) {
return 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 * 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(); ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) { definition.setCode(theSystem);
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) { definition.setDisplay(theCode);
expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay()); return new ValidationResult(definition);
}
}
expandedValueSet = new ValueSetExpansionOutcome(expansion);
} }
/* /*
* The following valueset is a special case, since the mime types codesystem is very difficult to expand * 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())) { if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
ValueSet expansion = new ValueSet(); ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
expansion.getExpansion().addContains().setCode(theCode).setSystem(theSystem).setDisplay(theDisplay); definition.setCode(theSystem);
expandedValueSet = new ValueSetExpansionOutcome(expansion); 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); expandedValueSet = expand(theVs, null);
} }
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) { if (expandedValueSet != null) {
String nextCode = next.getCode(); for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
if (!caseSensitive) { String nextCode = next.getCode();
nextCode = nextCode.toUpperCase(); if (!caseSensitive) {
} nextCode = nextCode.toUpperCase();
}
if (nextCode.equals(wantCode)) { if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) { if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode()); definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay()); definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition); ValidationResult retVal = new ValidationResult(definition);
return retVal; return retVal;
}
} }
} }
} }
@ -330,12 +338,12 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
} }
@Override @Override
public void setLogger(ILoggingService theLogger) { public ILoggingService getLogger() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public ILoggingService getLogger() { public void setLogger(ILoggingService theLogger) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -349,6 +357,11 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override @Override
public boolean isNoTerminologyServer() { public boolean isNoTerminologyServer() {
return false; return false;
@ -384,11 +397,6 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName); return fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
} }
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override @Override
public String getLinkForUrl(String corePath, String url) { public String getLinkForUrl(String corePath, String url) {
throw new UnsupportedOperationException(); 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.FhirContext;
import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.context.support.IContextValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource; 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;
import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.StructureDefinition;
@ -46,6 +47,7 @@ public interface IValidationSupport
* @param uri Canonical Uri of the ValueSet * @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet) * @return The valueset (must not be null, but can be an empty ValueSet)
*/ */
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri); 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 theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>" * @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated * @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 * @return Returns a validation result object
*/ */
@Override @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> { class CodeValidationResult extends IContextValidationSupport.CodeValidationResult<ConceptDefinitionComponent, IssueSeverity> {

View File

@ -1,20 +1,21 @@
package org.hl7.fhir.r4.test.support; package org.hl7.fhir.r4.test.support;
import ca.uhn.fhir.util.XmlUtil; import ca.uhn.fhir.util.XmlUtil;
import com.google.common.base.Charsets;
import com.google.gson.*; import com.google.gson.*;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.utilities.CSFile; import org.hl7.fhir.utilities.CSFile;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap; import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder; import java.io.File;
import javax.xml.parsers.DocumentBuilderFactory; import java.io.FileInputStream;
import java.io.*; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -172,30 +173,8 @@ public class TestingUtilities {
} }
private static Document loadXml(InputStream fn) throws Exception { private static Document loadXml(InputStream fn) throws Exception {
DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory(); String input = IOUtils.toString(fn, Charsets.UTF_8);
DocumentBuilder builder = factory.newDocumentBuilder(); return XmlUtil.parseDocument(input);
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);
} }
private static String compareObjects(String path, JsonObject o1, JsonObject o2) { private static String compareObjects(String path, JsonObject o1, JsonObject o2) {
@ -250,9 +229,9 @@ public class TestingUtilities {
JsonArray a2 = (JsonArray) n2; JsonArray a2 = (JsonArray) n2;
if (a1.size() != a2.size()) 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++) { 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)) if (!Utilities.noString(s))
return 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.FhirContext;
import ca.uhn.fhir.context.support.IContextValidationSupport; import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.rest.api.Constants; 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.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.r5.terminologies.ValueSetExpander; import org.hl7.fhir.r5.terminologies.ValueSetExpander;
import org.hl7.fhir.r5.terminologies.ValueSetExpanderSimple;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import java.io.IOException; import java.io.IOException;
@ -26,296 +28,322 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class DefaultProfileValidationSupport implements IValidationSupport { public class DefaultProfileValidationSupport implements IValidationSupport {
private static final String URL_PREFIX_VALUE_SET = "http://hl7.org/fhir/ValueSet/"; 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 = "http://hl7.org/fhir/StructureDefinition/";
private static final String URL_PREFIX_STRUCTURE_DEFINITION_BASE = "http://hl7.org/fhir/"; 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, CodeSystem> myCodeSystems;
private Map<String, StructureDefinition> myStructureDefinitions; private Map<String, StructureDefinition> myStructureDefinitions;
private Map<String, ValueSet> myValueSets; private Map<String, ValueSet> myValueSets;
private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set<String> theWantCodes, List<ConceptDefinitionComponent> theConcepts) { private void addConcepts(ConceptSetComponent theInclude, ValueSetExpansionComponent theRetVal, Set<String> theWantCodes, List<ConceptDefinitionComponent> theConcepts) {
for (ConceptDefinitionComponent next : theConcepts) { for (ConceptDefinitionComponent next : theConcepts) {
if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) { if (theWantCodes.isEmpty() || theWantCodes.contains(next.getCode())) {
theRetVal theRetVal
.addContains() .addContains()
.setSystem(theInclude.getSystem()) .setSystem(theInclude.getSystem())
.setCode(next.getCode()) .setCode(next.getCode())
.setDisplay(next.getDisplay()); .setDisplay(next.getDisplay());
} }
addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept()); addConcepts(theInclude, theRetVal, theWantCodes, next.getConcept());
} }
} }
@Override @Override
public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { public ValueSetExpander.ValueSetExpansionOutcome expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet()); ValueSetExpander.ValueSetExpansionOutcome retVal = new ValueSetExpander.ValueSetExpansionOutcome(new ValueSet());
Set<String> wantCodes = new HashSet<>(); Set<String> wantCodes = new HashSet<>();
for (ConceptReferenceComponent next : theInclude.getConcept()) { for (ConceptReferenceComponent next : theInclude.getConcept()) {
wantCodes.add(next.getCode()); wantCodes.add(next.getCode());
} }
CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem()); CodeSystem system = fetchCodeSystem(theContext, theInclude.getSystem());
if (system != null) { if (system != null) {
List<ConceptDefinitionComponent> concepts = system.getConcept(); List<ConceptDefinitionComponent> concepts = system.getConcept();
addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts); addConcepts(theInclude, retVal.getValueset().getExpansion(), wantCodes, concepts);
} }
for (UriType next : theInclude.getValueSet()) { for (UriType next : theInclude.getValueSet()) {
ValueSet vs = myValueSets.get(defaultString(next.getValueAsString())); ValueSet vs = myValueSets.get(defaultString(next.getValueAsString()));
if (vs != null) { if (vs != null) {
for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) { for (ConceptSetComponent nextInclude : vs.getCompose().getInclude()) {
ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude); ValueSetExpander.ValueSetExpansionOutcome contents = expandValueSet(theContext, nextInclude);
retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains()); retVal.getValueset().getExpansion().getContains().addAll(contents.getValueset().getExpansion().getContains());
} }
} }
} }
return retVal; return retVal;
} }
@Override @Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) { public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
ArrayList<IBaseResource> retVal = new ArrayList<>(); ArrayList<IBaseResource> retVal = new ArrayList<>();
retVal.addAll(myCodeSystems.values()); retVal.addAll(myCodeSystems.values());
retVal.addAll(myStructureDefinitions.values()); retVal.addAll(myStructureDefinitions.values());
retVal.addAll(myValueSets.values()); retVal.addAll(myValueSets.values());
return retVal; return retVal;
} }
@Override @Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) { public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return new ArrayList<>(provideStructureDefinitionMap(theContext).values()); return new ArrayList<>(provideStructureDefinitionMap(theContext).values());
} }
@Override @Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true); return (CodeSystem) fetchCodeSystemOrValueSet(theContext, theSystem, true);
} }
private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) { private DomainResource fetchCodeSystemOrValueSet(FhirContext theContext, String theSystem, boolean codeSystem) {
synchronized (this) { synchronized (this) {
Map<String, CodeSystem> codeSystems = myCodeSystems; Map<String, CodeSystem> codeSystems = myCodeSystems;
Map<String, ValueSet> valueSets = myValueSets; Map<String, ValueSet> valueSets = myValueSets;
if (codeSystems == null || valueSets == null) { if (codeSystems == null || valueSets == null) {
codeSystems = new HashMap<>(); codeSystems = new HashMap<>();
valueSets = 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/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/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/v3-codesystems.xml");
myCodeSystems = codeSystems; myCodeSystems = codeSystems;
myValueSets = valueSets; myValueSets = valueSets;
} }
// System can take the form "http://url|version" // System can take the form "http://url|version"
String system = theSystem; String system = theSystem;
if (system.contains("|")) { if (system.contains("|")) {
String version = system.substring(system.indexOf('|') + 1); String version = system.substring(system.indexOf('|') + 1);
if (version.matches("^[0-9.]+$")) { if (version.matches("^[0-9.]+$")) {
system = system.substring(0, system.indexOf('|')); system = system.substring(0, system.indexOf('|'));
} }
} }
if (codeSystem) { if (codeSystem) {
return codeSystems.get(system); return codeSystems.get(system);
} else { } else {
return valueSets.get(system); return valueSets.get(system);
} }
} }
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) { public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
Validate.notBlank(theUri, "theUri must not be null or blank"); Validate.notBlank(theUri, "theUri must not be null or blank");
if (theClass.equals(StructureDefinition.class)) { if (theClass.equals(StructureDefinition.class)) {
return (T) fetchStructureDefinition(theContext, theUri); return (T) fetchStructureDefinition(theContext, theUri);
} }
if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) { if (theClass.equals(ValueSet.class) || theUri.startsWith(URL_PREFIX_VALUE_SET)) {
return (T) fetchValueSet(theContext, theUri); return (T) fetchValueSet(theContext, theUri);
} }
return null; return null;
} }
@Override @Override
public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) { public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUrl) {
String url = theUrl; String url = theUrl;
if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) { if (url.startsWith(URL_PREFIX_STRUCTURE_DEFINITION)) {
// no change // no change
} else if (url.indexOf('/') == -1) { } else if (url.indexOf('/') == -1) {
url = URL_PREFIX_STRUCTURE_DEFINITION + url; url = URL_PREFIX_STRUCTURE_DEFINITION + url;
} else if (StringUtils.countMatches(url, '/') == 1) { } else if (StringUtils.countMatches(url, '/') == 1) {
url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url; url = URL_PREFIX_STRUCTURE_DEFINITION_BASE + url;
} }
return provideStructureDefinitionMap(theContext).get(url); return provideStructureDefinitionMap(theContext).get(url);
} }
@Override @Override
public ValueSet fetchValueSet(FhirContext theContext, String uri) { public ValueSet fetchValueSet(FhirContext theContext, String uri) {
return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false);
} }
public void flush() { public void flush() {
myCodeSystems = null; myCodeSystems = null;
myStructureDefinitions = null; myStructureDefinitions = null;
} }
@Override @Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) { public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
CodeSystem cs = fetchCodeSystem(theContext, theSystem); CodeSystem cs = fetchCodeSystem(theContext, theSystem);
return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT; return cs != null && cs.getContent() != CodeSystemContentMode.NOTPRESENT;
} }
@Override @Override
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) { public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theWebUrl, String theProfileName) {
return null; return null;
} }
private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) { private void loadCodeSystems(FhirContext theContext, Map<String, CodeSystem> theCodeSystems, Map<String, ValueSet> theValueSets, String theClasspath) {
ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath); ourLog.info("Loading CodeSystem/ValueSet from classpath: {}", theClasspath);
InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); InputStream inputStream = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
InputStreamReader reader = null; InputStreamReader reader = null;
if (inputStream != null) { if (inputStream != null) {
try { try {
reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8); reader = new InputStreamReader(inputStream, Constants.CHARSET_UTF8);
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) { for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof CodeSystem) { if (next.getResource() instanceof CodeSystem) {
CodeSystem nextValueSet = (CodeSystem) next.getResource(); CodeSystem nextValueSet = (CodeSystem) next.getResource();
nextValueSet.getText().setDivAsString(""); nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl(); String system = nextValueSet.getUrl();
if (isNotBlank(system)) { if (isNotBlank(system)) {
theCodeSystems.put(system, nextValueSet); theCodeSystems.put(system, nextValueSet);
} }
} else if (next.getResource() instanceof ValueSet) { } else if (next.getResource() instanceof ValueSet) {
ValueSet nextValueSet = (ValueSet) next.getResource(); ValueSet nextValueSet = (ValueSet) next.getResource();
nextValueSet.getText().setDivAsString(""); nextValueSet.getText().setDivAsString("");
String system = nextValueSet.getUrl(); String system = nextValueSet.getUrl();
if (isNotBlank(system)) { if (isNotBlank(system)) {
theValueSets.put(system, nextValueSet); theValueSets.put(system, nextValueSet);
} }
} }
} }
} finally { } finally {
try { try {
if (reader != null) { if (reader != null) {
reader.close(); reader.close();
} }
inputStream.close(); inputStream.close();
} catch (IOException e) { } catch (IOException e) {
ourLog.warn("Failure closing stream", e); ourLog.warn("Failure closing stream", e);
} }
} }
} else { } else {
ourLog.warn("Unable to load resource: {}", theClasspath); ourLog.warn("Unable to load resource: {}", theClasspath);
} }
} }
private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) { private void loadStructureDefinitions(FhirContext theContext, Map<String, StructureDefinition> theCodeSystems, String theClasspath) {
ourLog.info("Loading structure definitions from classpath: {}", theClasspath); ourLog.info("Loading structure definitions from classpath: {}", theClasspath);
InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath); InputStream valuesetText = DefaultProfileValidationSupport.class.getResourceAsStream(theClasspath);
if (valuesetText != null) { if (valuesetText != null) {
InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8); InputStreamReader reader = new InputStreamReader(valuesetText, Constants.CHARSET_UTF8);
Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader); Bundle bundle = theContext.newXmlParser().parseResource(Bundle.class, reader);
for (BundleEntryComponent next : bundle.getEntry()) { for (BundleEntryComponent next : bundle.getEntry()) {
if (next.getResource() instanceof StructureDefinition) { if (next.getResource() instanceof StructureDefinition) {
StructureDefinition nextSd = (StructureDefinition) next.getResource(); StructureDefinition nextSd = (StructureDefinition) next.getResource();
nextSd.getText().setDivAsString(""); nextSd.getText().setDivAsString("");
String system = nextSd.getUrl(); String system = nextSd.getUrl();
if (isNotBlank(system)) { if (isNotBlank(system)) {
theCodeSystems.put(system, nextSd); theCodeSystems.put(system, nextSd);
} }
} }
} }
} else { } else {
ourLog.warn("Unable to load resource: {}", theClasspath); ourLog.warn("Unable to load resource: {}", theClasspath);
} }
} }
private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) { private Map<String, StructureDefinition> provideStructureDefinitionMap(FhirContext theContext) {
Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions; Map<String, StructureDefinition> structureDefinitions = myStructureDefinitions;
if (structureDefinitions == null) { if (structureDefinitions == null) {
structureDefinitions = new HashMap<>(); 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-resources.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/profile/profiles-types.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/profile/profiles-others.xml");
loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/extension/extension-definitions.xml"); loadStructureDefinitions(theContext, structureDefinitions, "/org/hl7/fhir/r5/model/extension/extension-definitions.xml");
myStructureDefinitions = structureDefinitions; myStructureDefinitions = structureDefinitions;
} }
return structureDefinitions; return structureDefinitions;
} }
private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) { private CodeValidationResult testIfConceptIsInList(CodeSystem theCodeSystem, String theCode, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive) {
String code = theCode; String code = theCode;
if (theCaseSensitive == false) { if (theCaseSensitive == false) {
code = code.toUpperCase(); 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) { private CodeValidationResult testIfConceptIsInListInner(CodeSystem theCodeSystem, List<ConceptDefinitionComponent> conceptList, boolean theCaseSensitive, String code) {
CodeValidationResult retVal = null; CodeValidationResult retVal = null;
for (ConceptDefinitionComponent next : conceptList) { for (ConceptDefinitionComponent next : conceptList) {
String nextCandidate = next.getCode(); String nextCandidate = next.getCode();
if (theCaseSensitive == false) { if (theCaseSensitive == false) {
nextCandidate = nextCandidate.toUpperCase(); nextCandidate = nextCandidate.toUpperCase();
} }
if (nextCandidate.equals(code)) { if (nextCandidate.equals(code)) {
retVal = new CodeValidationResult(next); retVal = new CodeValidationResult(next);
break; break;
} }
// recurse // recurse
retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive); retVal = testIfConceptIsInList(theCodeSystem, code, next.getConcept(), theCaseSensitive);
if (retVal != null) { if (retVal != null) {
break; break;
} }
} }
if (retVal != null) { if (retVal != null) {
retVal.setCodeSystemName(theCodeSystem.getName()); retVal.setCodeSystemName(theCodeSystem.getName());
retVal.setCodeSystemVersion(theCodeSystem.getVersion()); retVal.setCodeSystemVersion(theCodeSystem.getVersion());
} }
return retVal; return retVal;
} }
@Override @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) {
CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem); if (isNotBlank(theValueSetUrl)) {
if (cs != null) { ValueSetExpander expander = new ValueSetExpanderSimple(new HapiWorkerContext(theContext, this));
boolean caseSensitive = true; try {
if (cs.hasCaseSensitive()) { ValueSet valueSet = fetchValueSet(theContext, theValueSetUrl);
caseSensitive = cs.getCaseSensitive(); 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) { if (theCodeSystem != null) {
return retVal; 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 if (retVal != null) {
public IContextValidationSupport.LookupCodeResult lookupCode(FhirContext theContext, String theSystem, String theCode) { return retVal;
return validateCode(theContext, theSystem, theCode, null).asLookupCodeResult(theSystem, theCode); }
} }
}
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 @Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String theSystem, String theCode, String theDisplay) { 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) { if (result == null) {
return 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 * 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(); ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
for (ConceptSetComponent nextInclude : theVs.getCompose().getInclude()) { definition.setCode(theSystem);
for (ConceptReferenceComponent nextConcept : nextInclude.getConcept()) { definition.setDisplay(theCode);
expansion.getExpansion().addContains().setCode(nextConcept.getCode()).setDisplay(nextConcept.getDisplay()); return new ValidationResult(definition);
}
}
expandedValueSet = new ValueSetExpansionOutcome(expansion);
} }
/* /*
* The following valueset is a special case, since the mime types codesystem is very difficult to expand * 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())) { if (theVs != null && "http://hl7.org/fhir/ValueSet/mimetypes".equals(theVs.getUrl())) {
ValueSet expansion = new ValueSet(); ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
expansion.getExpansion().addContains().setCode(theCode).setSystem(theSystem).setDisplay(theDisplay); definition.setCode(theSystem);
expandedValueSet = new ValueSetExpansionOutcome(expansion); 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); expandedValueSet = expand(theVs, null);
} }
for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) { if (expandedValueSet != null) {
String nextCode = next.getCode(); for (ValueSetExpansionContainsComponent next : expandedValueSet.getValueset().getExpansion().getContains()) {
if (!caseSensitive) { String nextCode = next.getCode();
nextCode = nextCode.toUpperCase(); if (!caseSensitive) {
} nextCode = nextCode.toUpperCase();
}
if (nextCode.equals(wantCode)) { if (nextCode.equals(wantCode)) {
if (theSystem == null || next.getSystem().equals(theSystem)) { if (theSystem == null || next.getSystem().equals(theSystem)) {
ConceptDefinitionComponent definition = new ConceptDefinitionComponent(); ConceptDefinitionComponent definition = new ConceptDefinitionComponent();
definition.setCode(next.getCode()); definition.setCode(next.getCode());
definition.setDisplay(next.getDisplay()); definition.setDisplay(next.getDisplay());
ValidationResult retVal = new ValidationResult(definition); ValidationResult retVal = new ValidationResult(definition);
return retVal; return retVal;
}
} }
} }
} }
@ -269,6 +277,7 @@ public final class HapiWorkerContext implements IWorkerContext, ValueSetExpander
return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]"); return new ValidationResult(IssueSeverity.ERROR, "Unknown code[" + theCode + "] in system[" + theSystem + "]");
} }
@Override @Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String code, ValueSet vs) { public ValidationResult validateCode(TerminologyServiceOptions theOptions, String code, ValueSet vs) {
return validateCode(theOptions, null, code, null, vs); return validateCode(theOptions, null, code, null, vs);

View File

@ -46,6 +46,7 @@ public interface IValidationSupport
* @param uri Canonical Uri of the ValueSet * @param uri Canonical Uri of the ValueSet
* @return The valueset (must not be null, but can be an empty ValueSet) * @return The valueset (must not be null, but can be an empty ValueSet)
*/ */
@Override
ValueSet fetchValueSet(FhirContext theContext, String uri); 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 theCodeSystem The code system, e.g. "<code>http://loinc.org</code>"
* @param theCode The code, e.g. "<code>1234-5</code>" * @param theCode The code, e.g. "<code>1234-5</code>"
* @param theDisplay The display name, if it should also be validated * @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 * @return Returns a validation result object
*/ */
@Override @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> { 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 { public class ValidatorWrapper {
private static final Logger ourLog = LoggerFactory.getLogger(ValidatorWrapper.class); private static final Logger ourLog = LoggerFactory.getLogger(ValidatorWrapper.class);
private final DocumentBuilderFactory myDocBuilderFactory;
private IResourceValidator.BestPracticeWarningLevel myBestPracticeWarningLevel; private IResourceValidator.BestPracticeWarningLevel myBestPracticeWarningLevel;
private boolean myAnyExtensionsAllowed; private boolean myAnyExtensionsAllowed;
private boolean myErrorForUnknownProfiles; private boolean myErrorForUnknownProfiles;
@ -39,7 +38,7 @@ public class ValidatorWrapper {
* Constructor * Constructor
*/ */
public ValidatorWrapper() { public ValidatorWrapper() {
myDocBuilderFactory = XmlUtil.newDocumentBuilderFactory(); super();
} }
public ValidatorWrapper setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel theBestPracticeWarningLevel) { public ValidatorWrapper setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel theBestPracticeWarningLevel) {
@ -95,9 +94,7 @@ public class ValidatorWrapper {
if (encoding == EncodingEnum.XML) { if (encoding == EncodingEnum.XML) {
Document document; Document document;
try { try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder(); document = XmlUtil.parseDocument(input);
InputSource src = new InputSource(new StringReader(input));
document = builder.parse(src);
} catch (Exception e2) { } catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2); ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage(); ValidationMessage m = new ValidationMessage();

View File

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

View File

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

View File

@ -181,7 +181,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

@ -91,7 +91,7 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

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

View File

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

View File

@ -91,7 +91,7 @@ public class ValidationSupportChain implements IValidationSupport {
@Override @Override
public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) { public CodeValidationResult validateCode(FhirContext theCtx, String theCodeSystem, String theCode, String theDisplay) {
for (IValidationSupport next : myChain) { for (IValidationSupport next : myChain) {
if (next.isCodeSystemSupported(theCtx, theCodeSystem)) { if (theCodeSystem != null && next.isCodeSystemSupported(theCtx, theCodeSystem)) {
return next.validateCode(theCtx, theCodeSystem, theCode, theDisplay); 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.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class CachingValidationSupport implements IValidationSupport { public class CachingValidationSupport implements IValidationSupport {
@ -81,9 +83,9 @@ public class CachingValidationSupport implements IValidationSupport {
} }
@Override @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) {
String key = "validateCode " + theCodeSystem + " " + theCode; String key = "validateCode " + theCodeSystem + " " + theCode + " " + defaultIfBlank(theValueSetUrl, "NO_VS");
return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay)); return loadFromCache(key, t -> myWrap.validateCode(theContext, theCodeSystem, theCode, theDisplay, theValueSetUrl));
} }
@Override @Override

View File

@ -188,7 +188,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

@ -92,7 +92,7 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

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

View File

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

View File

@ -188,7 +188,7 @@ public class PrePopulatedValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

@ -92,7 +92,7 @@ public class SnapshotGeneratingValidationSupport implements IValidationSupport {
} }
@Override @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; return null;
} }

View File

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

View File

@ -53,6 +53,7 @@ import org.mockito.stubbing.Answer;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
@ -158,12 +159,14 @@ public class FhirInstanceValidatorDstu3Test {
return retVal; 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 @Override
public CodeValidationResult answer(InvocationOnMock theInvocation) { public CodeValidationResult answer(InvocationOnMock theInvocation) {
FhirContext ctx = (FhirContext) theInvocation.getArguments()[0]; FhirContext ctx = theInvocation.getArgument(0, FhirContext.class);
String system = (String) theInvocation.getArguments()[1]; String system = theInvocation.getArgument(1, String.class);
String code = (String) theInvocation.getArguments()[2]; String code = theInvocation.getArgument(2, String.class);
String display = theInvocation.getArgument(3, String.class);
String valueSetUrl = theInvocation.getArgument(4, String.class);
CodeValidationResult retVal; CodeValidationResult retVal;
if (myValidConcepts.contains(system + "___" + code)) { if (myValidConcepts.contains(system + "___" + code)) {
retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(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(); Optional<ConceptDefinitionComponent> found = cs.getConcept().stream().filter(t -> t.getCode().equals(code)).findFirst();
retVal = found.map(t->new CodeValidationResult(t)).orElse(null); retVal = found.map(t->new CodeValidationResult(t)).orElse(null);
} else { } 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; return retVal;
} }
}); });
@ -241,7 +244,7 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0; int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) { for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {}:{} {} - {}", 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++; index++;
retVal.add(next); retVal.add(next);
@ -255,7 +258,7 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0; int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) { 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++; index++;
if (next.getSeverity() != ResultSeverityEnum.INFORMATION) { if (next.getSeverity() != ResultSeverityEnum.INFORMATION) {
@ -536,7 +539,7 @@ public class FhirInstanceValidatorDstu3Test {
String name = "profiles-resources"; String name = "profiles-resources";
ourLog.info("Uploading " + name); ourLog.info("Uploading " + name);
String vsContents; 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>(); TreeSet<String> ids = new TreeSet<String>();
@ -566,7 +569,7 @@ public class FhirInstanceValidatorDstu3Test {
@Test @Test
public void testValidateBundleWithNoType() throws Exception { 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); ValidationResult output = myVal.validateWithResult(vsContents);
logResultsAndReturnNonInformationalOnes(output); logResultsAndReturnNonInformationalOnes(output);
@ -579,7 +582,7 @@ public class FhirInstanceValidatorDstu3Test {
String name = "profiles-resources"; String name = "profiles-resources";
ourLog.info("Uploading " + name); ourLog.info("Uploading " + name);
String inputString; 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); Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, inputString);
FHIRPathEngine fp = new FHIRPathEngine(new HapiWorkerContext(ourCtx, myDefaultValidationSupport)); FHIRPathEngine fp = new FHIRPathEngine(new HapiWorkerContext(ourCtx, myDefaultValidationSupport));
@ -631,7 +634,7 @@ public class FhirInstanceValidatorDstu3Test {
String name = "profiles-resources"; String name = "profiles-resources";
ourLog.info("Uploading " + name); ourLog.info("Uploading " + name);
String vsContents; 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); ValidationResult output = myVal.validateWithResult(vsContents);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
@ -639,7 +642,7 @@ public class FhirInstanceValidatorDstu3Test {
@Test @Test
public void testValidateDocument() throws Exception { 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); ValidationResult output = myVal.validateWithResult(vsContents);
logResultsAndReturnNonInformationalOnes(output); logResultsAndReturnNonInformationalOnes(output);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -139,19 +139,21 @@ public class FhirInstanceValidatorR5Test {
return retVal; 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 @Override
public CodeValidationResult answer(InvocationOnMock theInvocation) throws Throwable { public CodeValidationResult answer(InvocationOnMock theInvocation) {
FhirContext ctx = (FhirContext) theInvocation.getArguments()[0]; FhirContext ctx = theInvocation.getArgument(0, FhirContext.class);
String system = (String) theInvocation.getArguments()[1]; String system = theInvocation.getArgument(1, String.class);
String code = (String) theInvocation.getArguments()[2]; String code = theInvocation.getArgument(2, String.class);
String display = theInvocation.getArgument(3, String.class);
String valueSetUrl = theInvocation.getArgument(4, String.class);
CodeValidationResult retVal; CodeValidationResult retVal;
if (myValidConcepts.contains(system + "___" + code)) { if (myValidConcepts.contains(system + "___" + code)) {
retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code))); retVal = new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(code)));
} else { } 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; return retVal;
} }
}); });

View File

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

View File

@ -51,12 +51,31 @@
to Elastic in the future. to Elastic in the future.
]]> ]]>
</action> </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"> <action type="add" issue="1489">
<![CDATA[ <![CDATA[
<b>Improvement</b>: <b>Performance Improvement</b>:
A significant performance improvement was made to the parsers (particularly the Json Parser) 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 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! 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>
<action type="add" issue="1357"> <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 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. corrected to account for names up to 40 characters long.
</action> </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"> <action type="fix">
The subscription triggering operation was not able to handle commas within search URLs being 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. 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 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. result in an HTTP 400 error with a meaningful error message.
</action> </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"> <action type="add" issue="1541">
The server CapabilityStatement (/metadata) endpoint now respects the Cache-Control header. Thanks The server CapabilityStatement (/metadata) endpoint now respects the Cache-Control header. Thanks
to Jens Villadsen for the pull request! to Jens Villadsen for the pull request!