HL7 message builder and unit tests

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
3
down vote

favorite












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() "));


@Test
public void testPatientSetLastName()

testPatient.setLastname("Meier");

String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

assertTrue(result.contains("

@Test
public void testcreatePipeMessageAdmission()

String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

assertTrue(result.contains("

@Test
public void testcreatePipeMessageDischarge() ADT^A03^ADT_A03"));


@Test
public void testWithIncompletePatientData()

testPatient.setLastname("");

String result = testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);

System.out.println(result);










share|improve this question



























    up vote
    3
    down vote

    favorite












    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() "));


    @Test
    public void testPatientSetLastName()

    testPatient.setLastname("Meier");

    String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

    assertTrue(result.contains("

    @Test
    public void testcreatePipeMessageAdmission()

    String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

    assertTrue(result.contains("

    @Test
    public void testcreatePipeMessageDischarge() ADT^A03^ADT_A03"));


    @Test
    public void testWithIncompletePatientData()

    testPatient.setLastname("");

    String result = testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);

    System.out.println(result);










    share|improve this question























      up vote
      3
      down vote

      favorite









      up vote
      3
      down vote

      favorite











      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() "));


      @Test
      public void testPatientSetLastName()

      testPatient.setLastname("Meier");

      String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

      assertTrue(result.contains("

      @Test
      public void testcreatePipeMessageAdmission()

      String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

      assertTrue(result.contains("

      @Test
      public void testcreatePipeMessageDischarge() ADT^A03^ADT_A03"));


      @Test
      public void testWithIncompletePatientData()

      testPatient.setLastname("");

      String result = testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);

      System.out.println(result);










      share|improve this question













      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() "));


      @Test
      public void testPatientSetLastName()

      testPatient.setLastname("Meier");

      String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

      assertTrue(result.contains("

      @Test
      public void testcreatePipeMessageAdmission()

      String result = testHl7builder.createMessage(testPatient, Type.ADMISSION, Format.PIPE);

      assertTrue(result.contains("

      @Test
      public void testcreatePipeMessageDischarge() ADT^A03^ADT_A03"));


      @Test
      public void testWithIncompletePatientData()

      testPatient.setLastname("");

      String result = testHl7builder.createPipeMessage(testPatient, Type.ADMISSION);

      System.out.println(result);












      share|improve this question












      share|improve this question




      share|improve this question








      edited Feb 28 at 23:28









      200_success

      123k14142399




      123k14142399









      asked Feb 28 at 10:01









      Dominik Wolf

      284




      284




















          1 Answer
          1






          active

          oldest

          votes

















          up vote
          2
          down vote



          accepted










          Welcome to Code Review and thanks for a nicely prepared question!



          Structure



          Concerning the design of this code, there is clearly an issue that should be solved with OOP approach. I mean that the following methods:



          public String createPipeMessage(Patient patient, Type type);
          public String createPipeMessageAdmission(Patient patient, Type type);
          public String createPipeMessageTransfer(Patient patient, Type type);
          public String createPipeMessageDischarge(Patient patient, Type type);


          all have same role: creating a message.



          Since the HL7Builder interface is in the same package as the implementation, I suppose that this interface does not come from a third party API and it is something that you can define and change.



          I suggest defining an interface dedicated to pipe message building:



          public interface PipeMessageBuilder 
          public String createPipeMessage(Patient patient, Type type);



          And there should be an implementation for each type of message:



          public class AdmissionMessageBuilder implements PipeMessageBuilder 

          @Override
          public String createPipeMessage(Patient patient, Type type)
          // contents of current createPipeMessageAdmission() impl



          // and so on for other message types


          HL7Builder should provide a method that returns a pipe message builder depending on the format, for example:



          public class HL7Builder 

          public PipeMessageBuilder getBuilderFor(Format format)
          // logic of bulder choice





          I think that with this approach it is not mandatory to bind HL7Builder to an interface. The getBuilderFor method may even be static. The returned builder references may be either constant or instantiated for each call: it depends on other details.



          With this approach, building a message will be reduced to this chaining:



          String message = HL7Builder.getBuilderFor(format).createPipeMessage(patient, type);


          The advantages are significant: 1) flexibility and separation of concerns; 2) tests are much easier.



          The private methods set*Segment, used by all the builders, should be grouped in an abstract class and be accessible from the implementations, so the builders class headers should finally be like public class AdmissionMessageBuilder extends AbstractPipeMessageBuilder. setEvnSegment should be abstract and implemented for each concrete message builder, without the if conditions.



          Other Issues



          Args Validation



          Multiple not-null checks might be extracted:



          private static void ensureNotNull(String value, String argName) 
          if (value == null)
          throw new IllegalArgumentException(argName + " must not be null");




          This will significantly reduce the validation code, for example:



          ensureNotNull(patient, "patient");
          ensureNotNull(type, "type");
          ensureNotNull(format, "format");


          Exceptions Handling



          There is a lot of pollution with unnecessary information or layers:



          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);



          IAE should not be caught, because it is wrapped in yet another IAE. Is it useful?



          The messages in HL7BuilderException are not informative at all. The type of the wrapped exception is known anyway, so the messages should be changed or removed, to have simply throw new HL7BuilderException(e).



          Law of Demeter



          Multiple calls like



          msh.getSendingApplication().getNamespaceID().setValue("KIS");


          represent violations of Law of Demeter. It looks like there are more problems with other entities design.



          JUnit Tests



          The test case class must be in the same package than the tested entity.



          There is a mess in testPatient* initializations:



          testPatientTwo = new Patient.Builder().build();
          testPatientTwo = testPatient;


          I'd also suggest removing Patient fields at all from the test class and create instances of Patient only in the methods that need to use this object.



          Calls like assertTrue(result == 0) should be avoided, because they lack explicitness when there are test failures. It is more expressive when reformulated as assertEquals(0, result), or even with an explanation/details message.



          If there is no way to avoid assertTrue, like in assertTrue(result.contains("|ADT^A01^ADT_A01")), there should be an explanation message, for example:



          String expected = "|ADT^A01^ADT_A01";
          assertTrue(String.format("Result did not contain '%1$s'", expected), result.contains(expected));


          Otherwise, a test failure will produce the message "false was not true".



          And of course, if you choose to extract message builders in separate implementing classes, there should be separate test classes corresponding to each builder.






          share|improve this answer























          • Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
            – mtj
            Mar 1 at 6:34










          • @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
            – Antot
            Mar 1 at 7:30










          • Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
            – Dominik Wolf
            Mar 1 at 8:52










          • Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
            – Dominik Wolf
            Mar 13 at 9:40











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188507%2fhl7-message-builder-and-unit-tests%23new-answer', 'question_page');

          );

          Post as a guest






























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes








          up vote
          2
          down vote



          accepted










          Welcome to Code Review and thanks for a nicely prepared question!



          Structure



          Concerning the design of this code, there is clearly an issue that should be solved with OOP approach. I mean that the following methods:



          public String createPipeMessage(Patient patient, Type type);
          public String createPipeMessageAdmission(Patient patient, Type type);
          public String createPipeMessageTransfer(Patient patient, Type type);
          public String createPipeMessageDischarge(Patient patient, Type type);


          all have same role: creating a message.



          Since the HL7Builder interface is in the same package as the implementation, I suppose that this interface does not come from a third party API and it is something that you can define and change.



          I suggest defining an interface dedicated to pipe message building:



          public interface PipeMessageBuilder 
          public String createPipeMessage(Patient patient, Type type);



          And there should be an implementation for each type of message:



          public class AdmissionMessageBuilder implements PipeMessageBuilder 

          @Override
          public String createPipeMessage(Patient patient, Type type)
          // contents of current createPipeMessageAdmission() impl



          // and so on for other message types


          HL7Builder should provide a method that returns a pipe message builder depending on the format, for example:



          public class HL7Builder 

          public PipeMessageBuilder getBuilderFor(Format format)
          // logic of bulder choice





          I think that with this approach it is not mandatory to bind HL7Builder to an interface. The getBuilderFor method may even be static. The returned builder references may be either constant or instantiated for each call: it depends on other details.



          With this approach, building a message will be reduced to this chaining:



          String message = HL7Builder.getBuilderFor(format).createPipeMessage(patient, type);


          The advantages are significant: 1) flexibility and separation of concerns; 2) tests are much easier.



          The private methods set*Segment, used by all the builders, should be grouped in an abstract class and be accessible from the implementations, so the builders class headers should finally be like public class AdmissionMessageBuilder extends AbstractPipeMessageBuilder. setEvnSegment should be abstract and implemented for each concrete message builder, without the if conditions.



          Other Issues



          Args Validation



          Multiple not-null checks might be extracted:



          private static void ensureNotNull(String value, String argName) 
          if (value == null)
          throw new IllegalArgumentException(argName + " must not be null");




          This will significantly reduce the validation code, for example:



          ensureNotNull(patient, "patient");
          ensureNotNull(type, "type");
          ensureNotNull(format, "format");


          Exceptions Handling



          There is a lot of pollution with unnecessary information or layers:



          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);



          IAE should not be caught, because it is wrapped in yet another IAE. Is it useful?



          The messages in HL7BuilderException are not informative at all. The type of the wrapped exception is known anyway, so the messages should be changed or removed, to have simply throw new HL7BuilderException(e).



          Law of Demeter



          Multiple calls like



          msh.getSendingApplication().getNamespaceID().setValue("KIS");


          represent violations of Law of Demeter. It looks like there are more problems with other entities design.



          JUnit Tests



          The test case class must be in the same package than the tested entity.



          There is a mess in testPatient* initializations:



          testPatientTwo = new Patient.Builder().build();
          testPatientTwo = testPatient;


          I'd also suggest removing Patient fields at all from the test class and create instances of Patient only in the methods that need to use this object.



          Calls like assertTrue(result == 0) should be avoided, because they lack explicitness when there are test failures. It is more expressive when reformulated as assertEquals(0, result), or even with an explanation/details message.



          If there is no way to avoid assertTrue, like in assertTrue(result.contains("|ADT^A01^ADT_A01")), there should be an explanation message, for example:



          String expected = "|ADT^A01^ADT_A01";
          assertTrue(String.format("Result did not contain '%1$s'", expected), result.contains(expected));


          Otherwise, a test failure will produce the message "false was not true".



          And of course, if you choose to extract message builders in separate implementing classes, there should be separate test classes corresponding to each builder.






          share|improve this answer























          • Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
            – mtj
            Mar 1 at 6:34










          • @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
            – Antot
            Mar 1 at 7:30










          • Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
            – Dominik Wolf
            Mar 1 at 8:52










          • Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
            – Dominik Wolf
            Mar 13 at 9:40















          up vote
          2
          down vote



          accepted










          Welcome to Code Review and thanks for a nicely prepared question!



          Structure



          Concerning the design of this code, there is clearly an issue that should be solved with OOP approach. I mean that the following methods:



          public String createPipeMessage(Patient patient, Type type);
          public String createPipeMessageAdmission(Patient patient, Type type);
          public String createPipeMessageTransfer(Patient patient, Type type);
          public String createPipeMessageDischarge(Patient patient, Type type);


          all have same role: creating a message.



          Since the HL7Builder interface is in the same package as the implementation, I suppose that this interface does not come from a third party API and it is something that you can define and change.



          I suggest defining an interface dedicated to pipe message building:



          public interface PipeMessageBuilder 
          public String createPipeMessage(Patient patient, Type type);



          And there should be an implementation for each type of message:



          public class AdmissionMessageBuilder implements PipeMessageBuilder 

          @Override
          public String createPipeMessage(Patient patient, Type type)
          // contents of current createPipeMessageAdmission() impl



          // and so on for other message types


          HL7Builder should provide a method that returns a pipe message builder depending on the format, for example:



          public class HL7Builder 

          public PipeMessageBuilder getBuilderFor(Format format)
          // logic of bulder choice





          I think that with this approach it is not mandatory to bind HL7Builder to an interface. The getBuilderFor method may even be static. The returned builder references may be either constant or instantiated for each call: it depends on other details.



          With this approach, building a message will be reduced to this chaining:



          String message = HL7Builder.getBuilderFor(format).createPipeMessage(patient, type);


          The advantages are significant: 1) flexibility and separation of concerns; 2) tests are much easier.



          The private methods set*Segment, used by all the builders, should be grouped in an abstract class and be accessible from the implementations, so the builders class headers should finally be like public class AdmissionMessageBuilder extends AbstractPipeMessageBuilder. setEvnSegment should be abstract and implemented for each concrete message builder, without the if conditions.



          Other Issues



          Args Validation



          Multiple not-null checks might be extracted:



          private static void ensureNotNull(String value, String argName) 
          if (value == null)
          throw new IllegalArgumentException(argName + " must not be null");




          This will significantly reduce the validation code, for example:



          ensureNotNull(patient, "patient");
          ensureNotNull(type, "type");
          ensureNotNull(format, "format");


          Exceptions Handling



          There is a lot of pollution with unnecessary information or layers:



          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);



          IAE should not be caught, because it is wrapped in yet another IAE. Is it useful?



          The messages in HL7BuilderException are not informative at all. The type of the wrapped exception is known anyway, so the messages should be changed or removed, to have simply throw new HL7BuilderException(e).



          Law of Demeter



          Multiple calls like



          msh.getSendingApplication().getNamespaceID().setValue("KIS");


          represent violations of Law of Demeter. It looks like there are more problems with other entities design.



          JUnit Tests



          The test case class must be in the same package than the tested entity.



          There is a mess in testPatient* initializations:



          testPatientTwo = new Patient.Builder().build();
          testPatientTwo = testPatient;


          I'd also suggest removing Patient fields at all from the test class and create instances of Patient only in the methods that need to use this object.



          Calls like assertTrue(result == 0) should be avoided, because they lack explicitness when there are test failures. It is more expressive when reformulated as assertEquals(0, result), or even with an explanation/details message.



          If there is no way to avoid assertTrue, like in assertTrue(result.contains("|ADT^A01^ADT_A01")), there should be an explanation message, for example:



          String expected = "|ADT^A01^ADT_A01";
          assertTrue(String.format("Result did not contain '%1$s'", expected), result.contains(expected));


          Otherwise, a test failure will produce the message "false was not true".



          And of course, if you choose to extract message builders in separate implementing classes, there should be separate test classes corresponding to each builder.






          share|improve this answer























          • Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
            – mtj
            Mar 1 at 6:34










          • @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
            – Antot
            Mar 1 at 7:30










          • Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
            – Dominik Wolf
            Mar 1 at 8:52










          • Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
            – Dominik Wolf
            Mar 13 at 9:40













          up vote
          2
          down vote



          accepted







          up vote
          2
          down vote



          accepted






          Welcome to Code Review and thanks for a nicely prepared question!



          Structure



          Concerning the design of this code, there is clearly an issue that should be solved with OOP approach. I mean that the following methods:



          public String createPipeMessage(Patient patient, Type type);
          public String createPipeMessageAdmission(Patient patient, Type type);
          public String createPipeMessageTransfer(Patient patient, Type type);
          public String createPipeMessageDischarge(Patient patient, Type type);


          all have same role: creating a message.



          Since the HL7Builder interface is in the same package as the implementation, I suppose that this interface does not come from a third party API and it is something that you can define and change.



          I suggest defining an interface dedicated to pipe message building:



          public interface PipeMessageBuilder 
          public String createPipeMessage(Patient patient, Type type);



          And there should be an implementation for each type of message:



          public class AdmissionMessageBuilder implements PipeMessageBuilder 

          @Override
          public String createPipeMessage(Patient patient, Type type)
          // contents of current createPipeMessageAdmission() impl



          // and so on for other message types


          HL7Builder should provide a method that returns a pipe message builder depending on the format, for example:



          public class HL7Builder 

          public PipeMessageBuilder getBuilderFor(Format format)
          // logic of bulder choice





          I think that with this approach it is not mandatory to bind HL7Builder to an interface. The getBuilderFor method may even be static. The returned builder references may be either constant or instantiated for each call: it depends on other details.



          With this approach, building a message will be reduced to this chaining:



          String message = HL7Builder.getBuilderFor(format).createPipeMessage(patient, type);


          The advantages are significant: 1) flexibility and separation of concerns; 2) tests are much easier.



          The private methods set*Segment, used by all the builders, should be grouped in an abstract class and be accessible from the implementations, so the builders class headers should finally be like public class AdmissionMessageBuilder extends AbstractPipeMessageBuilder. setEvnSegment should be abstract and implemented for each concrete message builder, without the if conditions.



          Other Issues



          Args Validation



          Multiple not-null checks might be extracted:



          private static void ensureNotNull(String value, String argName) 
          if (value == null)
          throw new IllegalArgumentException(argName + " must not be null");




          This will significantly reduce the validation code, for example:



          ensureNotNull(patient, "patient");
          ensureNotNull(type, "type");
          ensureNotNull(format, "format");


          Exceptions Handling



          There is a lot of pollution with unnecessary information or layers:



          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);



          IAE should not be caught, because it is wrapped in yet another IAE. Is it useful?



          The messages in HL7BuilderException are not informative at all. The type of the wrapped exception is known anyway, so the messages should be changed or removed, to have simply throw new HL7BuilderException(e).



          Law of Demeter



          Multiple calls like



          msh.getSendingApplication().getNamespaceID().setValue("KIS");


          represent violations of Law of Demeter. It looks like there are more problems with other entities design.



          JUnit Tests



          The test case class must be in the same package than the tested entity.



          There is a mess in testPatient* initializations:



          testPatientTwo = new Patient.Builder().build();
          testPatientTwo = testPatient;


          I'd also suggest removing Patient fields at all from the test class and create instances of Patient only in the methods that need to use this object.



          Calls like assertTrue(result == 0) should be avoided, because they lack explicitness when there are test failures. It is more expressive when reformulated as assertEquals(0, result), or even with an explanation/details message.



          If there is no way to avoid assertTrue, like in assertTrue(result.contains("|ADT^A01^ADT_A01")), there should be an explanation message, for example:



          String expected = "|ADT^A01^ADT_A01";
          assertTrue(String.format("Result did not contain '%1$s'", expected), result.contains(expected));


          Otherwise, a test failure will produce the message "false was not true".



          And of course, if you choose to extract message builders in separate implementing classes, there should be separate test classes corresponding to each builder.






          share|improve this answer















          Welcome to Code Review and thanks for a nicely prepared question!



          Structure



          Concerning the design of this code, there is clearly an issue that should be solved with OOP approach. I mean that the following methods:



          public String createPipeMessage(Patient patient, Type type);
          public String createPipeMessageAdmission(Patient patient, Type type);
          public String createPipeMessageTransfer(Patient patient, Type type);
          public String createPipeMessageDischarge(Patient patient, Type type);


          all have same role: creating a message.



          Since the HL7Builder interface is in the same package as the implementation, I suppose that this interface does not come from a third party API and it is something that you can define and change.



          I suggest defining an interface dedicated to pipe message building:



          public interface PipeMessageBuilder 
          public String createPipeMessage(Patient patient, Type type);



          And there should be an implementation for each type of message:



          public class AdmissionMessageBuilder implements PipeMessageBuilder 

          @Override
          public String createPipeMessage(Patient patient, Type type)
          // contents of current createPipeMessageAdmission() impl



          // and so on for other message types


          HL7Builder should provide a method that returns a pipe message builder depending on the format, for example:



          public class HL7Builder 

          public PipeMessageBuilder getBuilderFor(Format format)
          // logic of bulder choice





          I think that with this approach it is not mandatory to bind HL7Builder to an interface. The getBuilderFor method may even be static. The returned builder references may be either constant or instantiated for each call: it depends on other details.



          With this approach, building a message will be reduced to this chaining:



          String message = HL7Builder.getBuilderFor(format).createPipeMessage(patient, type);


          The advantages are significant: 1) flexibility and separation of concerns; 2) tests are much easier.



          The private methods set*Segment, used by all the builders, should be grouped in an abstract class and be accessible from the implementations, so the builders class headers should finally be like public class AdmissionMessageBuilder extends AbstractPipeMessageBuilder. setEvnSegment should be abstract and implemented for each concrete message builder, without the if conditions.



          Other Issues



          Args Validation



          Multiple not-null checks might be extracted:



          private static void ensureNotNull(String value, String argName) 
          if (value == null)
          throw new IllegalArgumentException(argName + " must not be null");




          This will significantly reduce the validation code, for example:



          ensureNotNull(patient, "patient");
          ensureNotNull(type, "type");
          ensureNotNull(format, "format");


          Exceptions Handling



          There is a lot of pollution with unnecessary information or layers:



          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);



          IAE should not be caught, because it is wrapped in yet another IAE. Is it useful?



          The messages in HL7BuilderException are not informative at all. The type of the wrapped exception is known anyway, so the messages should be changed or removed, to have simply throw new HL7BuilderException(e).



          Law of Demeter



          Multiple calls like



          msh.getSendingApplication().getNamespaceID().setValue("KIS");


          represent violations of Law of Demeter. It looks like there are more problems with other entities design.



          JUnit Tests



          The test case class must be in the same package than the tested entity.



          There is a mess in testPatient* initializations:



          testPatientTwo = new Patient.Builder().build();
          testPatientTwo = testPatient;


          I'd also suggest removing Patient fields at all from the test class and create instances of Patient only in the methods that need to use this object.



          Calls like assertTrue(result == 0) should be avoided, because they lack explicitness when there are test failures. It is more expressive when reformulated as assertEquals(0, result), or even with an explanation/details message.



          If there is no way to avoid assertTrue, like in assertTrue(result.contains("|ADT^A01^ADT_A01")), there should be an explanation message, for example:



          String expected = "|ADT^A01^ADT_A01";
          assertTrue(String.format("Result did not contain '%1$s'", expected), result.contains(expected));


          Otherwise, a test failure will produce the message "false was not true".



          And of course, if you choose to extract message builders in separate implementing classes, there should be separate test classes corresponding to each builder.







          share|improve this answer















          share|improve this answer



          share|improve this answer








          edited Mar 1 at 9:19


























          answered Feb 28 at 21:04









          Antot

          3,5181515




          3,5181515











          • Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
            – mtj
            Mar 1 at 6:34










          • @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
            – Antot
            Mar 1 at 7:30










          • Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
            – Dominik Wolf
            Mar 1 at 8:52










          • Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
            – Dominik Wolf
            Mar 13 at 9:40

















          • Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
            – mtj
            Mar 1 at 6:34










          • @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
            – Antot
            Mar 1 at 7:30










          • Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
            – Dominik Wolf
            Mar 1 at 8:52










          • Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
            – Dominik Wolf
            Mar 13 at 9:40
















          Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
          – mtj
          Mar 1 at 6:34




          Regarding your ensureNotNull-method: the base library already has this: Objects.requireNonNull()
          – mtj
          Mar 1 at 6:34












          @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
          – Antot
          Mar 1 at 7:30




          @mtj yes, exact. The difference is the type of exception thrown. NPE vs IAE. In this code we use IAE, so this shortcut is justified.
          – Antot
          Mar 1 at 7:30












          Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
          – Dominik Wolf
          Mar 1 at 8:52




          Thank you very much for your time @Antot! I understand and can comprehend everything you wrote and you explained it very well ! I will adapt your recommendations to my code step by step, thanks for sharing your experience.
          – Dominik Wolf
          Mar 1 at 8:52












          Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
          – Dominik Wolf
          Mar 13 at 9:40





          Well, I am done with the whole structure thing and very happy with this solution. Now I have another question: I have to visualize my project with some diagrams and stuff. I have made an UML class diagram for the old and the new Builder, but I want to add some more to show the functionality. Maybe one diagram, that shows the way of a message through my Builder class. I´ve done some research but didn`t find something matching. Would be nice, if someone has an idea?
          – Dominik Wolf
          Mar 13 at 9:40













           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188507%2fhl7-message-builder-and-unit-tests%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation