I had to code a project due to my final exams in April. It is an application which simulates a hospital. There are admissions, transfers and discharges of patients in this hospital. For each of those, there is a hl7 message generated (and in progress send to a server). What is HL7?
For this reason, I implemented the class HL7BuilderImpl with the interface HL7Builder. This Builder is part of my exam and I have to show it to the audit commission.
The HL7Builder gets a Patient (with data like birthday, gender, name and stuff), type (this is an enum class, which contains admission, transfer and discharge) and a format (pipe or xml, but I only had to implement the pipe option), it returns a String. This is, how one of my generated HL7 Messages for a patients admission would look like:
MSH|^~\&|KIS|Testsender|HIS|Testreceiver|201802281055||ADT^A01^ADT_A01|62|T|2.4
EVN|A01|201802281055
PID|||HL7SIM000000041||Doe^John||19780320|M
PV1||I|KAR-2^^^KAR||||||||||||||||HL7SIM000000041C1||||||||||||||||||||KAR|||||201802281055
I used the HAPI Framework to map the patients data to the fields of an hl7-message. Finally, the messaged is parsed to a String and returned to a sender (which is not part of my exams, so i will not ask for reviewing this here).
It is working well and does, what it should, the tests are running green, but i also am an greenhorn in programming, so I would like to ask you some questions:
- What do you think about the structure? I tried to order the methods from general to special an i also tried to seperate as much as necessary and as less as possible.
- First, I implemented 2 methods, which only contain some checks and then delegate to further methods. Due to my less experience, is this a good way to code in practice?
- At this point, I dont know a lot about security with my code. Is there some point, which could be a problem with security with my code?
- I dont have a lot of experience with Unit Tests, i coded some and got a coverage > 80% for my class but i am not sure, if my tests are kinda "too easy"?
- Did I miss something to test?
I would be very thankful for some tips, advice and improvement proposals with my code. Here the HL7BuilderImpl class:
package com.hl7sim.hl7;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.hl7sim.patient.Patient;
import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HL7Exception;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.model.AbstractMessage;
import ca.uhn.hl7v2.model.DataTypeException;
import ca.uhn.hl7v2.model.v24.message.ADT_A01;
import ca.uhn.hl7v2.model.v24.message.ADT_A02;
import ca.uhn.hl7v2.model.v24.message.ADT_A03;
import ca.uhn.hl7v2.model.v24.segment.EVN;
import ca.uhn.hl7v2.model.v24.segment.MSH;
import ca.uhn.hl7v2.model.v24.segment.PID;
import ca.uhn.hl7v2.model.v24.segment.PV1;
import ca.uhn.hl7v2.parser.Parser;
public class HL7BuilderImpl implements HL7Builder {
private static AtomicInteger messageControlId;
private List<String> allHL7s;
public HL7BuilderImpl() {
HL7BuilderImpl.messageControlId = new AtomicInteger(0);
this.allHL7s = new ArrayList<String>();
}
public static int getMessageControlId() {
return messageControlId.get();
}
public static void setMessageControlId(AtomicInteger messageControlId) {
HL7BuilderImpl.messageControlId = messageControlId;
}
@Override
public List<String> getAllHL7s() {
return allHL7s;
}
@Override
public void setAllHL7s(List<String> allHL7s) {
this.allHL7s = allHL7s;
}
@Override
public String createMessage(Patient patient, Type type, Format format) {
if (patient == null) {
throw new IllegalArgumentException("No Patient");
}
if (type == null) {
throw new IllegalArgumentException("No Messagetype");
}
if (format == null) {
throw new IllegalArgumentException("Argument 'format' must not be null");
}
String message = "";
if (format == Format.PIPE) {
message = createPipeMessage(patient, type);
} else if (format == Format.XML) {
} else {
throw new IllegalArgumentException("Format '" + format.name() + "' is not supported.");
}
return message;
}
@Override
public String createPipeMessage(Patient patient, Type type) {
if(type == null) {
throw new IllegalArgumentException("Type may not be null");
}
if(patient == null) {
throw new IllegalArgumentException("No Patients");
}
switch (type) {
case ADMISSION:
return createPipeMessageAdmission(patient, type);
case DISCHARGE:
return createPipeMessageDischarge(patient, type);
case TRANSFER:
return createPipeMessageTransfer(patient, type);
default:
throw new IllegalArgumentException("Unknown Type, Builder not working with this Type");
}
}
@Override
public String createPipeMessageAdmission(Patient patient, Type type) {
ADT_A01 adt = new ADT_A01();
patient.setAdmissionDateTime(LocalDateTime.now());
try {
adt.initQuickstart("ADT", "A01", "T");
setMshSegment(adt.getMSH(), patient);
setPidSegment(adt.getPID(), patient);
setEvnSegment(adt.getEVN(), type);
setPv1Segment(adt.getPV1(), patient);
return parseMessage(adt);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Illegal Argument Exception", e);
} catch (DataTypeException e) {
throw new HL7BuilderException("Data type exception", e);
} catch (HL7Exception e) {
throw new HL7BuilderException("HL7 exception", e);
} catch (IOException e) {
throw new HL7BuilderException("IO exception", e);
}
}
@Override
public String createPipeMessageTransfer(Patient patient, Type type) {
ADT_A02 adt = new ADT_A02();
try {
adt.initQuickstart("ADT", "A02", "T");
setMshSegment(adt.getMSH(), patient);
setPidSegment(adt.getPID(), patient);
setEvnSegment(adt.getEVN(), type);
setPv1Segment(adt.getPV1(), patient);
setPriorLocation(adt, patient);
return parseMessage(adt);
} catch (DataTypeException e) {
throw new HL7BuilderException("Data Type Exception", e);
} catch (HL7Exception e) {
throw new HL7BuilderException("HL7 Exception", e);
} catch (IOException e) {
throw new HL7BuilderException("I/O Exception", e);
}
}
@Override
public String createPipeMessageDischarge(Patient patient, Type type) {
ADT_A03 adt = new ADT_A03();
patient.setDischargeDateTime(LocalDateTime.now());
try {
adt.initQuickstart("ADT", "A03", "T");
setMshSegment(adt.getMSH(), patient);
setPidSegment(adt.getPID(), patient);
setEvnSegment(adt.getEVN(), type);
setPv1Segment(adt.getPV1(), patient);
return parseMessage(adt);
} catch (DataTypeException e) {
throw new HL7BuilderException("Data Type Exception", e);
} catch (HL7Exception e) {
throw new HL7BuilderException("HL7 Exception", e);
} catch (IOException e) {
throw new HL7BuilderException("I/O Exception", e);
}
}
private void setMshSegment(MSH msh, Patient patient) throws DataTypeException {
LocalDateTime now = LocalDateTime.now();
msh.getSendingFacility().getNamespaceID().setValue("Testsender");
msh.getSendingApplication().getNamespaceID().setValue("KIS");
msh.getReceivingFacility().getNamespaceID().setValue("Testreceiver");
msh.getReceivingApplication().getNamespaceID().setValue("HIS");
msh.getDateTimeOfMessage().getTimeOfAnEvent().setDateMinutePrecision(now.getYear(), now.getMonthValue(),
now.getDayOfMonth(), now.getHour(), now.getMinute());
msh.getMessageControlID().setValue(String.valueOf(messageControlId.incrementAndGet()));
}
private void setEvnSegment(EVN evn, Type type) throws DataTypeException {
LocalDateTime now = LocalDateTime.now();
evn.getRecordedDateTime().getTimeOfAnEvent().setDateMinutePrecision(now.getYear(), now.getMonthValue(),
now.getDayOfMonth(), now.getHour(), now.getMinute());
if (type == Type.DISCHARGE) {
evn.getEventTypeCode().setValue("A03");
} else if (type == Type.TRANSFER) {
evn.getEventTypeCode().setValue("A02");
} else if (type == Type.ADMISSION) {
evn.getEventTypeCode().setValue("A01");
}
}
private void setPidSegment(PID pid, Patient patient) throws DataTypeException {
pid.getPatientIdentifierList(0).getCx1_ID().setValue(String.valueOf(patient.getId()));
pid.getAdministrativeSex().setValue(patient.getGender());
pid.getPatientName(0).getGivenName().setValue(patient.getFirstname());
pid.getPatientName(0).getFamilyName().getSurname().setValue(patient.getLastname());
pid.getDateTimeOfBirth().getTimeOfAnEvent().setDatePrecision(patient.getBirthday().getYear(),
patient.getBirthday().getMonthValue(), patient.getBirthday().getDayOfMonth());
}
private void setPv1Segment(PV1 pv1, Patient patient) throws DataTypeException {
setPV1Time(pv1, patient);
setPV1Location(pv1, patient);
}
private void setPV1Time(PV1 pv1, Patient patient) throws DataTypeException {
if (patient.getAdmissionDateTime() != null) {
pv1.getAdmitDateTime().getTimeOfAnEvent().setDateMinutePrecision(patient.getAdmissionDateTime().getYear(),
patient.getAdmissionDateTime().getMonthValue(), patient.getAdmissionDateTime().getDayOfMonth(),
patient.getAdmissionDateTime().getHour(), patient.getAdmissionDateTime().getMinute());
}
if (patient.getDischargeDateTime() != null) {
pv1.getDischargeDateTime(0).getTimeOfAnEvent().setDateMinutePrecision(patient.getDischargeDateTime().getYear(),
patient.getDischargeDateTime().getMonthValue(), patient.getDischargeDateTime().getDayOfMonth(),
patient.getDischargeDateTime().getHour(), patient.getDischargeDateTime().getMinute());
}
}
private void setPV1Location(PV1 pv1, Patient patient) throws DataTypeException {
pv1.getServicingFacility().setValue(patient.getDepartment());
pv1.getAssignedPatientLocation().getPl1_PointOfCare().setValue(patient.getWard());
pv1.getAssignedPatientLocation().getPl4_Facility().getNamespaceID().setValue(patient.getDepartment());
pv1.getPatientClass().setValue(patient.getStatus());
pv1.getPv119_VisitNumber().getCx1_ID().setValue(String.valueOf(patient.getInstance()));
}
private void setPriorLocation(ADT_A02 adt, Patient patient) throws DataTypeException {
adt.getPV1().getPriorPatientLocation().getPl1_PointOfCare().setValue(patient.getPriorWard());
adt.getPV1().getPriorPatientLocation().getPl4_Facility().getNamespaceID()
.setValue(patient.getPriorDepartment());
}
private String parseMessage(AbstractMessage message) throws HL7Exception {
try (HapiContext context = new DefaultHapiContext()) {
context.getExecutorService();
Parser parser = context.getPipeParser();
final String result = parser.encode(message);
allHL7s.add(result);
return result;
} catch (IOException e) {
throw new HL7BuilderException("Error on parsing message.", e);
}
}
}
And here the belonging JUnit tests:
package com.hl7sim;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import com.hl7sim.hl7.Format;
import com.hl7sim.hl7.HL7Builder;
import com.hl7sim.hl7.HL7BuilderImpl;
import com.hl7sim.hl7.Type;
import com.hl7sim.patient.Patient;
public class HL7BuilderImplTest {
HL7Builder testHl7builder;
List<String> testAllHl7s;
Patient testPatient;
Patient testPatientTwo;
List<Patient> testBothPatients;
@Before
public void setUp() throws Exception {
testHl7builder = new HL7BuilderImpl();
testPatient = new Patient.Builder().build();
testPatient.setBirthday(LocalDate.of(1911, 11, 11));
testPatient.setFirstname("Test");
testPatient.setLastname("Mann");
testPatient.setGender("M");
testPatient.setAdmissionDateTime(LocalDateTime.now());
testPatient.setDischargeDateTime(LocalDateTime.now());
testPatientTwo = new Patient.Builder().build();
testPatientTwo = testPatient;
testBothPatients = new ArrayList<Patient>();
testAllHl7s = new ArrayList<String>();
testBothPatients.add(testPatient);
testBothPatients.add(testPatientTwo);
}
@Test
public void testGetMessageControlId() {
int result = HL7BuilderImpl.getMessageControlId();
assertTrue(result == 0);
}
@Test
public void testAllHl7sAtBegin() {
int result = testAllHl7s.size();
assertTrue(result == 0);
}
@Test
public void testAddingOneHl7ToAllHl7s() {
String test = "Test";
testAllHl7s.add(test);
int result = testAllHl7s.size();
assertTrue(result == 1);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateMessageWithNullValuesForOnePatient() {
testHl7builder.createMessage((Patient)null, null, null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithoutType() {
testHl7builder.createMessage(testPatient, null, Format.PIPE);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithoutFormat() {
testHl7builder.createMessage(testPatient, Type.ADMISSION, null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithWrongFormat() {
testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.UNKNOWN);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateMessageOnePatientWithWrongType() {
testHl7builder.createMessage(testPatient, Type.UNKNOWN, Format.PIPE);
}
@Test(expected = IllegalArgumentException.class)
public void testCreatePipeMessageOnePatientNoType() {
testHl7builder.createPipeMessage(testPatient, null);
}
@Test(expected = IllegalArgumentException.class)
public void testCreatePipeMessageWithoutOnePatient() {
testPatient = null;
testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);
}
@Test
public void testCreatePipeMessageOnePatientWrongType() {
testHl7builder.createPipeMessage(testPatient, Type.TRANSFER);
}
@Test
public void testPatientSetFirstNameName() {
// Given
testPatient.setFirstname("Albert");
// When
String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);
// Then
assertTrue(result.contains("^Albert|"));
}
@Test
public void testPatientSetLastName() {
testPatient.setLastname("Meier");
String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);
assertTrue(result.contains("|Meier^"));
}
@Test
public void testcreatePipeMessageAdmission() {
String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);
assertTrue(result.contains("|ADT^A01^ADT_A01"));
}
@Test
public void testcreatePipeMessageDischarge() {
String result = testHl7builder.createMessage(testPatient, Type.DISCHARGE, Format.PIPE);
assertTrue(result.contains("|ADT^A03^ADT_A03"));
}
@Test
public void testWithIncompletePatientData() {
testPatient.setLastname("");
String result = testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);
System.out.println(result);
}
}