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:
parent
e3a5aaf298
commit
0a64294467
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: add
|
||||
issue: 3131
|
||||
title: "Provided a Remote Terminology Service implementation for the $lookup Operation."
|
|
@ -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."
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
release-date: "2021-11-18"
|
||||
codename: "Raccoon"
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -172,6 +172,4 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
|
|||
|
||||
return requestValidator;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -135,10 +135,6 @@ public class TestJpaDstu3Config extends BaseJavaConfigDstu3 {
|
|||
return requestValidator;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IBinaryStorageSvc binaryStorage() {
|
||||
return new MemoryBinaryStorageSvcImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultProfileValidationSupport validationSupportChainDstu3() {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue