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 #2143 - load compartment definitions from the registry #2381

Merged
merged 1 commit into from
May 19, 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
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2019
* (C) Copyright IBM Corp. 2019, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -16,8 +16,6 @@
/**
* Compartment Cache is a localized class to cache the compartment information and provide helper methods to add to the
* cache.
*
* @author pbastide
*/
public class CompartmentCache {

Expand All @@ -32,21 +30,22 @@ public CompartmentCache() {

/**
* add the code and parameters to the given compartment cache.
*
* @param inclusionCode
* @param params
*
* @param inclusionResourceCode the name of the resource type that can be within the target compartment type
* @param params the inclusion criteria used to determine whether a resource of type {@code inclusionResourceCode}
* is in a target compartment
*/
public void add(java.lang.String inclusionCode, List<com.ibm.fhir.model.type.String> params) {
public void add(java.lang.String inclusionResourceCode, List<com.ibm.fhir.model.type.String> params) {
if (params != null) {
// Fast Conversion to java.lang.String
List<String> paramsAsStrings = params.stream().map(param -> param.getValue()).collect(Collectors.toList());
codeAndParams.put(inclusionCode, paramsAsStrings);
codeAndParams.put(inclusionResourceCode, paramsAsStrings);
}
}

/**
* gets the resource types (codes) in the compartment
*
*
* @return
*/
public List<String> getResourceTypesInCompartment() {
Expand All @@ -55,7 +54,7 @@ public List<String> getResourceTypesInCompartment() {

/**
* get parameters by resource type in the compartment cache.
*
*
* @param resourceType
* @return
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,95 +1,52 @@
/*
* (C) Copyright IBM Corp. 2019, 2020
* (C) Copyright IBM Corp. 2019, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/

package com.ibm.fhir.search.compartment;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;

import com.ibm.fhir.exception.FHIRException;
import com.ibm.fhir.model.format.Format;
import com.ibm.fhir.model.generator.FHIRGenerator;
import com.ibm.fhir.model.generator.exception.FHIRGeneratorException;
import com.ibm.fhir.model.parser.FHIRParser;
import com.ibm.fhir.model.resource.Bundle;
import com.ibm.fhir.model.resource.CompartmentDefinition;
import com.ibm.fhir.model.resource.CompartmentDefinition.Resource;
import com.ibm.fhir.model.type.code.BundleType;
import com.ibm.fhir.path.FHIRPathNode;
import com.ibm.fhir.path.FHIRPathResourceNode;
import com.ibm.fhir.path.evaluator.FHIRPathEvaluator;
import com.ibm.fhir.path.evaluator.FHIRPathEvaluator.EvaluationContext;
import com.ibm.fhir.registry.FHIRRegistry;
import com.ibm.fhir.search.exception.FHIRSearchException;
import com.ibm.fhir.search.exception.SearchExceptionUtil;

/**
* This class supplements SearchUtil with compartment-specific utilities. <br>
* The compartments are defined using FHIR R4 CompartmentDefinitions. <br>
* The R4 CompartmentDefintions and boundaries are defined at https://www.hl7.org/fhir/compartmentdefinition.html <br>
* The R4 CompartmentDefintions and boundaries are defined at https://hl7.org/fhir/R4/compartmentdefinition.html <br>
* <br>
* CompartmentDefintion:
* <ul>
* <li>Patient - https://www.hl7.org/fhir/compartmentdefinition-patient.json</li>
* <li>Encounter - https://www.hl7.org/fhir/compartmentdefinition-encounter.json</li>
* <li>RelatedPerson - https://www.hl7.org/fhir/compartmentdefinition-relatedperson.json</li>
* <li>Practitioner - https://www.hl7.org/fhir/compartmentdefinition-practitioner.json</li>
* <li>Device - https://www.hl7.org/fhir/compartmentdefinition-device.json</li>
* <li>Patient - https://hl7.org/fhir/R4/compartmentdefinition-patient.json</li>
* <li>Encounter - https://hl7.org/fhir/R4/compartmentdefinition-encounter.json</li>
* <li>RelatedPerson - https://hl7.org/fhir/R4/compartmentdefinition-relatedperson.json</li>
* <li>Practitioner - https://hl7.org/fhir/R4/compartmentdefinition-practitioner.json</li>
* <li>Device - https://hl7.org/fhir/R4/compartmentdefinition-device.json</li>
* </ul>
*
* This class extracts the Compartment Logic from SearchUtil, converts the Custom Compartment logic and format into the
* ComponentDefintion, adds support for the the default definition.
*
* <br>
* Load the class in the classloader to initialize static members. Call this before using the class in order to avoid a
* slight performance hit on first use.
* Call {@link #init()} to initialize static members and avoid a slight performance hit on first use.
*/
public class CompartmentUtil {

private static final String CLASSNAME = CompartmentUtil.class.getName();
private static final Logger log = Logger.getLogger(CLASSNAME);

// FHIR:
public static final String FHIR_PATH_BUNDLE_ENTRY = "entry.children()";
public static final String RESOURCE = "/compartments.json";

// List of compartmentDefinitions.
private static final Set<String> compartmentDefinitions = new HashSet<String>() {

private static final long serialVersionUID = 7152515293380769882L;

{
add("/compartments/compartmentdefinition-device.json");
add("/compartments/compartmentdefinition-encounter.json");
add("/compartments/compartmentdefinition-patient.json");
add("/compartments/compartmentdefinition-practitioner.json");
add("/compartments/compartmentdefinition-relatedperson.json");
}
};

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

// Map of Resource type to ResourceCompartmentCache
// Map of Inclusion resource type to ResourceCompartmentCache
private static final Map<String, ResourceCompartmentCache> resourceCompartmentMap = new HashMap<>();

static {
// make one pass over the configuration to build both maps
// make one pass over the CompartmentDefinitions to build both maps
buildMaps(compartmentMap, resourceCompartmentMap);
}


/**
* Loads the class in the classloader to initialize static members. Call this before using the class in order to
* avoid a slight performance hit on first use.
Expand All @@ -99,11 +56,8 @@ public static void init() {
}

// Exceptions:
public static final String PARSE_EXCEPTION = "Unable to parse the entry that is read from compartments.json %s";
public static final String IO_EXCEPTION = "Unable to read the entry that is read from compartments.json %s";
public static final String INVALID_COMPARTMENT = "Invalid compartment: %s";
public static final String INVALID_COMPARTMENT_AND_RESOURCE = "Invalid resource type: %s for compartment: %s";
public static final String FROM_STREAM = "from_stream";

private CompartmentUtil() {
// No Operation
Expand All @@ -115,76 +69,39 @@ private CompartmentUtil() {
* @implNote the maps being built are passed in as arguments to aid unit testing
* @param compMap map of compartment name to CompartmentCache
* @param resourceCompMap map of resource type name to ResourceCompartmentCache
* @throws IOException
*/
public static final void buildMaps(Map<String, CompartmentCache> compMap, Map<String, ResourceCompartmentCache> resourceCompMap) {
buildMaps(RESOURCE, compMap, resourceCompMap);
}

/**
* Builds an in-memory model of the Compartment map defined in compartments.json, for supporting compartment based
* FHIR searches.
* @implNote the maps being built are passed in as arguments to aid unit testing
* @param the source resource to be read using getResourceAsStream
* @param compMap map of compartment name to CompartmentCache
* @param resourceCompMap map of resource type name to ResourceCompartmentCache
* @throws IOException
*/
public static final void buildMaps(String source, Map<String, CompartmentCache> compMap, Map<String, ResourceCompartmentCache> resourceCompMap) {
if (compMap == null) {
throw new IllegalArgumentException("compMap must not be null");
}

if (resourceCompMap == null) {
throw new IllegalArgumentException("resourceCompMap must not be null");
}

try (InputStreamReader reader = new InputStreamReader(CompartmentUtil.class.getResourceAsStream(source))) {
Bundle bundle = FHIRParser.parser(Format.JSON).parse(reader);

FHIRPathEvaluator evaluator = FHIRPathEvaluator.evaluator();
EvaluationContext evaluationContext = new EvaluationContext(bundle);

Collection<FHIRPathNode> result = evaluator.evaluate(evaluationContext, FHIR_PATH_BUNDLE_ENTRY);

Iterator<FHIRPathNode> iter = result.iterator();
while (iter.hasNext()) {
FHIRPathResourceNode node = iter.next().asResourceNode();

// Convert to Resource and lookup.
CompartmentDefinition compartmentDefinition = node.resource().as(CompartmentDefinition.class);
String compartmentName = compartmentDefinition.getCode().getValue();

// The cached object (a smaller/lighter lookup resource) used for point lookups
CompartmentCache compartmentDefinitionCache = new CompartmentCache();

// Iterates over the resources embedded in the CompartmentDefinition.
for (Resource resource : compartmentDefinition.getResource()) {
String inclusionCode = resource.getCode().getValue();
List<com.ibm.fhir.model.type.String> params = resource.getParam();
// Make sure to only add the valid resource types (at least with one inclusion) instead of all types.
if (!params.isEmpty()) {
compartmentDefinitionCache.add(inclusionCode, params);

// Look up the ResourceCompartmentCache and create a new one if needed
ResourceCompartmentCache rcc = resourceCompMap.get(inclusionCode);
if (rcc == null) {
rcc = new ResourceCompartmentCache();
resourceCompMap.put(inclusionCode, rcc);
}

// Add the mapping for this parameter to the target compartment name
rcc.add(params, compartmentName);
Objects.requireNonNull(compMap, "compMap");
Objects.requireNonNull(compMap, "resourceCompMap");

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

// The cached object (a smaller/lighter lookup resource) used for point lookups
CompartmentCache compartmentDefinitionCache = new CompartmentCache();

// Iterates over the resources embedded in the CompartmentDefinition.
for (Resource resource : compartmentDefinition.getResource()) {
String inclusionResourceCode = resource.getCode().getValue();
List<com.ibm.fhir.model.type.String> params = resource.getParam();
// Make sure to only add the valid resource types (at least with one inclusion) instead of all types.
if (!params.isEmpty()) {
compartmentDefinitionCache.add(inclusionResourceCode, resource.getParam());

// Look up the ResourceCompartmentCache and create a new one if needed
ResourceCompartmentCache rcc = resourceCompMap.get(inclusionResourceCode);
if (rcc == null) {
rcc = new ResourceCompartmentCache();
resourceCompMap.put(inclusionResourceCode, rcc);
}
}

compMap.put(compartmentName, compartmentDefinitionCache);
// Add the mapping for this parameter to the target compartment name
rcc.add(params, compartmentName);
}
}

} catch (FHIRException e) {
log.warning(String.format(PARSE_EXCEPTION, FROM_STREAM));
} catch (IOException e1) {
log.warning(String.format(IO_EXCEPTION, FROM_STREAM));
compMap.put(compartmentName, compartmentDefinitionCache);
}
}

Expand Down Expand Up @@ -241,42 +158,15 @@ public static void checkValidCompartmentAndResource(final String compartment, fi
}
}

/**
* builds the bundle and the resources for the compartments.json and puts out to the output stream.
*
* @throws FHIRGeneratorException
*/
public static void buildCompositeBundle(PrintStream out) throws FHIRGeneratorException {

Bundle.Builder build = Bundle.builder().type(BundleType.COLLECTION);
for (String compartmentDefintion : compartmentDefinitions) {

try (InputStreamReader reader = new InputStreamReader(CompartmentUtil.class.getResourceAsStream(compartmentDefintion))) {
CompartmentDefinition compartmentDefinitionResource = FHIRParser.parser(Format.JSON).parse(reader);

build.entry(Bundle.Entry.builder().resource(compartmentDefinitionResource).build());

} catch (FHIRException e) {
log.warning(String.format(PARSE_EXCEPTION, compartmentDefintion));
} catch (IOException e1) {
log.warning(String.format(IO_EXCEPTION, compartmentDefintion));
}
}

Bundle bundle = build.build();
FHIRGenerator.generator(Format.JSON, true).generate(bundle, out);
out.println(bundle.toString());

}

/**
* Get the map of parameter names used as compartment references for the
* given resource type. For example for CareTeam:
* <pre>
* participant -> {RelatedPerson, Patient}
* patient -> {Patient}
* encounter -> {Encounter}
* ...
* etc.
* </pre>
* @param resourceType the resource type name
* @return a map of parameter name to set of compartment names
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* (C) Copyright IBM Corp. 2020
* (C) Copyright IBM Corp. 2020, 2021
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -37,6 +37,7 @@ public ResourceCompartmentCache() {
* In the schema, we therefore have to store (unique) values for this parameter
* as both patient_compartment and relatedperson_compartment token references.
* @param params a list of model parameter names
* @param compartmentName the compartment associated with these parameters
*/
public void add(List<com.ibm.fhir.model.type.String> params, java.lang.String compartmentName) {
if (params != null) {
Expand Down
Loading