------ What's New ------ ------ ------ What's New This page shows new features in HAPI. Version 1.1 Version 1.1 is API compatible with version 1.0 (aside from a method change in the Message interface, but this should not generally be directly extended). Upgrading should be as simple as dropping in the updated JARs. This release includes several important bugfixes and a number of great contributions. See the {{{./changes-report.html}Changelog}} for more information. * Noteworthy new features ** Structure Class Generator Maven Plugin HAPI now includes a Maven Plugin which can be used to translate an HL7 Conformance Profile into a set of HAPI structure objects representing Message/Group/Segment types. See the {{{http://hl7api.sourceforge.net/conformance.html}conformance}} page for more information. ** QuickStart method to initialize Messages AbstractMessage provides an "initQuickstart" method for quickly setting values in the MSH segment to standard default values: +----------------+ public static void main(String[] args) throws HL7Exception, IOException { ORU_R01 msg = new ORU_R01(); msg.initQuickstart("ORU", "R01", "T"); System.out.println(msg); // MSH|^~\&|||||20110220174633.569-0500||ORU^R01^ORU_R01|436|T|2.4 } +----------------+ ** Convenience methods for temportal datatypes HAPI now has some pleasant convenience methods for bridging the gap between HL7's Date/Time types and Java Date/Calendar types: +----------------+ ORU_R01 msg = new ORU_R01(); msg.initQuickstart("ORU", "R01", "T"); TS ts = msg.getPATIENT_RESULT().getORDER_OBSERVATION().getORC().getDateTimeOfTransaction(); DTM time = ts.getTime(); time.setValue("20110201121233"); Date date = time.getValueAsDate(); System.out.println(date); // Tue Feb 01 12:12:33 EST 2011 Calendar cal = new GregorianCalendar(); cal.set(Calendar.YEAR, 2010); cal.set(Calendar.MONTH, 0); cal.set(Calendar.DATE, 29); cal.set(Calendar.HOUR_OF_DAY, 12); cal.set(Calendar.MINUTE, 59); time.setValueToMinute(cal); System.out.println(ts.encode()); // 201001291259 cal = time.getValueAsCalendar(); System.out.println(cal.getTime()); // Tue Feb 01 12:12:33 EST 2011 +----------------+ ** Application Exception Handlers Application instances may now declare an {{{http://hl7api.sourceforge.net/base/apidocs/ca/uhn/hl7v2/app/ApplicationExceptionHandler.html}additional interface}} which allows them to process exceptions and optionally override the system generated response message. (Thanks to Gabriel Landais) +----------------+ /** * Example Application processor which always attempts to generate an * ACK, even if exceptions are thrown */ private class MyApplication implements Application, ApplicationExceptionHandler { public String processException(String incomingMessage, String outgoingMessage, Exception e) throws HL7Exception { // Process/log the exception..? ADT_A01 message = new ADT_A01(); try { message.parse(incomingMessage); } catch (HL7Exception e2) { // ignore } // create a new ACK message ACK ack = new ACK(); try { ack.initQuickstart("ACK", null, null); } catch (IOException e1) { throw new HL7Exception(e1); } ack.getMSA().getMsa1_AcknowledgmentCode().setValue("AA"); ack.getMSA().getMsa2_MessageControlID().setValue(message.getMSH().getMessageControlID().getValue()); return ack.encode(); } public Message processMessage(Message in) throws ApplicationException, HL7Exception { // do some processing try { return in.generateACK(); } catch (IOException e) { throw new HL7Exception(e); } } public boolean canProcess(Message in) { return true; } } +----------------+ Version 1.0 Version 1.0 contains a number of bugfixes. See the {{{changes-report.html}Changelog}} for more information. * Noteworthy new features ** Parser Speed Improvements For version 1.0, a major effort to profile and speed up the parser was made (with some help from Christian Ohr). This resulted in a fairly significant improvement. The figure above shows a comparison between performing parses and encodes on versions 0.6 and 1.0. The simple message used is a very simple ADT^A01 with minimal data. The complex message used is a fairly long ORU^R01 with 50 segments. Times are in millis (shorter is better). [images/speed_comparison_1_0.png] Speed Comparison (Tests performed 1000 iterations of a parse or an encode on an Athlon64 X2 2.2Ghz CPU with 6Gb of RAM) ** Convenience Parse and Encode Methods HAPI structures now have a few new convenience methods to parse and encode various structures. For example, messages may have values parsed in through the parse(String) method, and messages may be re-encoded using the encode() method. +------------+ String string = "MSH|^~\\&|LABGL1||DMCRES||19951002185200||ADT^A01|LABGL1199510021852632|P|2.2\r" + "PID|||T12345||TEST^PATIENT^P||19601002|M||||||||||123456\r" + "PV1|||NER|||||||GSU||||||||E||||||||||||||||||||||||||19951002174900|19951006\r"; ADT_A01 a01 = new ADT_A01(); a01.parse(string); // Parses the text into the message object using PipeParser (by default, can be changed) System.out.println(a01.encode()); // Re-encodes the message using the default parser +------------+ Segments and fields also have parse() and encode() methods +------------+ String pid = a01.getPID().encode(); System.out.println(pid); // PID|||T12345||TEST^PATIENT^P||19601002|M||||||||||123456 String messageType = a01.getMSH().getMessageType().encode(); System.out.println(messageType); // ADT^A01 // Set the message type the old way a01.getMSH().getMessageType().getMessageType().setValue("ADT"); a01.getMSH().getMessageType().getTriggerEvent().setValue("A04"); // A quicker way of doing it a01.getMSH().getMessageType().parse("ADT^A04"); +------------+ ** Convenience Numbered Segment Getters Segments now have a second set of accessors with numbers in them, so that if you want (for instance) to jump straight to PID-3 using your IDE's autocomplete without remembering the name of the field, you can do that. +------------+ // The old way a01.getPID().getPatientIDInternalID(0).parse("1234"); // The alternate way a01.getPID().getPid3_PatientIDInternalID(0).parse("1234"); +------------+ ** Convenience ACK/NAK generators This is a great idea from the Open eHealth {{{http://repo.openehealth.org/confluence/display/ipf/HAPI+extensions}HAPI extensions}} project. The message class can now generate a corresponding ACK or NAK based on the contents of the message's own contents. +------------+ // Parse the message String string = "MSH|^~\\&|LABGL1||DMCRES||19951002185200||ADT^A01|LABGL1199510021852632|P|2.2\r" + "PID|||T12345||TEST^PATIENT^P||19601002|M||||||||||123456\r" + "PV1|||NER|||||||GSU||||||||E||||||||||||||||||||||||||19951002174900|19951006\r"; Message message = new PipeParser().parse(string); // Generate an ACK Message ack = message.generateACK(); System.out.println(ack.encode()); // MSH|^~\&|||||20090926173004.067-0500||ACK|225|P|2.2 // MSA|AA|LABGL1199510021852632 // Generate a NAK Message nak = message.generateACK("AR", new HL7Exception("Error Message", HL7Exception.ACK_AE)); System.out.println(nak.encode()); // MSH|^~\&|||||20090926180218.494-0500||ACK|231|P|2.2 // MSA|AR|LABGL1199510021852632 // ERR|^^^2&ERROR&hl70357&&Error Message +------------+ ** Better handling of unexpected segments in PipeParser Thanks to an overhaul of the PipeParser's inner workings, it can now handle unexpected segments (such as custom Z-segments in standard messages) in a much more predictable way, reducing the need for custom classes much of the time. Consider the following code sample (note the custom 4th segment- ZPI): +-------------+ String messageText = "MSH|^~\\&|IRIS|SANTER|AMB_R|SANTER|200803051508||ADT^A01|263206|P|2.5\r" + "EVN||200803051509||||200803031508\r" + "PID|||5520255^^^PK^PK~ZZZZZZ83M64Z148R^^^CF^CF~ZZZZZZ83M64Z148R^^^SSN^SSN^^20070103^99991231~^^^^TEAM||ZZZ^ZZZ||19830824|F||||||||||||||||||||||N\r" + "ZPI|Fido~Fred|13\r" + "PV1||I|6402DH^^^^^^^^MED. 1 - ONCOLOGIA^^OSPEDALE MAGGIORE DI LODI&LODI|||^^^^^^^^^^OSPEDALE MAGGIORE DI LODI&LODI|13936^TEST^TEST||||||||||5068^TEST2^TEST2||2008003369||||||||||||||||||||||||||200803031508\r" + "PR1|1||1111^Mastoplastica|Protesi|20090224|02|"; // HAPI will still parse this message fine ADT_A03 message = (ADT_A03) new PipeParser().parse(messageText); +--------------+ Previously, upon hitting the unexpected ZPI segment, the parser would recurse as deep as it could into the first group it found, and would drop the unexpected segment at the tail of that, leaving you with segments in completely unexpected places. The new behaviour is that upon finding a segment that isn't supposed to be found anywhere in the message, a custom segment is inserted into the current location, and parsing continues normally. Note that if you have code depending on the old behaviour, PipeParser has a setLegacyMode and corresponding system property that can be set (see the javadoc) ** Insert and delete segment repetitions It is now possible to insert and delete repetitions of segments and segment groups at arbitrary positions within their parent group. +--------------+ // Insert a new observation group (OBX-NTE) segment between reps 1 and 2 ORU_R01_OBSERVATION newGroup = oruR01.getPATIENT_RESULT().getORDER_OBSERVATION().insertOBSERVATION(1); newGroup.getOBX().getSetIDOBX().setValue("2"); // Delete the observation group (OBX-NTE) segment between reps 1 and 2 ORU_R01_OBSERVATION obx = oruR01.getPATIENT_RESULT().getORDER_OBSERVATION().removeOBSERVATION(1); +--------------+