Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #2342 - add support for TerminologyCapabilities #2347

Merged
merged 4 commits into from
May 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ public <T extends Resource> T getResource(String url, Class<T> resourceType) {
/**
* Get the resources for the given resource type
*
* <p>Use this method to get actual FHIR resources and not FHIR registry resources (metadata)
*
* @param resourceType
* the resource type
* @return
Expand All @@ -184,6 +186,28 @@ public <T extends Resource> Collection<T> getResources(Class<T> resourceType) {
return Collections.unmodifiableList(resources);
}

/**
* Get the registry resources for the given resource type
*
* <p>Use this method to get FHIR registry resources (metadata) and not actual FHIR resources
*
* @param resourceType
* the resource type
* @return
* the registry resources for the given resource type
* @throws IllegalArgumentException
* if the resource type is not a definitional resource type
*/
public Collection<FHIRRegistryResource> getRegistryResources(Class<? extends Resource> resourceType) {
Objects.requireNonNull(resourceType);
requireDefinitionalResourceType(resourceType);
List<FHIRRegistryResource> registryResources = new ArrayList<>();
for (FHIRRegistryResourceProvider provider : providers) {
registryResources.addAll(provider.getRegistryResources(resourceType));
}
return registryResources;
}

/**
* Get the search parameters with the given search parameter type (e.g. string, token, etc.)
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -34,9 +35,11 @@
import java.util.stream.Collectors;

import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
Expand All @@ -54,10 +57,16 @@
import com.ibm.fhir.model.resource.CapabilityStatement.Rest;
import com.ibm.fhir.model.resource.CapabilityStatement.Rest.Resource.Interaction;
import com.ibm.fhir.model.resource.CapabilityStatement.Rest.Resource.Operation;
import com.ibm.fhir.model.resource.CodeSystem;
import com.ibm.fhir.model.resource.DomainResource;
import com.ibm.fhir.model.resource.OperationDefinition;
import com.ibm.fhir.model.resource.Resource;
import com.ibm.fhir.model.resource.SearchParameter;
import com.ibm.fhir.model.resource.TerminologyCapabilities;
import com.ibm.fhir.model.resource.TerminologyCapabilities.Closure;
import com.ibm.fhir.model.resource.TerminologyCapabilities.Expansion;
import com.ibm.fhir.model.resource.TerminologyCapabilities.Translation;
import com.ibm.fhir.model.resource.TerminologyCapabilities.ValidateCode;
import com.ibm.fhir.model.type.Canonical;
import com.ibm.fhir.model.type.Code;
import com.ibm.fhir.model.type.CodeableConcept;
Expand All @@ -67,6 +76,7 @@
import com.ibm.fhir.model.type.Markdown;
import com.ibm.fhir.model.type.Uri;
import com.ibm.fhir.model.type.code.CapabilityStatementKind;
import com.ibm.fhir.model.type.code.CodeSearchSupport;
import com.ibm.fhir.model.type.code.ConditionalDeleteStatus;
import com.ibm.fhir.model.type.code.ConditionalReadStatus;
import com.ibm.fhir.model.type.code.FHIRVersion;
Expand All @@ -79,6 +89,7 @@
import com.ibm.fhir.model.util.ModelSupport;
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;
import com.ibm.fhir.registry.FHIRRegistry;
import com.ibm.fhir.registry.resource.FHIRRegistryResource;
import com.ibm.fhir.search.util.SearchUtil;
import com.ibm.fhir.server.FHIRBuildIdentifier;
import com.ibm.fhir.server.operation.FHIROperationRegistry;
Expand All @@ -96,6 +107,7 @@ public class Capabilities extends FHIRResource {
// Constants
private static final String FHIR_SERVER_NAME = "IBM FHIR Server";
private static final String FHIR_COPYRIGHT = "(C) Copyright IBM Corporation 2016, 2021";
private static final String FHIR_PUBLISHER = "IBM Corporation";
private static final String EXTENSION_URL = "http://ibm.com/fhir/extension";
private static final String BASE_CAPABILITY_URL = "http://hl7.org/fhir/CapabilityStatement/base";
private static final String BASE_2_CAPABILITY_URL = "http://hl7.org/fhir/CapabilityStatement/base2";
Expand All @@ -108,7 +120,6 @@ public class Capabilities extends FHIRResource {
private static final String ERROR_MSG = "Caught exception while processing 'metadata' request.";
private static final String ERROR_CONSTRUCTING = "An error occurred while constructing the Conformance statement.";

private static final Object PRESENT = new Object();
private static final String CAPABILITY_STATEMENT_CACHE_NAME = "com.ibm.fhir.server.resources.Capabilities.statementCache";

// Constructor
Expand All @@ -118,25 +129,31 @@ public Capabilities() throws Exception {

@GET
@Path("metadata")
public Response capabilities() {
public Response capabilities(@QueryParam("mode") @DefaultValue("full") String mode) {
log.entering(this.getClass().getName(), "capabilities()");
try {
Date startTime = new Date();
checkInitComplete();

if (!isValidMode(mode)) {
throw new IllegalArgumentException("Invalid mode parameter: must be one of [full, normative, terminology]");
}

// Defaults to 60 minutes (or what's in the fhirConfig)
int cacheTimeout = FHIRConfigHelper.getIntProperty(PROPERTY_CAPABILITY_STATEMENT_CACHE, 60);
Configuration configuration = Configuration.of(Duration.of(cacheTimeout, ChronoUnit.MINUTES));

Map<Object, CapabilityStatement> cacheAsMap = CacheManager.getCacheAsMap(CAPABILITY_STATEMENT_CACHE_NAME, configuration);
CapabilityStatement capabilityStatement = cacheAsMap.computeIfAbsent(PRESENT, k -> computeCapabilityStatement());
Map<String, Resource> cacheAsMap = CacheManager.getCacheAsMap(CAPABILITY_STATEMENT_CACHE_NAME, configuration);
Resource capabilityStatement = cacheAsMap.computeIfAbsent(mode, k -> computeCapabilityStatement(mode));

RestAuditLogger.logMetadata(httpServletRequest, startTime, new Date(), Response.Status.OK);

CacheControl cacheControl = new CacheControl();
cacheControl.setPrivate(true);
cacheControl.setMaxAge(60 * cacheTimeout);
return Response.ok().entity(capabilityStatement).cacheControl(cacheControl).build();
} catch (IllegalArgumentException e) {
return exceptionResponse(e, Response.Status.BAD_REQUEST);
} catch (RuntimeException e) {
FHIROperationException foe = buildRestException(ERROR_CONSTRUCTING, IssueType.EXCEPTION);
if (e.getCause() != null) {
Expand All @@ -153,14 +170,90 @@ public Response capabilities() {
}
}

private CapabilityStatement computeCapabilityStatement() {
private boolean isValidMode(String mode) {
return "full".equals(mode) || "normative".equals(mode) || "terminology".equals(mode);
}

private Resource computeCapabilityStatement(String mode) {
try {
return buildCapabilityStatement();
switch (mode) {
case "terminology":
return buildTerminologyCapabilities();
case "full":
case "normative":
default:
return buildCapabilityStatement();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private TerminologyCapabilities buildTerminologyCapabilities() {
FHIRBuildIdentifier buildInfo = new FHIRBuildIdentifier();
String buildDescription = FHIR_SERVER_NAME + " version " + buildInfo.getBuildVersion() + " build id " + buildInfo.getBuildId() + "";
return TerminologyCapabilities.builder()
.status(PublicationStatus.ACTIVE)
.date(DateTime.now(ZoneOffset.UTC))
.kind(CapabilityStatementKind.INSTANCE)
.version(string(buildInfo.getBuildVersion()))
.name(string(FHIR_SERVER_NAME))
.description(Markdown.of(buildDescription))
.copyright(Markdown.of(FHIR_COPYRIGHT))
.publisher(string(FHIR_PUBLISHER))
.software(TerminologyCapabilities.Software.builder()
.name(string(FHIR_SERVER_NAME))
.version(string(buildInfo.getBuildVersion()))
.id(buildInfo.getBuildId())
.build())
.codeSystem(buildCodeSystem())
.expansion(Expansion.builder()
.hierarchical(com.ibm.fhir.model.type.Boolean.FALSE)
.paging(com.ibm.fhir.model.type.Boolean.FALSE)
.incomplete(com.ibm.fhir.model.type.Boolean.FALSE)
.textFilter(Markdown.of("Text searching is not supported"))
.build())
.codeSearch(CodeSearchSupport.ALL)
.validateCode(ValidateCode.builder()
.translations(com.ibm.fhir.model.type.Boolean.FALSE)
.build())
.translation(Translation.builder()
.needsMap(com.ibm.fhir.model.type.Boolean.TRUE)
.build())
.closure(Closure.builder()
.translation(com.ibm.fhir.model.type.Boolean.FALSE)
.build())
.build();
}

private List<TerminologyCapabilities.CodeSystem> buildCodeSystem() {
Map<String, List<TerminologyCapabilities.CodeSystem.Version>> versionMap = new LinkedHashMap<>();

for (FHIRRegistryResource registryResource : FHIRRegistry.getInstance().getRegistryResources(CodeSystem.class)) {
String url = registryResource.getUrl();
FHIRRegistryResource.Version version = registryResource.getVersion();

List<TerminologyCapabilities.CodeSystem.Version> versions = versionMap.computeIfAbsent(url, k -> new ArrayList<>());
if (!FHIRRegistryResource.NO_VERSION.equals(version)) {
versions.add(TerminologyCapabilities.CodeSystem.Version.builder()
.code(string(version.toString()))
.build());
}
}

List<TerminologyCapabilities.CodeSystem> codeSystems = new ArrayList<>(versionMap.keySet().size());

for (String url : versionMap.keySet()) {
List<TerminologyCapabilities.CodeSystem.Version> versions = versionMap.get(url);
codeSystems.add(TerminologyCapabilities.CodeSystem.builder()
.uri(Canonical.of(url))
.version(versions)
.build());
}

return codeSystems;
}

/**
* Builds a CapabilityStatement resource instance which describes this server.
*
Expand Down Expand Up @@ -390,7 +483,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception {
CapabilityStatement conformance = CapabilityStatement.builder()
.status(PublicationStatus.ACTIVE)
.date(DateTime.now(ZoneOffset.UTC))
.kind(CapabilityStatementKind.CAPABILITY)
.kind(CapabilityStatementKind.INSTANCE)
.fhirVersion(FHIRVersion.VERSION_4_0_1)
.format(format)
.patchFormat(Code.of(FHIRMediaType.APPLICATION_JSON_PATCH),
Expand All @@ -400,7 +493,7 @@ private CapabilityStatement buildCapabilityStatement() throws Exception {
.name(string(FHIR_SERVER_NAME))
.description(Markdown.of(buildDescription))
.copyright(Markdown.of(FHIR_COPYRIGHT))
.publisher(string("IBM Corporation"))
.publisher(string(FHIR_PUBLISHER))
.software(CapabilityStatement.Software.builder()
.name(string(FHIR_SERVER_NAME))
.version(string(buildInfo.getBuildVersion()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ void testBuildCapabilityStatement_resources_omitted() throws Exception {
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities();
Response capabilities = c.capabilities("full");
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
Expand All @@ -60,7 +60,7 @@ void testBuildCapabilityStatement_resources_empty() throws Exception {
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities();
Response capabilities = c.capabilities("full");
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
Expand All @@ -75,7 +75,7 @@ void testBuildCapabilityStatement_resources_filtered() throws Exception {
FHIRRequestContext.get().setOriginalRequestUri("http://example.com/metadata");
CapabilitiesChild c = new CapabilitiesChild();

Response capabilities = c.capabilities();
Response capabilities = c.capabilities("full");
CapabilityStatement capabilityStatement = capabilities.readEntity(CapabilityStatement.class);

assertEquals(capabilityStatement.getRest().size(), 1, "Number of REST Elements");
Expand Down Expand Up @@ -121,9 +121,9 @@ public CapabilitiesChild() throws Exception {
}

@Override
public Response capabilities() {
public Response capabilities(String mode) {
httpServletRequest = new MockHttpServletRequest();
return super.capabilities();
return super.capabilities(mode);
}
}
}