io.github.linuxforhealth.hl7.message.HL7MessageEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hl7v2-fhir-converter Show documentation
Show all versions of hl7v2-fhir-converter Show documentation
FHIR converter is a Java based library that enables converting Hl7v2 messages to FHIR resources
/*
* (C) Copyright IBM Corp. 2020
*
* SPDX-License-Identifier: Apache-2.0
*/
package io.github.linuxforhealth.hl7.message;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.Meta;
import org.joda.time.LocalDateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import ca.uhn.hl7v2.model.Structure;
import io.github.linuxforhealth.api.EvaluationResult;
import io.github.linuxforhealth.api.FHIRResourceTemplate;
import io.github.linuxforhealth.api.InputDataExtractor;
import io.github.linuxforhealth.api.MessageEngine;
import io.github.linuxforhealth.api.ResourceModel;
import io.github.linuxforhealth.api.ResourceValue;
import io.github.linuxforhealth.core.Constants;
import io.github.linuxforhealth.core.ObjectMapperUtil;
import io.github.linuxforhealth.core.exception.RequiredConstraintFailureException;
import io.github.linuxforhealth.core.expression.EvaluationResultFactory;
import io.github.linuxforhealth.core.resource.ResourceResult;
import io.github.linuxforhealth.fhir.FHIRContext;
import io.github.linuxforhealth.fhir.FHIRResourceMapper;
import io.github.linuxforhealth.hl7.message.util.SegmentExtractorUtil;
import io.github.linuxforhealth.hl7.message.util.SegmentGroup;
/**
* Implements Message engine for HL7 message data
*
*
* @author pbhallam
*/
public class HL7MessageEngine implements MessageEngine {
private static final Logger LOGGER = LoggerFactory.getLogger(HL7MessageEngine.class);
private static final ObjectMapper OBJ_MAPPER = ObjectMapperUtil.getJSONInstance();
private FHIRContext context;
private BundleType bundleType;
/**
*
* @param context
*/
public HL7MessageEngine(FHIRContext context) {
this(context, Constants.DEFAULT_BUNDLE_TYPE);
}
/**
*
* @param context
* @param bundleType
*/
public HL7MessageEngine(FHIRContext context, BundleType bundleType) {
this.context = context;
this.bundleType = bundleType;
}
/**
* Converts a HL7 message to a FHIR bundle with the list of resources specified
*
* @see io.github.linuxforhealth.api.MessageEngine#transform(io.github.linuxforhealth.api.InputDataExtractor,
* java.lang.Iterable, java.util.Map)
*/
@Override
public Bundle transform(final InputDataExtractor dataInput, final Iterable resources,
final Map contextValues) {
Preconditions.checkArgument(dataInput != null, "dataInput cannot be null");
Preconditions.checkArgument(contextValues != null, "contextValues cannot be null");
Preconditions.checkArgument(resources != null, "resources cannot be null");
HL7MessageData hl7DataInput = (HL7MessageData) dataInput;
Bundle bundle = initBundle(dataInput);
Map localContextValues = new HashMap<>(contextValues);
for (FHIRResourceTemplate res : resources) {
HL7FHIRResourceTemplate template = (HL7FHIRResourceTemplate) res;
ResourceModel rs = res.getResource();
List resourceResults = new ArrayList<>();
try {
MDC.put("Resource", rs.getName());
if (res.isRepeats()) {
List results =
generateMultiple(hl7DataInput, template, localContextValues, bundle);
if (results != null) {
resourceResults.addAll(results);
}
} else {
ResourceResult resourceValue =
generateSingle(hl7DataInput, template, localContextValues, bundle);
if (resourceValue != null) {
resourceResults.add(resourceValue);
}
}
resourceResults.removeIf(isEmpty());
Map newContextValues =
getContextValuesFromResource(res, resourceResults);
localContextValues.putAll(newContextValues);
} catch (IllegalArgumentException | IllegalStateException e) {
LOGGER.error("Exception during resource {} generation", rs.getName(), e);
} finally {
MDC.remove("Resource");
}
}
return bundle;
}
private static Predicate isEmpty() {
return (ResourceResult p) -> {
return p == null || p.isEmpty() || p.getValue().isEmpty();
};
}
private static Map getContextValuesFromResource(
FHIRResourceTemplate resTemplate, List resourceResults) {
Map localContextValues = new HashMap<>();
// Add resource generated to context map so other resources can reference this resource
if (resourceResults.isEmpty() || !resTemplate.isReferenced()) {
return localContextValues;
}
if (!resTemplate.isRepeats()) {
localContextValues.put(resTemplate.getResourceName(), EvaluationResultFactory
.getEvaluationResult(resourceResults.get(0).getValue().getResource()));
} else {
Map> resourcesByGroup = resourceResults.stream()
.collect(Collectors.groupingBy(r -> getResultIdentifier(resTemplate, r)));
for (Entry> e : resourcesByGroup.entrySet()) {
List evl = new ArrayList<>();
e.getValue().forEach(res -> evl.add(res.getValue()));
localContextValues.put(e.getKey(), EvaluationResultFactory.getEvaluationResult(evl));
}
}
return localContextValues;
}
private static String getResultIdentifier(FHIRResourceTemplate resTemplate,
ResourceResult result) {
if (result != null && result.getGroupId() != null) {
return resTemplate.getResourceName() + "_" + result.getGroupId();
} else {
return resTemplate.getResourceName();
}
}
private Bundle initBundle(final InputDataExtractor dataInput) {
Bundle bundle = new Bundle();
bundle.setType(this.bundleType);
bundle.setId(UUID.randomUUID().toString());
Meta m = new Meta();
m.setSource("Message: " + dataInput.getName() + ", Message Control Id: " + dataInput.getId());
m.setLastUpdated(LocalDateTime.now().toDate());
bundle.setMeta(m);
return bundle;
}
private ResourceResult generateSingle(final HL7MessageData hl7DataInput,
final HL7FHIRResourceTemplate template, final Map contextValues,
final Bundle bundle) {
ResourceModel rs = template.getResource();
List groups = template.getSegment().getGroup();
String segment = template.getSegment().getSegment();
SegmentGroup segmentGroup;
if (groups == null || groups.isEmpty()) {
segmentGroup = SegmentExtractorUtil.extractSegmentGroup(segment,
template.getAdditionalSegments(), hl7DataInput.getHL7DataParser());
} else {
segmentGroup = SegmentExtractorUtil.extractSegmentGroup(groups, segment,
template.getAdditionalSegments(), hl7DataInput.getHL7DataParser(), template.getGroup());
}
if (segmentGroup != null) {
Map localContextValues = new HashMap<>(contextValues);
localContextValues.put(Constants.GROUP_ID,
EvaluationResultFactory.getEvaluationResult(segmentGroup.getGroupId()));
localContextValues.putAll(getContextMap(segmentGroup));
ResourceResult evaluatedValue =
rs.evaluate(hl7DataInput, ImmutableMap.copyOf(localContextValues),
EvaluationResultFactory.getEvaluationResult(segmentGroup.getSegment()));
if (evaluatedValue != null && !evaluatedValue.isEmpty()
&& evaluatedValue.getValue().getResource() != null
&& !evaluatedValue.getValue().getResource().isEmpty()) {
addEntry(template.getResourceName(), evaluatedValue.getValue(), bundle);
List additionalResourceObjects = evaluatedValue.getAdditionalResources();
addToBundle(bundle, additionalResourceObjects);
return evaluatedValue;
}
}
return null;
}
private static Map getContextMap(SegmentGroup segmentGroup) {
// populate local context with additional segment details
Map localContextValues = new HashMap<>();
if (!segmentGroup.getAdditionalSegments().isEmpty()) {
for (Entry> e : segmentGroup.getAdditionalSegments().entrySet()) {
localContextValues.put(e.getKey(),
EvaluationResultFactory.getEvaluationResult(e.getValue()));
}
}
return localContextValues;
}
private List generateMultiple(final HL7MessageData hl7DataInput,
final HL7FHIRResourceTemplate template, final Map contextValues,
final Bundle bundle) {
ResourceModel rs = template.getResource();
List groups = template.getSegment().getGroup();
String segment = template.getSegment().getSegment();
List resourceResults = null;
List multipleSegments =
getMultipleSegments(hl7DataInput, template, groups, segment);
if (!multipleSegments.isEmpty()) {
resourceResults =
generateMultipleResources(hl7DataInput, rs, contextValues, multipleSegments);
}
if (resourceResults != null && !resourceResults.isEmpty()) {
for (ResourceResult resReult : resourceResults) {
addToBundle(bundle, Lists.newArrayList(resReult.getValue()));
addToBundle(bundle, resReult.getAdditionalResources());
}
}
return resourceResults;
}
private static List getMultipleSegments(final HL7MessageData hl7DataInput,
final HL7FHIRResourceTemplate template, List groups, String segment) {
List multipleSegments;
if (groups != null && !groups.isEmpty()) {
multipleSegments = SegmentExtractorUtil.extractSegmentGroups(groups, segment,
template.getAdditionalSegments(), hl7DataInput.getHL7DataParser(), template.getGroup());
} else {
multipleSegments = SegmentExtractorUtil.extractSegmentGroups(segment,
template.getAdditionalSegments(), hl7DataInput.getHL7DataParser());
}
return multipleSegments;
}
private static List generateMultipleResources(final HL7MessageData hl7DataInput,
final ResourceModel rs, final Map contextValues,
final List multipleSegments) {
List resourceResults = new ArrayList<>();
for (SegmentGroup segGroup : multipleSegments) {
List baseValues = new ArrayList<>();
Map localContextValues = new HashMap<>(contextValues);
localContextValues.put(Constants.GROUP_ID,
EvaluationResultFactory.getEvaluationResult(segGroup.getGroupId()));
segGroup.getSegments()
.forEach(struct -> baseValues.add(EvaluationResultFactory.getEvaluationResult(struct)));
localContextValues.putAll(getContextMap(segGroup));
for (EvaluationResult baseValue : baseValues) {
try {
ResourceResult result =
rs.evaluate(hl7DataInput, ImmutableMap.copyOf(localContextValues), baseValue);
if (result != null && result.getValue() != null) {
resourceResults.add(result);
}
} catch (RequiredConstraintFailureException | IllegalArgumentException
| IllegalStateException e) {
LOGGER.warn("Exception encountered", e);
}
}
}
return resourceResults;
}
private void addToBundle(Bundle bundle, List objects) {
if (objects != null && !objects.isEmpty()) {
objects.forEach(obj -> {
addEntry(obj.getFHIRResourceType(), obj, bundle);
});
}
}
private void addEntry(String resourceClass, ResourceValue obj, Bundle bundle) {
try {
if (obj != null) {
LOGGER.info("Converting resourceName {} to FHIR {}", resourceClass, obj.getResource());
String json = OBJ_MAPPER.writeValueAsString(obj.getResource());
if (json != null) {
org.hl7.fhir.r4.model.Resource parsed = context.getParser()
.parseResource(FHIRResourceMapper.getResourceClass(resourceClass), json);
bundle.addEntry().setResource(parsed);
}
}
} catch (JsonProcessingException e) {
LOGGER.error("Processing exception when Serialization", e);
}
}
@Override
public FHIRContext getFHIRContext() {
return context;
}
}