Skip to content

Commit

Permalink
Refactored for HL7ExpressionEvaluator to use the new parser and remov…
Browse files Browse the repository at this point in the history
…e hapi dependency

Co-authored-by: luis-pabon-tf <luis-pabon-tf@users.noreply.github.com>
  • Loading branch information
basiliskus and luis-pabon-tf committed Dec 13, 2024
1 parent 0350cbd commit a1381ef
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 152 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package gov.hhs.cdc.trustedintermediary.rse2e.hl7;

import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.model.Message;
import gov.hhs.cdc.trustedintermediary.wrappers.HealthData;
import gov.hhs.cdc.trustedintermediary.wrappers.HealthDataExpressionEvaluator;
import java.util.Arrays;
Expand All @@ -21,7 +19,6 @@ public class HL7ExpressionEvaluator implements HealthDataExpressionEvaluator {

private static final HL7ExpressionEvaluator INSTANCE = new HL7ExpressionEvaluator();

private static final String NEWLINE_REGEX = "\\r?\\n|\\r";
private static final Pattern OPERATION_PATTERN =
Pattern.compile("^(\\S+)\\s*(=|!=|in)\\s*(.+)$");
private static final Pattern HL7_COUNT_PATTERN = Pattern.compile("(\\S+)\\.count\\(\\)");
Expand All @@ -30,7 +27,6 @@ public class HL7ExpressionEvaluator implements HealthDataExpressionEvaluator {
Pattern.compile("\\(([^)]+)\\)");
private static final Pattern MESSAGE_SOURCE_PATTERN =
Pattern.compile("(input|output)?\\.?(\\S+)");
private static final Pattern HL7_FIELD_NAME_PATTERN = Pattern.compile("(\\w+)(?:-(\\S+))?");

private HL7ExpressionEvaluator() {}

Expand All @@ -56,9 +52,8 @@ public final boolean evaluateExpression(String expression, HealthData<?>... data
String rightOperand =
matcher.group(3); // e.g. MSH-5.1, input.MSH-5.1, 'EPIC', ('EPIC', 'CERNER'), 2

// TODO: replace with our own Message implementation
Message outputMessage = (Message) data[0].getUnderlyingData();
Message inputMessage = (data.length > 1) ? (Message) data[1].getUnderlyingData() : null;
HL7Message outputMessage = (HL7Message) data[0];
HL7Message inputMessage = (data.length > 1) ? (HL7Message) data[1] : null;

// matches a count operation (e.g. OBR.count())
Matcher hl7CountMatcher = HL7_COUNT_PATTERN.matcher(leftOperand);
Expand Down Expand Up @@ -124,23 +119,6 @@ protected boolean evaluateCollectionCount(
}
}

@Deprecated // this guy should be gone by the time this branch is ready to merge
protected boolean evaluateCollectionCount(
Message message, String segmentName, String rightOperand, String operator) {
try {
int count = countSegments(message.encode(), segmentName);
int rightValue = Integer.parseInt(rightOperand);
return evaluateEquality(count, rightValue, operator);
} catch (HL7Exception | NumberFormatException e) {
throw new IllegalArgumentException(
"Error evaluating collection count. Segment: "
+ segmentName
+ ", count: "
+ rightOperand,
e);
}
}

protected String getLiteralOrFieldValue(
HL7Message outputMessage, HL7Message inputMessage, String operand) {
Matcher literalValueMatcher = LITERAL_VALUE_PATTERN.matcher(operand);
Expand All @@ -150,16 +128,6 @@ protected String getLiteralOrFieldValue(
return getFieldValue(outputMessage, inputMessage, operand);
}

@Deprecated // this guy should be gone by the time this branch is ready to merge
protected String getLiteralOrFieldValue(
Message outputMessage, Message inputMessage, String operand) {
Matcher literalValueMatcher = LITERAL_VALUE_PATTERN.matcher(operand);
if (literalValueMatcher.matches()) {
return literalValueMatcher.group(1);
}
return getFieldValue(outputMessage, inputMessage, operand);
}

protected String getFieldValue(
HL7Message outputMessage, HL7Message inputMessage, String fieldName) {
Matcher messageSourceMatcher = MESSAGE_SOURCE_PATTERN.matcher(fieldName);
Expand All @@ -168,112 +136,9 @@ protected String getFieldValue(
}

String fileSource = messageSourceMatcher.group(1);
String fieldNameWithoutFileSource = messageSourceMatcher.group(2);
HL7Message message = getMessageBySource(fileSource, inputMessage, outputMessage);

try {
String messageString = message.encode();
char fieldSeparator = message.getFieldSeparatorValue();
String encodingCharacters = message.getEncodingCharactersValue();
return getSegmentFieldValue(
messageString, fieldNameWithoutFileSource, fieldSeparator, encodingCharacters);
} catch (HL7Exception | NumberFormatException e) {
throw new IllegalArgumentException(
"Failed to extract field value for: " + fieldName, e);
}
}

@Deprecated // this guy should be gone by the time this branch is ready to merge
protected String getFieldValue(Message outputMessage, Message inputMessage, String fieldName) {
Matcher messageSourceMatcher = MESSAGE_SOURCE_PATTERN.matcher(fieldName);
if (!messageSourceMatcher.matches()) {
throw new IllegalArgumentException("Invalid field name format: " + fieldName);
}

String fileSource = messageSourceMatcher.group(1);
String fieldNameWithoutFileSource = messageSourceMatcher.group(2);
Message message = getMessageBySource(fileSource, inputMessage, outputMessage);

try {
String messageString = message.encode();
char fieldSeparator = message.getFieldSeparatorValue();
String encodingCharacters = message.getEncodingCharactersValue();
return getSegmentFieldValue(
messageString, fieldNameWithoutFileSource, fieldSeparator, encodingCharacters);
} catch (HL7Exception | NumberFormatException e) {
throw new IllegalArgumentException(
"Failed to extract field value for: " + fieldName, e);
}
}

// We decided to implement our own simple HL7 parser as the Hapi library was not adequate for
// our needs.
protected static String getSegmentFieldValue(
String hl7Message, String fieldName, char fieldSeparator, String encodingCharacters) {
Matcher hl7FieldNameMatcher = HL7_FIELD_NAME_PATTERN.matcher(fieldName);
if (!hl7FieldNameMatcher.matches()) {
throw new IllegalArgumentException("Invalid HL7 field format: " + fieldName);
}

String segmentName = hl7FieldNameMatcher.group(1);
String segmentFieldIndex = hl7FieldNameMatcher.group(2);

String[] lines = hl7Message.split(NEWLINE_REGEX);
for (String line : lines) {
if (!line.startsWith(segmentName)) {
continue;
}

if (segmentFieldIndex == null) {
return line;
}

String[] fields = line.split(Pattern.quote(String.valueOf(fieldSeparator)));
String[] indexParts = segmentFieldIndex.split("\\.");

try {
int fieldPos = Integer.parseInt(indexParts[0]);

if (segmentName.equals("MSH")) {
fieldPos--;
}

if (fieldPos < 0 || fieldPos >= fields.length) {
throw new IllegalArgumentException(
"Invalid field index (out of bounds): " + segmentFieldIndex);
}

String field = fields[fieldPos];

if (indexParts.length == 1 || field.isEmpty()) {
return field;
}

int subFieldEncodingCharactersIndex = indexParts.length - 2;
if (subFieldEncodingCharactersIndex >= encodingCharacters.length()) {
throw new IllegalArgumentException(
"Invalid subfield index (out of bounds): " + segmentFieldIndex);
}
char subfieldSeparator = encodingCharacters.charAt(subFieldEncodingCharactersIndex);
String[] subfields = field.split(Pattern.quote(String.valueOf(subfieldSeparator)));
int subFieldPos = Integer.parseInt(indexParts[1]) - 1;
return subFieldPos >= 0 && subFieldPos < subfields.length
? subfields[subFieldPos]
: "";
} catch (NumberFormatException e) {
throw new IllegalArgumentException(
"Invalid field index formatting: " + segmentFieldIndex, e);
}
}

return null;
}

protected static int countSegments(String hl7Message, String segmentName) {
return (int)
Arrays.stream(hl7Message.split(NEWLINE_REGEX))
.filter(line -> line.startsWith(segmentName))
.count();
return message.getValue(fieldName);
}

protected HL7Message getMessageBySource(
Expand All @@ -286,16 +151,4 @@ protected HL7Message getMessageBySource(
}
return outputMessage;
}

@Deprecated // this guy should be gone by the time this branch is ready to merge
protected Message getMessageBySource(
String source, Message inputMessage, Message outputMessage) {
if ("input".equals(source)) {
if (inputMessage == null) {
throw new IllegalArgumentException("Input message is null for: " + source);
}
return inputMessage;
}
return outputMessage;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package gov.hhs.cdc.trustedintermediary.rse2e.hl7;

import gov.hhs.cdc.trustedintermediary.wrappers.HealthData;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;

/**
* Represents a HAPI HL7 message that implements the HealthData interface. This class provides a
Expand Down Expand Up @@ -31,6 +33,19 @@ public int getSegmentCount(String name) {
return 0;
}

public String getValue(String hl7Path) {
Matcher hl7FieldNameMatcher = HL7Parser.HL7_FIELD_NAME_PATTERN.matcher(hl7Path);
if (!hl7FieldNameMatcher.matches()) {
throw new IllegalArgumentException("Invalid HL7 path format: " + hl7Path);
}

String segmentName = hl7FieldNameMatcher.group(1);
String segmentFieldIndex = hl7FieldNameMatcher.group(2);
int[] indexParts =
Arrays.stream(segmentFieldIndex.split("\\.")).mapToInt(Integer::parseInt).toArray();

Check notice

Code scanning / CodeQL

Missing catch of NumberFormatException Note

Potential uncaught 'java.lang.NumberFormatException'.
return getValue(segmentName, indexParts);
}

public String getValue(String segmentName, int... indices) {
List<String> fields = getSegmentFields(segmentName);
char[] levelDelimiters = this.getOrderedLevelDelimiters();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class HL7Parser {
protected static final String SUBCOMPONENT_DELIMITER_NAME = "subcomponent";
protected static final String MSH_SEGMENT_NAME = "MSH";
protected static final String DEFAULT_SEGMENT_DELIMITER = "\n";
protected static final Pattern HL7_FIELD_NAME_PATTERN = Pattern.compile("(\\w+)(?:-(\\S+))?");

public static HL7Message parse(String content) {
Map<String, List<String>> segments = new HashMap<>();
Expand Down Expand Up @@ -58,13 +59,13 @@ public static String parseAndGetValue(List<String> fields, char[] delimiters, in
String value = fields.get(indices[0] - 1);
for (int i = 1; i < indices.length; i++) {
if (i >= delimiters.length) {
return null;
throw new IllegalArgumentException("Invalid delimiter index (out of bounds): " + i);
}
char levelDelimiter = delimiters[i];
int index = indices[i] - 1;
String[] parts = value.split(Pattern.quote(String.valueOf(levelDelimiter)));
if (index < 0 || index >= parts.length) {
return null;
throw new IllegalArgumentException("Invalid field index (out of bounds): " + index);
}
value = parts[index];
}
Expand Down

0 comments on commit a1381ef

Please sign in to comment.