Skip to content

Commit

Permalink
issue #2143 - support custom compartment types via extension
Browse files Browse the repository at this point in the history
The CompartmentDefinition resource has a required binding to a fixed
list of compartment types. To support a custom compartments, we will now
look for the `http://ibm.com/fhir/extension/custom-compartment-type`
extension on this element (when it has no value).

Additionally, I created a constant in fhir-core FHIRConstants for our
base extension url and I updated all the different places we have
extension urls to use it.

Signed-off-by: Lee Surprenant <lmsurpre@us.ibm.com>
  • Loading branch information
lmsurpre authored and tbieste committed Jun 9, 2021
1 parent 6567376 commit 61b31b8
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Set;
import java.util.stream.Collectors;

import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.model.format.Format;
import com.ibm.fhir.model.generator.FHIRGenerator;
import com.ibm.fhir.model.resource.SearchParameter;
Expand All @@ -35,7 +36,7 @@
* to search parameters which always reference code values with a particular system
*/
public class SearchParameterAugmenter {
private static final String IMPLICIT_SYSTEM_EXT_URL = "http://ibm.com/fhir/extension/implicit-system";
private static final String IMPLICIT_SYSTEM_EXT_URL = FHIRConstants.EXT_BASE + "implicit-system";
private static final FHIRGenerator generator = FHIRGenerator.generator(Format.JSON, false);

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Set;
import java.util.stream.Collectors;

import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.model.format.Format;
import com.ibm.fhir.model.generator.FHIRGenerator;
import com.ibm.fhir.model.resource.SearchParameter;
Expand All @@ -35,7 +36,7 @@
* to search parameters which always reference code values with a particular system
*/
public class SearchParameterAugmenter {
private static final String IMPLICIT_SYSTEM_EXT_URL = "http://ibm.com/fhir/extension/implicit-system";
private static final String IMPLICIT_SYSTEM_EXT_URL = FHIRConstants.EXT_BASE + "implicit-system";
private static final FHIRGenerator generator = FHIRGenerator.generator(Format.JSON, false);

public static void main(String[] args) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2016,2019
* (C) Copyright IBM Corp. 2016, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -38,54 +38,54 @@
import com.ibm.fhir.provider.FHIRProvider;

public class JaxrsClientTestMain {

public static void main(String[] args) throws Exception {
Patient patient = buildPatient();
System.out.println("\nJSON:");

FHIRGenerator.generator( Format.JSON, false).generate(patient, System.out);
System.out.println("\nXML:");
FHIRGenerator.generator( Format.XML, false).generate(patient, System.out);

Client client = ClientBuilder.newBuilder()
.register(new FHIRProvider(RuntimeType.CLIENT))
.build();

WebTarget target = client.target("http://localhost:9080/fhir-server/api/v4");
Entity<Patient> entity = Entity.entity(patient, FHIRMediaType.APPLICATION_FHIR_XML);
Response response = target.path("Patient").request().post(entity, Response.class);

if (Response.Status.CREATED.getStatusCode() == response.getStatus()) {
System.out.println("");
System.out.println(response.getStatus());
System.out.println(response.getStatusInfo().getReasonPhrase());
String location = response.getLocation().toString();
System.out.println("location: " + location);

String id = location.substring(location.lastIndexOf("/") + 1);
response = target.path("Patient/" + id).request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
patient = response.readEntity(Patient.class);
System.out.println("\nJSON:");
FHIRGenerator.generator( Format.JSON, false).generate(patient, System.out);
System.out.println("\nXML:");
FHIRGenerator.generator( Format.XML, false).generate(patient, System.out);

Observation observation = buildObservation(id);
System.out.println("\nJSON:");
FHIRGenerator.generator( Format.JSON, false).generate(observation, System.out);
System.out.println("\nXML:");
FHIRGenerator.generator( Format.XML, false).generate(observation, System.out);

Entity<Observation> observationEntity = Entity.entity(observation, FHIRMediaType.APPLICATION_FHIR_JSON);
response = target.path("Observation").request().post(observationEntity, Response.class);

if (Response.Status.CREATED.getStatusCode() == response.getStatus()) {
System.out.println("");
System.out.println(response.getStatus());
System.out.println(response.getStatusInfo().getReasonPhrase());
location = response.getLocation().toString();
System.out.println("location: " + location);

response = target.path("Observation").queryParam("subject", "Patient/" + id).request(FHIRMediaType.APPLICATION_FHIR_JSON).get();
Bundle bundle = response.readEntity(Bundle.class);
System.out.println("\nJSON:");
Expand All @@ -105,7 +105,7 @@ public static void main(String[] args) throws Exception {
System.out.println(response.readEntity(String.class));
}
}

public static Patient buildPatient() {
Patient patient = Patient.builder().name(HumanName.builder()
.family(string("Doe"))
Expand All @@ -114,12 +114,12 @@ public static Patient buildPatient() {
.telecom(ContactPoint.builder().system(ContactPointSystem.PHONE)
.use(ContactPointUse.HOME).value(string("555-1234")).build())
.extension(Extension.builder().url("http://ibm.com/fhir/extension/Patient/favorite-color")
.value(string("blue")).build()).build();
.value(string("blue")).build()).build();
return patient;
}

public static Observation buildObservation(String patientId) {
Observation observation = Observation.builder().status(ObservationStatus.FINAL).bodySite(
Observation observation = Observation.builder().status(ObservationStatus.FINAL).bodySite(
CodeableConcept.builder().coding(Coding.builder().code(Code.of("55284-4"))
.system(Uri.of("http://loinc.org")).build())
.text(string("Blood pressure systolic & diastolic")).build())
Expand All @@ -135,8 +135,8 @@ public static Observation buildObservation(String patientId) {
.system(Uri.of("http://loinc.org")).build())
.text(string("Diastolic")).build())
.value(Quantity.builder().value(Decimal.of(93.7)).unit(string("mmHg")).build()).build())
.build();
.build();

return observation;
}
}
}
2 changes: 2 additions & 0 deletions fhir-core/src/main/java/com/ibm/fhir/core/FHIRConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ public class FHIRConstants {

public static final String UPDATE_IF_MODIFIED_HEADER = "X-FHIR-UPDATE-IF-MODIFIED";

public static final String EXT_BASE = "http://ibm.com/fhir/extension/";

/**
* General parameter names that can be used with any FHIR interaction.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1336,7 +1336,7 @@ private FHIRPersistenceNotSupportedException buildNotSupportedException(String m
.severity(IssueSeverity.FATAL)
.code(IssueType.NOT_SUPPORTED.toBuilder()
.extension(Extension.builder()
.url("http://ibm.com/fhir/extension/not-supported-detail")
.url(FHIRConstants.EXT_BASE + "not-supported-detail")
.value(Code.of("interaction"))
.build())
.build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.Map;
import java.util.Set;

import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.model.type.Code;
import com.ibm.fhir.model.type.Coding;
import com.ibm.fhir.model.type.Uri;
Expand Down Expand Up @@ -94,8 +95,7 @@ private SearchConstants() {
// _has
public static final String HAS = "_has";

public static final String BASE_SYSTEM_EXT_URL = "http://ibm.com/fhir/extension/";
public static final String IMPLICIT_SYSTEM_EXT_URL = BASE_SYSTEM_EXT_URL + "implicit-system";
public static final String IMPLICIT_SYSTEM_EXT_URL = FHIRConstants.EXT_BASE + "implicit-system";

// Extracted search parameter suffix for :identifier modifier
public static final String IDENTIFIER_MODIFIER_SUFFIX = ":identifier";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
import java.util.Objects;
import java.util.Set;

import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.model.resource.CompartmentDefinition;
import com.ibm.fhir.model.resource.CompartmentDefinition.Resource;
import com.ibm.fhir.model.type.code.CompartmentType;
import com.ibm.fhir.model.util.FHIRUtil;
import com.ibm.fhir.registry.FHIRRegistry;
import com.ibm.fhir.search.exception.FHIRSearchException;
import com.ibm.fhir.search.exception.SearchExceptionUtil;
Expand All @@ -36,6 +39,9 @@
* Call {@link #init()} to initialize static members and avoid a slight performance hit on first use.
*/
public class CompartmentUtil {
// The URL of the compartment subtype extension...useful for defining new compartments
public static final String CUSTOM_COMPARTMENT_TYPE_EXT = FHIRConstants.EXT_BASE + "custom-compartment-type";

// Map of Compartment name to CompartmentCache
private static final Map<String, CompartmentCache> compartmentMap = new HashMap<>();

Expand Down Expand Up @@ -76,7 +82,10 @@ public static final void buildMaps(Map<String, CompartmentCache> compMap, Map<St

Collection<CompartmentDefinition> definitions = FHIRRegistry.getInstance().getResources(CompartmentDefinition.class);
for (CompartmentDefinition compartmentDefinition : definitions) {
String compartmentName = compartmentDefinition.getCode().getValue();
CompartmentType type = compartmentDefinition.getCode();

String compartmentName = type.hasValue() ? type.getValue()
: FHIRUtil.getExtensionStringValue(type, CUSTOM_COMPARTMENT_TYPE_EXT);

// The cached object (a smaller/lighter lookup resource) used for point lookups
CompartmentCache compartmentDefinitionCache = new CompartmentCache();
Expand Down Expand Up @@ -131,7 +140,7 @@ public static List<String> getCompartmentResourceTypeInclusionCriteria(final Str
}

/**
* checks that the compartment is valid, and throws and exception if, not
* checks that the compartment is valid and throws an exception if not
*
* @param compartment
* @throws FHIRSearchException
Expand All @@ -144,7 +153,7 @@ public static void checkValidCompartment(final String compartment) throws FHIRSe
}

/**
* checks that the compartment and resource are valid, and throws and exception if, not
* checks that the compartment and resource are valid and throws an exception if not
*
* @param compartment
* @throws FHIRSearchException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

package com.ibm.fhir.search.reference.value;

import java.util.Objects;

/**
* Represents a reference to a resource compartment extracted by SearchUtil
Expand All @@ -25,6 +26,9 @@ public class CompartmentReference {
* @param referenceResourceValue
*/
public CompartmentReference(String parameterName, String referenceResourceType, String referenceResourceValue) {
Objects.requireNonNull(parameterName, "parameterName");
Objects.requireNonNull(referenceResourceType, "referenceResourceType");
Objects.requireNonNull(referenceResourceValue, "referenceResourceValue");
this.parameterName = parameterName;
this.referenceResourceType = referenceResourceType;
this.referenceResourceValue = referenceResourceValue;
Expand Down
49 changes: 39 additions & 10 deletions fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package com.ibm.fhir.search.util;

import static com.ibm.fhir.model.util.ModelSupport.FHIR_STRING;

import java.io.FileNotFoundException;
import java.math.BigDecimal;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -43,6 +45,7 @@
import com.ibm.fhir.model.resource.ValueSet;
import com.ibm.fhir.model.type.Canonical;
import com.ibm.fhir.model.type.Code;
import com.ibm.fhir.model.type.Element;
import com.ibm.fhir.model.type.Reference;
import com.ibm.fhir.model.type.Uri;
import com.ibm.fhir.model.type.code.IssueSeverity;
Expand Down Expand Up @@ -2587,24 +2590,50 @@ public static Map<String, Set<CompartmentReference>> extractCompartmentParameter
}

for (FHIRPathNode node : nodes) {
Reference reference = node.asElementNode().element().as(Reference.class);
ReferenceValue rv = ReferenceUtil.createReferenceValueFrom(reference, baseUrl);
if (rv.getType() != ReferenceType.DISPLAY_ONLY && rv.getType() != ReferenceType.INVALID) {
String compartmentName = null;
String compartmentId = null;

Element element = node.asElementNode().element();
if (element.is(Reference.class)) {
Reference reference = element.as(Reference.class);
ReferenceValue rv = ReferenceUtil.createReferenceValueFrom(reference, baseUrl);
if (rv.getType() == ReferenceType.DISPLAY_ONLY || rv.getType() == ReferenceType.INVALID) {
if (log.isLoggable(Level.FINE)) {
log.fine("Skipping reference of type " + rv.getType());
}
continue;
}
compartmentName = rv.getTargetResourceType();
compartmentId = rv.getValue();

// Check that the target resource type of the reference matches one of the
// target resource types in the compartment definition.
final String compartmentName = rv.getTargetResourceType();
if (paramEntry.getValue().contains(compartmentName)) {
// Add this reference to the set of references we're collecting for each compartment
CompartmentReference cref = new CompartmentReference(searchParm, compartmentName, rv.getValue());
Set<CompartmentReference> references = result.computeIfAbsent(compartmentName, k -> new HashSet<>());
references.add(cref);
if (!paramEntry.getValue().contains(compartmentName)) {
if (log.isLoggable(Level.FINE)) {
log.fine("Skipping reference with value " + reference.getReference() + ";"
+ " target resource type does not match any of the allowed compartment types: " + paramEntry);
}
continue;
}
} else if (element.is(FHIR_STRING)) {
if (paramEntry.getValue().size() != 1) {
log.warning("CompartmentDefinition inclusion criteria must be of type Reference unless they have 1 and only 1 resource target");
continue;
}
compartmentName = paramEntry.getValue().iterator().next();
compartmentId = element.as(FHIR_STRING).getValue();
}

// Add this reference to the set of references we're collecting for each compartment
CompartmentReference cref = new CompartmentReference(searchParm, compartmentName, compartmentId);
Set<CompartmentReference> references = result.computeIfAbsent(compartmentName, k -> new HashSet<>());
references.add(cref);
}
} else if (!useStoredCompartmentParam()) {
log.warning("Compartment parameter not found: [" + resourceType + "] '" + searchParm + "'. This will stop compartment searches from working correctly.");
log.warning("Compartment parameter not found: [" + resourceType + "] '" + searchParm + "'. "
+ "This will stop compartment searches from working correctly.");
}

}
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2017, 2020
* (C) Copyright IBM Corp. 2017, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -17,6 +17,7 @@

import org.testng.annotations.Test;

import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.core.FHIRMediaType;
import com.ibm.fhir.model.generator.exception.FHIRGeneratorException;
import com.ibm.fhir.model.resource.Bundle;
Expand All @@ -34,7 +35,7 @@
import com.ibm.fhir.model.type.Uri;

public class SearchExtensionsTest extends FHIRServerTestBase {
private static final String EXTENSION_BASE_URL = "http://ibm.com/fhir/extension/Patient/";
private static final String EXTENSION_BASE_URL = FHIRConstants.EXT_BASE + "Patient/";

private static final boolean DEBUG_SEARCH = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import org.owasp.encoder.Encode;

import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.exception.FHIROperationException;
import com.ibm.fhir.model.resource.OperationDefinition;
import com.ibm.fhir.model.resource.OperationOutcome;
Expand All @@ -29,7 +30,6 @@
import com.ibm.fhir.model.type.code.ResourceType;
import com.ibm.fhir.model.util.FHIRUtil;
import com.ibm.fhir.model.util.ModelSupport;
import com.ibm.fhir.server.util.FHIRRestHelper;

public abstract class AbstractOperation implements FHIROperation {
protected final OperationDefinition definition;
Expand Down Expand Up @@ -186,7 +186,7 @@ private FHIROperationException buildUnsupportedResourceTypeException(String reso
.severity(IssueSeverity.FATAL)
.code(IssueType.NOT_SUPPORTED.toBuilder()
.extension(Extension.builder()
.url(FHIRRestHelper.EXTENSION_URL + "/not-supported-detail")
.url(FHIRConstants.EXT_BASE + "not-supported-detail")
.value(Code.of("resource"))
.build())
.build())
Expand Down
Loading

0 comments on commit 61b31b8

Please sign in to comment.