Release 5.6.0 (#3174)

* 3138 externalized binary packages (#3139)

* Add test and impl

* Add changelog

* Fix test

* Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3138-support-externalized-binaries.yaml

* add beans to test configs

* Typo

* Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/IBinaryStorageSvc.java

Co-authored-by: michaelabuckley <michaelabuckley@gmail.com>

* Update hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/binstore/NullBinaryStorageSvcImpl.java

Co-authored-by: michaelabuckley <michaelabuckley@gmail.com>

Co-authored-by: Kevin Dougan SmileCDR <72025369+KevinDougan-SmileCDR@users.noreply.github.com>
Co-authored-by: michaelabuckley <michaelabuckley@gmail.com>

* 3131 - Added support for the lookup operation in the Remote Terminology code (#3134)

* Remove leading underscores from identifiers (#3146)

* Version bump

* License files

* version.yaml

Co-authored-by: Kevin Dougan SmileCDR <72025369+KevinDougan-SmileCDR@users.noreply.github.com>
Co-authored-by: michaelabuckley <michaelabuckley@gmail.com>
This commit is contained in:
Tadgh 2021-11-19 10:24:44 -05:00 committed by GitHub
parent e3a5aaf298
commit 0a64294467
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 785 additions and 38 deletions

View File

@ -0,0 +1,4 @@
---
type: add
issue: 3131
title: "Provided a Remote Terminology Service implementation for the $lookup Operation."

View File

@ -0,0 +1,6 @@
---
type: fix
jira: SMILE-3152
issue: 3138
title: "Previously, the package registry would not work correctly when externalized binary storage was enabled. This has been corrected."

View File

@ -0,0 +1,3 @@
---
release-date: "2021-11-18"
codename: "Raccoon"

View File

@ -346,7 +346,7 @@ Sort specifications can be passed into handler methods by adding a parameter of
Example URL to invoke this method:
```url
http://fhir.example.com/Patient?_identifier=urn:foo|123&_sort=given
http://fhir.example.com/Patient?identifier=urn:foo|123&_sort=given
```
<a name="limiting-results"/>
@ -364,7 +364,7 @@ of resources fetched from the database.
Example URL to invoke this method:
```url
http://fhir.example.com/Patient?_identifier=urn:foo|123&_count=10
http://fhir.example.com/Patient?identifier=urn:foo|123&_count=10
```
# Paging
@ -388,17 +388,17 @@ for more information.
Example URL to invoke this method for the first page:
```url
http://fhir.example.com/Patient?_identifier=urn:foo|123&_count=10&_offset=0
http://fhir.example.com/Patient?identifier=urn:foo|123&_count=10&_offset=0
```
or just
```url
http://fhir.example.com/Patient?_identifier=urn:foo|123&_count=10
http://fhir.example.com/Patient?identifier=urn:foo|123&_count=10
```
Example URL to invoke this method for the second page:
```url
http://fhir.example.com/Patient?_identifier=urn:foo|123&_count=10&_offset=10
http://fhir.example.com/Patient?identifier=urn:foo|123&_count=10&_offset=10
```
Note that if the paging provider is configured to be database backed, `_offset=0` behaves differently than no `_offset`. This

View File

@ -20,20 +20,31 @@ package ca.uhn.fhir.jpa.binstore;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.PayloadTooLargeException;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.HapiExtensions;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashingInputStream;
import com.google.common.io.ByteStreams;
import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.Optional;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
private final SecureRandom myRandom;
@ -41,6 +52,8 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
private final int ID_LENGTH = 100;
private int myMaximumBinarySize = Integer.MAX_VALUE;
private int myMinimumBinarySize;
@Autowired
private FhirContext myFhirContext;
BaseBinaryStorageSvcImpl() {
myRandom = new SecureRandom();
@ -104,7 +117,6 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
};
}
String provideIdForNewBlob(String theBlobIdOrNull) {
String id = theBlobIdOrNull;
if (isBlank(theBlobIdOrNull)) {
@ -112,4 +124,32 @@ abstract class BaseBinaryStorageSvcImpl implements IBinaryStorageSvc {
}
return id;
}
@Override
public byte[] fetchDataBlobFromBinary(IBaseBinary theBaseBinary) throws IOException {
IPrimitiveType<byte[]> dataElement = BinaryUtil.getOrCreateData(myFhirContext, theBaseBinary);
byte[] value = dataElement.getValue();
if (value == null) {
Optional<String> attachmentId = getAttachmentId((IBaseHasExtensions) dataElement);
if (attachmentId.isPresent()) {
value = fetchBlob(theBaseBinary.getIdElement(), attachmentId.get());
} else {
throw new InternalErrorException("Unable to load binary blob data for " + theBaseBinary.getIdElement());
}
}
return value;
}
@SuppressWarnings("unchecked")
private Optional<String> getAttachmentId(IBaseHasExtensions theBaseBinary) {
return theBaseBinary
.getExtension()
.stream()
.filter(t -> HapiExtensions.EXT_EXTERNALIZED_BINARY_ID.equals(t.getUrl()))
.filter(t -> t.getValue() instanceof IPrimitiveType)
.map(t -> (IPrimitiveType<String>) t.getValue())
.map(t -> t.getValue())
.filter(t -> isNotBlank(t))
.findFirst();
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.binstore;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IIdType;
import javax.annotation.Nonnull;
@ -101,4 +103,13 @@ public interface IBinaryStorageSvc {
* @return The payload as a byte array
*/
byte[] fetchBlob(IIdType theResourceId, String theBlobId) throws IOException;
/**
* Fetch the byte[] contents of a given Binary resource's `data` element. If the data is a standard base64encoded string that is embedded, return it.
* Otherwise, attempt to load the externalized binary blob via the the externalized binary storage service.
*
* @param theResourceId The resource ID The ID of the Binary resource you want to extract data bytes from
* @return The binary data blob as a byte array
*/
byte[] fetchDataBlobFromBinary(IBaseBinary theResource) throws IOException;
}

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.binstore;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IIdType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -81,4 +83,9 @@ public class NullBinaryStorageSvcImpl implements IBinaryStorageSvc {
public byte[] fetchBlob(IIdType theResourceId, String theBlobId) {
throw new UnsupportedOperationException();
}
@Override
public byte[] fetchDataBlobFromBinary(IBaseBinary theResource) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@ -27,6 +27,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.api.model.ExpungeOptions;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.dao.data.INpmPackageDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionDao;
import ca.uhn.fhir.jpa.dao.data.INpmPackageVersionResourceDao;
@ -123,6 +124,9 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired(required = false)//It is possible that some implementers will not create such a bean.
private IBinaryStorageSvc myBinaryStorageSvc;
@Override
@Transactional
public NpmPackage loadPackageFromCacheOnly(String theId, @Nullable String theVersion) {
@ -172,13 +176,37 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
private IHapiPackageCacheManager.PackageContents loadPackageContents(NpmPackageVersionEntity thePackageVersion) {
IFhirResourceDao<? extends IBaseBinary> binaryDao = getBinaryDao();
IBaseBinary binary = binaryDao.readByPid(new ResourcePersistentId(thePackageVersion.getPackageBinary().getId()));
try {
byte[] content = fetchBlobFromBinary(binary);
PackageContents retVal = new PackageContents()
.setBytes(content)
.setPackageId(thePackageVersion.getPackageId())
.setVersion(thePackageVersion.getVersionId())
.setLastModified(thePackageVersion.getUpdatedTime());
return retVal;
} catch (IOException e) {
throw new InternalErrorException("Failed to load package. There was a problem reading binaries", e);
}
}
PackageContents retVal = new PackageContents()
.setBytes(binary.getContent())
.setPackageId(thePackageVersion.getPackageId())
.setVersion(thePackageVersion.getVersionId())
.setLastModified(thePackageVersion.getUpdatedTime());
return retVal;
/**
* Helper method which will attempt to use the IBinaryStorageSvc to resolve the binary blob if available. If
* the bean is unavailable, fallback to assuming we are using an embedded base64 in the data element.
* @param theBinary the Binary who's `data` blob you want to retrieve
* @return a byte array containing the blob.
*
* @throws IOException
*/
private byte[] fetchBlobFromBinary(IBaseBinary theBinary) throws IOException {
if (myBinaryStorageSvc != null) {
return myBinaryStorageSvc.fetchDataBlobFromBinary(theBinary);
} else {
byte[] value = BinaryUtil.getOrCreateData(myCtx, theBinary).getValue();
if (value == null) {
throw new InternalErrorException("Failed to fetch blob from Binary/" + theBinary.getIdElement());
}
return value;
}
}
@SuppressWarnings("unchecked")
@ -487,14 +515,12 @@ public class JpaPackageCache extends BasePackageCacheManager implements IHapiPac
private IBaseResource loadPackageEntity(NpmPackageVersionResourceEntity contents) {
try {
ResourcePersistentId binaryPid = new ResourcePersistentId(contents.getResourceBinary().getId());
IBaseBinary binary = getBinaryDao().readByPid(binaryPid);
byte[] resourceContentsBytes = BinaryUtil.getOrCreateData(myCtx, binary).getValue();
String resourceContents = new String(resourceContentsBytes, StandardCharsets.UTF_8);
FhirContext packageContext = getFhirContext(contents.getFhirVersion());
return EncodingEnum.detectEncoding(resourceContents).newParser(packageContext).parseResource(resourceContents);
ResourcePersistentId binaryPid = new ResourcePersistentId(contents.getResourceBinary().getId());
IBaseBinary binary = getBinaryDao().readByPid(binaryPid);
byte[] resourceContentsBytes= fetchBlobFromBinary(binary);
String resourceContents = new String(resourceContentsBytes, StandardCharsets.UTF_8);
FhirContext packageContext = getFhirContext(contents.getFhirVersion());
return EncodingEnum.detectEncoding(resourceContents).newParser(packageContext).parseResource(resourceContents);
} catch (Exception e) {
throw new RuntimeException("Failed to load package resource " + contents, e);
}

View File

@ -172,6 +172,4 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
return requestValidator;
}
}

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
@ -70,4 +72,10 @@ public class TestJPAConfig {
public BatchJobHelper batchJobHelper(JobExplorer theJobExplorer) {
return new BatchJobHelper(theJobExplorer);
}
@Bean
@Lazy
public IBinaryStorageSvc binaryStorage() {
return new MemoryBinaryStorageSvcImpl();
}
}

View File

@ -270,6 +270,78 @@ public class NpmR4Test extends BaseJpaR4Test {
});
}
@Test
public void testInstallR4PackageWithExternalizedBinaries() throws Exception {
myDaoConfig.setAllowExternalReferences(true);
myInterceptorService.registerInterceptor(myBinaryStorageInterceptor);
byte[] bytes = loadClasspathBytes("/packages/hl7.fhir.uv.shorthand-0.12.0.tgz");
myFakeNpmServlet.myResponses.put("/hl7.fhir.uv.shorthand/0.12.0", bytes);
PackageInstallationSpec spec = new PackageInstallationSpec().setName("hl7.fhir.uv.shorthand").setVersion("0.12.0").setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL);
PackageInstallOutcomeJson outcome = myPackageInstallerSvc.install(spec);
assertEquals(1, outcome.getResourcesInstalled().get("CodeSystem"));
// Be sure no further communication with the server
JettyUtil.closeServer(myServer);
// Make sure we can fetch the package by ID and Version
NpmPackage pkg = myPackageCacheManager.loadPackage("hl7.fhir.uv.shorthand", "0.12.0");
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", pkg.description());
// Make sure we can fetch the package by ID
pkg = myPackageCacheManager.loadPackage("hl7.fhir.uv.shorthand", null);
assertEquals("0.12.0", pkg.version());
assertEquals("Describes FHIR Shorthand (FSH), a domain-specific language (DSL) for defining the content of FHIR Implementation Guides (IG). (built Wed, Apr 1, 2020 17:24+0000+00:00)", pkg.description());
// Make sure DB rows were saved
runInTransaction(() -> {
NpmPackageEntity pkgEntity = myPackageDao.findByPackageId("hl7.fhir.uv.shorthand").orElseThrow(() -> new IllegalArgumentException());
assertEquals("hl7.fhir.uv.shorthand", pkgEntity.getPackageId());
NpmPackageVersionEntity versionEntity = myPackageVersionDao.findByPackageIdAndVersion("hl7.fhir.uv.shorthand", "0.12.0").orElseThrow(() -> new IllegalArgumentException());
assertEquals("hl7.fhir.uv.shorthand", versionEntity.getPackageId());
assertEquals("0.12.0", versionEntity.getVersionId());
assertEquals(3001, versionEntity.getPackageSizeBytes());
assertEquals(true, versionEntity.isCurrentVersion());
assertEquals("hl7.fhir.uv.shorthand", versionEntity.getPackageId());
assertEquals("4.0.1", versionEntity.getFhirVersionId());
assertEquals(FhirVersionEnum.R4, versionEntity.getFhirVersion());
NpmPackageVersionResourceEntity resource = myPackageVersionResourceDao.findCurrentVersionByCanonicalUrl(Pageable.unpaged(), FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand").getContent().get(0);
assertEquals("http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand", resource.getCanonicalUrl());
assertEquals("0.12.0", resource.getCanonicalVersion());
assertEquals("ImplementationGuide-hl7.fhir.uv.shorthand.json", resource.getFilename());
assertEquals("4.0.1", resource.getFhirVersionId());
assertEquals(FhirVersionEnum.R4, resource.getFhirVersion());
assertEquals(6155, resource.getResSizeBytes());
});
// Fetch resource by URL
runInTransaction(() -> {
IBaseResource asset = myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand");
assertThat(myFhirCtx.newJsonParser().encodeResourceToString(asset), containsString("\"url\":\"http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand\",\"version\":\"0.12.0\""));
});
// Fetch resource by URL with version
runInTransaction(() -> {
IBaseResource asset = myPackageCacheManager.loadPackageAssetByUrl(FhirVersionEnum.R4, "http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand|0.12.0");
assertThat(myFhirCtx.newJsonParser().encodeResourceToString(asset), containsString("\"url\":\"http://hl7.org/fhir/uv/shorthand/ImplementationGuide/hl7.fhir.uv.shorthand\",\"version\":\"0.12.0\""));
});
// Search for the installed resource
runInTransaction(() -> {
SearchParameterMap map = SearchParameterMap.newSynchronous();
map.add(StructureDefinition.SP_URL, new UriParam("http://hl7.org/fhir/uv/shorthand/CodeSystem/shorthand-code-system"));
IBundleProvider result = myCodeSystemDao.search(map);
assertEquals(1, result.sizeOrThrowNpe());
IBaseResource resource = result.getResources(0, 1).get(0);
assertEquals("CodeSystem/shorthand-code-system/_history/1", resource.getIdElement().toString());
});
myInterceptorService.unregisterInterceptor(myBinaryStorageInterceptor);
}
@Test
public void testNumericIdsInstalledWithNpmPrefix() throws Exception {
myDaoConfig.setAllowExternalReferences(true);

View File

@ -30,6 +30,7 @@ public class BinaryStorageEntity {
@Id
@Column(name = "BLOB_ID", length = 200, nullable = false)
//N.B GGG: Note that the `blob id` is the same as the `externalized binary id`.
private String myBlobId;
@Column(name = "RESOURCE_ID", length = 100, nullable = false)
private String myResourceId;

View File

@ -21,10 +21,13 @@ package ca.uhn.fhir.jpa.config;
*/
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.JpaTransactionManager;
@ -43,6 +46,12 @@ public class TestJpaConfig {
return daoConfig().getModelConfig();
}
@Bean
@Lazy
public IBinaryStorageSvc binaryStorage() {
return new MemoryBinaryStorageSvcImpl();
}
@Bean
@Primary
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {

View File

@ -135,10 +135,6 @@ public class TestJpaDstu3Config extends BaseJavaConfigDstu3 {
return requestValidator;
}
@Bean
public IBinaryStorageSvc binaryStorage() {
return new MemoryBinaryStorageSvcImpl();
}
@Bean
public DefaultProfileValidationSupport validationSupportChainDstu3() {

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
@ -9,8 +10,8 @@ import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.JsonUtil;
import ca.uhn.fhir.util.ParametersUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.checkerframework.framework.qual.InvisibleQualifier;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -82,7 +83,7 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
*/
private String extractCodeSystemForCode(ValueSet theValueSet, String theCode) {
if (theValueSet.getCompose() == null || theValueSet.getCompose().getInclude() == null
|| theValueSet.getCompose().getInclude().isEmpty()) {
|| theValueSet.getCompose().getInclude().isEmpty()) {
return null;
}
@ -111,11 +112,9 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
} catch (IOException theE) {
ourLog.error("IOException trying to serialize ValueSet to json: " + theE);
}
return null;
}
private String getVersionedCodeSystem(ValueSet.ConceptSetComponent theComponent) {
String codeSystem = theComponent.getSystem();
if ( ! codeSystem.contains("|") && theComponent.hasVersion()) {
@ -124,7 +123,6 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return codeSystem;
}
@Override
public IBaseResource fetchCodeSystem(String theSystem) {
IGenericClient client = provideClient();
@ -143,6 +141,167 @@ public class RemoteTerminologyServiceValidationSupport extends BaseValidationSup
return null;
}
@Override
public LookupCodeResult lookupCode(ValidationSupportContext theValidationSupportContext, String theSystem, String theCode, String theDisplayLanguage) {
Validate.notBlank(theCode, "theCode must be provided");
IGenericClient client = provideClient();
FhirContext fhirContext = client.getFhirContext();
FhirVersionEnum fhirVersion = fhirContext.getVersion().getVersion();
switch (fhirVersion) {
case DSTU3:
case R4:
IBaseParameters params = ParametersUtil.newInstance(fhirContext);
ParametersUtil.addParameterToParametersString(fhirContext, params, "code", theCode);
if (!StringUtils.isEmpty(theSystem)) {
ParametersUtil.addParameterToParametersString(fhirContext, params, "system", theSystem);
}
if (!StringUtils.isEmpty(theDisplayLanguage)) {
ParametersUtil.addParameterToParametersString(fhirContext, params, "language", theDisplayLanguage);
}
Class<?> codeSystemClass = myCtx.getResourceDefinition("CodeSystem").getImplementingClass();
IBaseParameters outcome = client
.operation()
.onType((Class<? extends IBaseResource>) codeSystemClass)
.named("$lookup")
.withParameters(params)
.useHttpGet()
.execute();
if (outcome != null && !outcome.isEmpty()) {
switch (fhirVersion) {
case DSTU3:
return generateLookupCodeResultDSTU3(theCode, theSystem, (org.hl7.fhir.dstu3.model.Parameters)outcome);
case R4:
return generateLookupCodeResultR4(theCode, theSystem, (org.hl7.fhir.r4.model.Parameters)outcome);
}
}
break;
default:
throw new UnsupportedOperationException("Unsupported FHIR version '" + fhirVersion.getFhirVersionString() +
"'. Only DSTU3 and R4 are supported.");
}
return null;
}
private LookupCodeResult generateLookupCodeResultDSTU3(String theCode, String theSystem, org.hl7.fhir.dstu3.model.Parameters outcomeDSTU3) {
// NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding
// several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in POM).
LookupCodeResult result = new LookupCodeResult();
result.setSearchedForCode(theCode);
result.setSearchedForSystem(theSystem);
result.setFound(true);
for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent parameterComponent : outcomeDSTU3.getParameter()) {
switch (parameterComponent.getName()) {
case "property":
org.hl7.fhir.dstu3.model.Property part = parameterComponent.getChildByName("part");
// The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be saved
if (part != null && part.hasValues() && part.getValues().size() >= 2) {
String key = ((org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) part.getValues().get(0)).getValue().toString();
String value = ((org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent) part.getValues().get(1)).getValue().toString();
if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(value)) {
result.getProperties().add(new StringConceptProperty(key, value));
}
}
break;
case "designation":
ConceptDesignation conceptDesignation = new ConceptDesignation();
for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent designationComponent : parameterComponent.getPart()) {
switch(designationComponent.getName()) {
case "language":
conceptDesignation.setLanguage(designationComponent.getValue().toString());
break;
case "use":
org.hl7.fhir.dstu3.model.Coding coding = (org.hl7.fhir.dstu3.model.Coding)designationComponent.getValue();
if (coding != null) {
conceptDesignation.setUseSystem(coding.getSystem());
conceptDesignation.setUseCode(coding.getCode());
conceptDesignation.setUseDisplay(coding.getDisplay());
}
break;
case "value":
conceptDesignation.setValue(((designationComponent.getValue() == null)?null:designationComponent.getValue().toString()));
break;
}
}
result.getDesignations().add(conceptDesignation);
break;
case "name":
result.setCodeSystemDisplayName(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
break;
case "version":
result.setCodeSystemVersion(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
break;
case "display":
result.setCodeDisplay(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
break;
case "abstract":
result.setCodeIsAbstract(((parameterComponent.getValue() == null)?false:Boolean.parseBoolean(parameterComponent.getValue().toString())));
break;
}
}
return result;
}
private LookupCodeResult generateLookupCodeResultR4(String theCode, String theSystem, org.hl7.fhir.r4.model.Parameters outcomeR4) {
// NOTE: I wanted to put all of this logic into the IValidationSupport Class, but it would've required adding
// several new dependencies on version-specific libraries and that is explicitly forbidden (see comment in POM).
LookupCodeResult result = new LookupCodeResult();
result.setSearchedForCode(theCode);
result.setSearchedForSystem(theSystem);
result.setFound(true);
for (org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent parameterComponent : outcomeR4.getParameter()) {
switch (parameterComponent.getName()) {
case "property":
org.hl7.fhir.r4.model.Property part = parameterComponent.getChildByName("part");
// The assumption here is that we may only have 2 elements in this part, and if so, these 2 will be saved
if (part != null && part.hasValues() && part.getValues().size() >= 2) {
String key = ((org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent) part.getValues().get(0)).getValue().toString();
String value = ((org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent) part.getValues().get(1)).getValue().toString();
if (!StringUtils.isEmpty(key) && !StringUtils.isEmpty(value)) {
result.getProperties().add(new StringConceptProperty(key, value));
}
}
break;
case "designation":
ConceptDesignation conceptDesignation = new ConceptDesignation();
for (org.hl7.fhir.r4.model.Parameters.ParametersParameterComponent designationComponent : parameterComponent.getPart()) {
switch(designationComponent.getName()) {
case "language":
conceptDesignation.setLanguage(designationComponent.getValue().toString());
break;
case "use":
org.hl7.fhir.r4.model.Coding coding = (org.hl7.fhir.r4.model.Coding)designationComponent.getValue();
if (coding != null) {
conceptDesignation.setUseSystem(coding.getSystem());
conceptDesignation.setUseCode(coding.getCode());
conceptDesignation.setUseDisplay(coding.getDisplay());
}
break;
case "value":
conceptDesignation.setValue(((designationComponent.getValue() == null)?null:designationComponent.getValue().toString()));
break;
}
}
result.getDesignations().add(conceptDesignation);
break;
case "name":
result.setCodeSystemDisplayName(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
break;
case "version":
result.setCodeSystemVersion(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
break;
case "display":
result.setCodeDisplay(((parameterComponent.getValue() == null)?null:parameterComponent.getValue().toString()));
break;
case "abstract":
result.setCodeIsAbstract(((parameterComponent.getValue() == null)?false:Boolean.parseBoolean(parameterComponent.getValue().toString())));
break;
}
}
return result;
}
@Override
public IBaseResource fetchValueSet(String theValueSetUrl) {
IGenericClient client = provideClient();

View File

@ -3,12 +3,14 @@ package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.parser.IJsonLikeParser;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IHttpRequest;
import ca.uhn.fhir.rest.client.api.IHttpResponse;
@ -22,12 +24,14 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.UriType;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@ -44,14 +48,18 @@ import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RemoteTerminologyServiceValidationSupportTest {
private static final String DISPLAY = "DISPLAY";
private static final String LANGUAGE = "en";
private static final String CODE_SYSTEM = "CODE_SYS";
private static final String CODE_SYSTEM_VERSION = "2.1";
private static final String CODE_SYSTEM_VERSION_AS_TEXT = "v2.1.12";
private static final String CODE = "CODE";
private static final String VALUE_SET_URL = "http://value.set/url";
private static final String ERROR_MESSAGE = "This is an error message";
private static FhirContext ourCtx = FhirContext.forR4();
@RegisterExtension
@ -88,7 +96,50 @@ public class RemoteTerminologyServiceValidationSupportTest {
}
@Test
public void testValidateCode_SystemCodeDisplayUrl_Success() {
public void testLookupOperation_CodeSystem_Success() {
createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT,
DISPLAY, null);
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE);
assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!");
assertEquals(DISPLAY, outcome.getCodeDisplay());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, myCodeSystemProvider.myNextReturnParams.getParameter("name").toString());
assertTrue(Boolean.parseBoolean(myCodeSystemProvider.myNextReturnParams.getParameter("result").primitiveValue()));
}
@Test
public void testLookupOperationWithAllParams_CodeSystem_Success() {
createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT,
DISPLAY, null);
addAdditionalReturnParameters();
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE);
assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!");
assertEquals(DISPLAY, outcome.getCodeDisplay());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, myCodeSystemProvider.myNextReturnParams.getParameter("name").toString());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
assertTrue(Boolean.parseBoolean(myCodeSystemProvider.myNextReturnParams.getParameter("result").primitiveValue()));
validateExtraCodeSystemParams();
}
@Test
public void testLookupCode_BlankCode_ThrowsException() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM,
"", null);
});
}
@Test
public void testValidateCode_ValueSet_Success() {
createNextValueSetReturnParameters(true, DISPLAY, null);
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, VALUE_SET_URL);
@ -104,6 +155,62 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(null, myValueSetProvider.myLastValueSet);
}
@Test
public void testValidateCodeWithAllParams_CodeSystem_Success() {
createNextCodeSystemReturnParameters(true, DISPLAY, null);
addAdditionalReturnParameters();
IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(null, null, CODE_SYSTEM, CODE, DISPLAY, null);
assertEquals(CODE, outcome.getCode());
assertEquals(DISPLAY, outcome.getDisplay());
assertEquals(null, outcome.getSeverity());
assertEquals(null, outcome.getMessage());
validateExtraCodeSystemParams();
}
private void validateExtraCodeSystemParams() {
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
for (Parameters.ParametersParameterComponent param : myCodeSystemProvider.myNextReturnParams.getParameter()) {
String paramName = param.getName();
if (paramName.equals("result")) {
assertEquals(true, ((BooleanType)param.getValue()).booleanValue());
} else if (paramName.equals("display")) {
assertEquals(DISPLAY, param.getValue().toString());
} else if (paramName.equals("property")) {
for (Parameters.ParametersParameterComponent propertyComponent : param.getPart()) {
switch(propertyComponent.getName()) {
case "name":
assertEquals("birthDate", propertyComponent.getValue().toString());
break;
case "value":
assertEquals("1930-01-01", propertyComponent.getValue().toString());
break;
}
}
} else if (paramName.equals("designation")) {
for (Parameters.ParametersParameterComponent designationComponent : param.getPart()) {
switch(designationComponent.getName()) {
case "language":
assertEquals(LANGUAGE, designationComponent.getValue().toString());
break;
case "use":
Coding coding = (Coding)designationComponent.getValue();
assertNotNull(coding, "Coding value returned via designation use should NOT be NULL!");
assertEquals("code", coding.getCode());
assertEquals("system", coding.getSystem());
assertEquals("display", coding.getDisplay());
break;
case "value":
assertEquals("some value", designationComponent.getValue().toString());
break;
}
}
}
}
}
@Test
public void testValidateCode_SystemCodeDisplayUrl_Error() {
createNextValueSetReturnParameters(false, null, ERROR_MESSAGE);
@ -132,7 +239,6 @@ public class RemoteTerminologyServiceValidationSupportTest {
assertEquals(null, outcome.getMessage());
assertEquals(CODE, myCodeSystemProvider.myLastCode.getCode());
assertEquals(DISPLAY, myCodeSystemProvider.myLastDisplay.getValue());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
}
@ -375,6 +481,18 @@ public class RemoteTerminologyServiceValidationSupportTest {
}
}
private void createNextCodeSystemLookupReturnParameters(boolean theResult, String theVersion, String theVersionAsText,
String theDisplay, String theMessage) {
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter("result", theResult);
myCodeSystemProvider.myNextReturnParams.addParameter("version", theVersion);
myCodeSystemProvider.myNextReturnParams.addParameter("name", theVersionAsText);
myCodeSystemProvider.myNextReturnParams.addParameter("display", theDisplay);
if (theMessage != null) {
myCodeSystemProvider.myNextReturnParams.addParameter("message", theMessage);
}
}
private void createNextValueSetReturnParameters(boolean theResult, String theDisplay, String theMessage) {
myValueSetProvider.myNextReturnParams = new Parameters();
myValueSetProvider.myNextReturnParams.addParameter("result", theResult);
@ -384,6 +502,23 @@ public class RemoteTerminologyServiceValidationSupportTest {
}
}
private void addAdditionalReturnParameters() {
// property
Parameters.ParametersParameterComponent param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("property");
param.addPart().setName("name").setValue(new StringType("birthDate"));
param.addPart().setName("value").setValue(new StringType("1930-01-01"));
// designation
param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("designation");
param.addPart().setName("language").setValue(new CodeType("en"));
Parameters.ParametersParameterComponent codingParam = param.addPart().setName("use");
Coding coding = new Coding();
coding.setCode("code");
coding.setSystem("system");
coding.setDisplay("display");
codingParam.setValue(coding);
param.addPart().setName("value").setValue(new StringType("some value"));
}
private static class MyCodeSystemProvider implements IResourceProvider {
private UriParam myLastUrlParam;
@ -391,8 +526,10 @@ public class RemoteTerminologyServiceValidationSupportTest {
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private StringType myLastDisplay;
private Coding myLastCoding;
private StringType myLastVersion;
private Parameters myNextReturnParams;
private IValidationSupport.LookupCodeResult myNextLookupCodeResult;
@Operation(name = "validate-code", idempotent = true, returnParameters = {
@OperationParam(name = "result", type = BooleanType.class, min = 1),
@ -409,11 +546,34 @@ public class RemoteTerminologyServiceValidationSupportTest {
myInvocationCount++;
myLastUrl = theCodeSystemUrl;
myLastCode = theCode;
myLastDisplay = theDisplay;
return myNextReturnParams;
}
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name="name", type=StringType.class, min=1),
@OperationParam(name="version", type=StringType.class, min=0),
@OperationParam(name="display", type=StringType.class, min=1),
@OperationParam(name="abstract", type=BooleanType.class, min=1),
})
public Parameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name="code", min=0, max=1) CodeType theCode,
@OperationParam(name="system", min=0, max=1) UriType theSystem,
@OperationParam(name="coding", min=0, max=1) Coding theCoding,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
@OperationParam(name="displayLanguage", min=0, max=1) CodeType theDisplayLanguage,
@OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties,
RequestDetails theRequestDetails
) {
myInvocationCount++;
myLastCode = theCode;
myLastUrl = theSystem;
myLastCoding = theCoding;
myLastVersion = theVersion;
return myNextReturnParams;
}
@Search
public List<CodeSystem> find(@RequiredParam(name = "url") UriParam theUrlParam) {
myLastUrlParam = theUrlParam;
@ -429,8 +589,6 @@ public class RemoteTerminologyServiceValidationSupportTest {
private static class MyValueSetProvider implements IResourceProvider {
private Parameters myNextReturnParams;
private List<ValueSet> myNextReturnValueSets;
private UriType myLastUrl;

View File

@ -0,0 +1,214 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.dstu3.model.BooleanType;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.DateType;
import org.hl7.fhir.dstu3.model.Parameters;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.UriType;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class RemoteTerminologyServiceValidationSupportDstu3Test {
private static final String DISPLAY = "DISPLAY";
private static final String LANGUAGE = "en";
private static final String CODE_SYSTEM = "CODE_SYS";
private static final String CODE_SYSTEM_VERSION = "2.1";
private static final String CODE_SYSTEM_VERSION_AS_TEXT = "v2.1.12";
private static final String CODE = "CODE";
private static FhirContext ourCtx = FhirContext.forDstu3();
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx);
private RemoteTerminologyServiceValidationSupport mySvc;
private MyCodeSystemProvider myCodeSystemProvider;
@BeforeEach
public void before() {
myCodeSystemProvider = new MyCodeSystemProvider();
myRestfulServerExtension.getRestfulServer().registerProvider(myCodeSystemProvider);
String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
mySvc.setBaseUrl(baseUrl);
mySvc.addClientInterceptor(new LoggingInterceptor(true));
}
@Test
public void testLookupOperation_CodeSystem_Success() {
createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT, DISPLAY);
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE);
assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!");
assertEquals(DISPLAY, outcome.getCodeDisplay());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, myCodeSystemProvider.myLastCode.asStringValue());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
for (Parameters.ParametersParameterComponent param : myCodeSystemProvider.myNextReturnParams.getParameter()) {
String paramName = param.getName();
if (paramName.equals("result")) {
assertEquals(true, ((BooleanType)param.getValue()).booleanValue());
} else if (paramName.equals("version")) {
assertEquals(CODE_SYSTEM_VERSION, param.getValue().toString());
} else if (paramName.equals("display")) {
assertEquals(DISPLAY, param.getValue().toString());
} else if (paramName.equals("name")) {
assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, param.getValue().toString());
}
}
}
@Test
public void testLookupOperationWithAllParams_CodeSystem_Success() {
createNextCodeSystemLookupReturnParameters(true, CODE_SYSTEM_VERSION, CODE_SYSTEM_VERSION_AS_TEXT, DISPLAY, LANGUAGE);
addAdditionalCodeSystemLookupReturnParameters();
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(null, CODE_SYSTEM, CODE, LANGUAGE);
assertNotNull(outcome, "Call to lookupCode() should return a non-NULL result!");
assertEquals(DISPLAY, outcome.getCodeDisplay());
assertEquals(CODE_SYSTEM_VERSION, outcome.getCodeSystemVersion());
assertEquals(CODE, myCodeSystemProvider.myLastCode.asStringValue());
assertEquals(CODE_SYSTEM, myCodeSystemProvider.myLastUrl.getValueAsString());
for (Parameters.ParametersParameterComponent param : myCodeSystemProvider.myNextReturnParams.getParameter()) {
String paramName = param.getName();
if (paramName.equals("result")) {
assertEquals(true, ((BooleanType)param.getValue()).booleanValue());
} else if (paramName.equals("version")) {
assertEquals(CODE_SYSTEM_VERSION, param.getValue().toString());
} else if (paramName.equals("display")) {
assertEquals(DISPLAY, param.getValue().toString());
} else if (paramName.equals("name")) {
assertEquals(CODE_SYSTEM_VERSION_AS_TEXT, param.getValue().toString());
} else if (paramName.equals("language")) {
assertEquals(LANGUAGE, param.getValue().toString());
} else if (paramName.equals("property")) {
for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent propertyComponent : param.getPart()) {
switch(propertyComponent.getName()) {
case "name":
assertEquals("birthDate", propertyComponent.getValue().toString());
break;
case "value":
assertEquals("1930-01-01", ((DateType)propertyComponent.getValue()).asStringValue());
break;
}
}
} else if (paramName.equals("designation")) {
for (org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent designationComponent : param.getPart()) {
switch(designationComponent.getName()) {
case "language":
assertEquals(LANGUAGE, designationComponent.getValue().toString());
break;
case "use":
Coding coding = (Coding)designationComponent.getValue();
assertNotNull(coding, "Coding value returned via designation use should NOT be NULL!");
assertEquals("code", coding.getCode());
assertEquals("system", coding.getSystem());
assertEquals("display", coding.getDisplay());
break;
case "value":
assertEquals("some value", designationComponent.getValue().toString());
break;
}
}
}
}
}
private void createNextCodeSystemLookupReturnParameters(boolean theResult, String theVersion, String theVersionAsText,
String theDisplay) {
createNextCodeSystemLookupReturnParameters(theResult, theVersion, theVersionAsText, theDisplay, null);
}
private void createNextCodeSystemLookupReturnParameters(boolean theResult, String theVersion, String theVersionAsText,
String theDisplay, String theLanguage) {
myCodeSystemProvider.myNextReturnParams = new Parameters();
myCodeSystemProvider.myNextReturnParams.addParameter().setName("result").setValue(new BooleanType(theResult));
myCodeSystemProvider.myNextReturnParams.addParameter().setName("version").setValue(new StringType(theVersion));
myCodeSystemProvider.myNextReturnParams.addParameter().setName("name").setValue(new StringType(theVersionAsText));
myCodeSystemProvider.myNextReturnParams.addParameter().setName("display").setValue(new StringType(theDisplay));
if (!StringUtils.isBlank(theLanguage)) {
myCodeSystemProvider.myNextReturnParams.addParameter().setName("language").setValue(new StringType(theLanguage));
}
}
private void addAdditionalCodeSystemLookupReturnParameters() {
// property
Parameters.ParametersParameterComponent param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("property");
param.addPart().setName("name").setValue(new StringType("birthDate"));
param.addPart().setName("value").setValue(new DateType("1930-01-01"));
// designation
param = myCodeSystemProvider.myNextReturnParams.addParameter().setName("designation");
param.addPart().setName("language").setValue(new CodeType("en"));
Parameters.ParametersParameterComponent codingParam = param.addPart().setName("use");
Coding coding = new Coding();
coding.setCode("code");
coding.setSystem("system");
coding.setDisplay("display");
codingParam.setValue(coding);
param.addPart().setName("value").setValue(new StringType("some value"));
}
private static class MyCodeSystemProvider implements IResourceProvider {
private int myInvocationCount;
private UriType myLastUrl;
private CodeType myLastCode;
private Coding myLastCoding;
private StringType myLastVersion;
private CodeType myLastDisplayLanguage;
private Parameters myNextReturnParams;
@Operation(name = JpaConstants.OPERATION_LOOKUP, idempotent = true, returnParameters= {
@OperationParam(name="name", type=StringType.class, min=1),
@OperationParam(name="version", type=StringType.class, min=0),
@OperationParam(name="display", type=StringType.class, min=1),
@OperationParam(name="abstract", type=BooleanType.class, min=1),
})
public Parameters lookup(
HttpServletRequest theServletRequest,
@OperationParam(name="code", min=0, max=1) CodeType theCode,
@OperationParam(name="system", min=0, max=1) UriType theSystem,
@OperationParam(name="coding", min=0, max=1) Coding theCoding,
@OperationParam(name="version", min=0, max=1) StringType theVersion,
@OperationParam(name="displayLanguage", min=0, max=1) CodeType theDisplayLanguage,
@OperationParam(name="property", min = 0, max = OperationParam.MAX_UNLIMITED) List<CodeType> theProperties,
RequestDetails theRequestDetails
) {
myInvocationCount++;
myLastCode = theCode;
myLastUrl = theSystem;
myLastCoding = theCoding;
myLastVersion = theVersion;
myLastDisplayLanguage = theDisplayLanguage;
return myNextReturnParams;
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return CodeSystem.class;
}
}
}

View File

@ -0,0 +1,35 @@
package org.hl7.fhir.r5.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
public class RemoteTerminologyServiceValidationSupportR5Test {
private static final String ANY_NONBLANK_VALUE = "anything";
private static FhirContext ourCtx = FhirContext.forR5();
@RegisterExtension
public RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx);
private RemoteTerminologyServiceValidationSupport mySvc;
@BeforeEach
public void before() {
String baseUrl = "http://localhost:" + myRestfulServerExtension.getPort();
mySvc = new RemoteTerminologyServiceValidationSupport(ourCtx);
mySvc.setBaseUrl(baseUrl);
}
@Test
public void testLookupCode_R5_ThrowsException() {
Assertions.assertThrows(UnsupportedOperationException.class, () -> {
IValidationSupport.LookupCodeResult outcome = mySvc.lookupCode(
new ValidationSupportContext(FhirContext.forR5().getValidationSupport()), ANY_NONBLANK_VALUE, ANY_NONBLANK_VALUE);
});
}
}