/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is OpenEMRConnect. * * The Initial Developer of the Original Code is International Training & * Education Center for Health (I-TECH) <http://www.go2itech.org/> * * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * * ***** END LICENSE BLOCK ***** */ package ke.go.moh.oec.lib; import ke.go.moh.oec.LogEntry; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import org.w3c.dom.Document; import org.w3c.dom.Element; import java.util.Date; import java.util.List; import javax.xml.transform.OutputKeys; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import ke.go.moh.oec.Fingerprint; import ke.go.moh.oec.Person; import ke.go.moh.oec.PersonIdentifier; import ke.go.moh.oec.PersonRequest; import ke.go.moh.oec.PersonResponse; import ke.go.moh.oec.RelatedPerson; import ke.go.moh.oec.Visit; import ke.go.moh.oec.Work; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /** * Packs message data into XML strings, and unpacks XML strings into message data. * <p> * Note that all the methods in this class start with "pack", "unpack" or * "common". "pack" methods are used only for packing XML messages. "unpack" * methods are used only for unpacking XML messages. "common" methods are * used by both. * * @author Purity Chemutai * @author Jim Grace */ class XmlPacker { /* * Define the Object IDs (OIDs) we need to know in the HL7 messages. */ private static final String OID_ROOT = "1.3.6.1.4.1.150.2474.11.1."; private static final String OID_MESSAGE_ID = OID_ROOT + "1"; private static final String OID_APPLICATION_ADDRESS = OID_ROOT + "2"; private static final String OID_QUERY_ID = OID_ROOT + "3"; private static final String OID_OTHER_NAME = OID_ROOT + "4.1"; private static final String OID_CLAN_NAME = OID_ROOT + "4.2"; private static final String OID_ALIVE_STATUS = OID_ROOT + "4.3"; private static final String OID_MOTHERS_FIRST_NAME = OID_ROOT + "4.4"; private static final String OID_MOTHERS_MIDDLE_NAME = OID_ROOT + "4.5"; private static final String OID_MOTHERS_LAST_NAME = OID_ROOT + "4.6"; private static final String OID_FATHERS_FIRST_NAME = OID_ROOT + "4.7"; private static final String OID_FATHERS_MIDDLE_NAME = OID_ROOT + "4.8"; private static final String OKD_FATHERS_LAST_NAME = OID_ROOT + "4.9"; private static final String OID_COMPOUND_HEAD_FIRST_NAME = OID_ROOT + "4.10"; private static final String OID_COMPOUND_HEAD_MIDDLE_NAME = OID_ROOT + "4.11"; private static final String OID_COMPOUND_HEAD_LAST_NAME = OID_ROOT + "4.12"; private static final String OID_MARITAL_STATUS = OID_ROOT + "4.13"; private static final String OID_CONSENT_SIGNED = OID_ROOT + "4.14"; private static final String OID_VILAGE_NAME = OID_ROOT + "4.15"; private static final String OID_PREVIOUS_VILLAGE_NAME = OID_ROOT + "4.16"; private static final String OID_LAST_MOVE_DATE = OID_ROOT + "4.17"; private static final String OID_EXPECTED_DELIVERY_DATE = OID_ROOT + "4.18"; private static final String OID_PREGNANCY_END_DATE = OID_ROOT + "4.19"; private static final String OID_PREGNANCY_OUTCOME = OID_ROOT + "4.20"; private static final String OID_SITE_NAME = OID_ROOT + "4.21"; private static final String OID_FINGERPRINT_MATCHED = OID_ROOT + "4.22"; private static final String OID_PATIENT_REGISTRY_ID = OID_ROOT + "5.1"; private static final String OID_MASTER_PATIENT_REGISTRY_ID = OID_ROOT + "5.2"; private static final String OID_CCC_UNIVERSAL_UNIQUE_ID = OID_ROOT + "5.3"; private static final String OID_CCC_LOCAL_PATIENT_ID = OID_ROOT + "5.4"; private static final String KISUMU_HDSS_ID = OID_ROOT + "5.5"; private static final String OID_REGULAR_VISIT_DATE = OID_ROOT + "6.1.1"; private static final String OID_REGULAR_VISIT_ADDRESS = OID_ROOT + "6.1.2"; private static final String OID_REGULAR_VISIT_FACILITY_NAME = OID_ROOT + "6.1.3"; private static final String OID_ONEOFF_VISIT_DATE = OID_ROOT + "6.2.1"; private static final String OID_ONEOFF_VISIT_ADDRESS = OID_ROOT + "6.2.2"; private static final String OID_ONEOFF_VISIT_FACILITY_NAME = OID_ROOT + "6.2.3"; private static final String OID_FINGERPRINT_LEFT_INDEX = OID_ROOT + "7.1"; private static final String OID_FINGERPRINT_LEFT_MIDDLE = OID_ROOT + "7.2"; private static final String OID_FINGERPRINT_LEFT_RING = OID_ROOT + "7.3"; private static final String OID_FINGERPRINT_RIGHT_INDEX = OID_ROOT + "7.4"; private static final String OID_FINGERPRINT_RIGHT_MIDDLE = OID_ROOT + "7.5"; private static final String OID_FINGERPRINT_RIGHT_RING = OID_ROOT + "7.6"; /* * Define other constant objects used in message processing. */ private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd"); private static final SimpleDateFormat SIMPLE_DATE_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); /* * --------------------------------------------------------------------------------------- * * P A C K M E T H O D S * * --------------------------------------------------------------------------------------- */ /** * Packs a data object into a XML string. * * @param m message to be packed * @return the packed XML in a string */ String pack(Message m) { Document doc = packMessage(m); String xml = packDocument(doc); return xml; } /** * Packs a DOM Document structure into an XML string. * * @param doc the DOM Document structure to pack * @return the packed XML string */ String packDocument(Document doc) { StringWriter stringWriter = new StringWriter(); try { Transformer t = TransformerFactory.newInstance().newTransformer(); t.setOutputProperty(OutputKeys.INDENT, "yes"); t.setOutputProperty(OutputKeys.STANDALONE, "yes"); Source source = new DOMSource(doc); t.transform(source, new StreamResult(stringWriter)); } catch (TransformerConfigurationException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } catch (TransformerException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } String returnString = stringWriter.toString(); try { stringWriter.close(); } catch (IOException ex) { } return returnString; } /** * Packs a message into a DOM Document structure. * * @param m message contents to pack * @return message packed in a <code>Document</code> */ Document packMessage(Message m) { Document doc = null; switch (m.getMessageType().getTemplateType()) { case findPerson: doc = packFindPersonMessage(m); break; case findPersonResponse: doc = packFindPersonResponseMessage(m); break; case createPerson: // Uses packGenericPersonRequestMessage(), below. case modifyPerson: // Uses packGenericPersonRequestMessage(), below. case notifyPersonChanged: doc = packGenericPersonRequestMessage(m); break; case createPersonAccepted: // Uses packGenericPersonResponseMessage(), below. case modifyPersonAccepted: doc = packGenericPersonResponseMessage(m); break; case logEntry: doc = packLogEntryMessage(m); break; case getWork: // Uses packWorkMessage(), below. case workDone: // Uses packWorkMessage(), below. case reassignWork: doc = packWorkMessage(m); break; } return doc; } /** * Packs a generic HL7 PersonRequest message into a <code>Document</code>. * <p> * Several of the HL7 person-related messages use the same formatting * rules, even though the templates differ. (The templates differ only * in the boilerplate parts that do not concern us directly.) * These messages are: * <p> * CREATE PERSON <br> * MODIFY PERSON <br> * NOTIFY PERSON CHANGED * * @param m notification message contents to pack * @return packed notification messages */ private Document packGenericPersonRequestMessage(Message m) { Document doc = packTemplate(m); Element root = doc.getDocumentElement(); packHl7Header(root, m); Element personNode = (Element) root.getElementsByTagName("patient").item(0); if (!(m.getMessageData() instanceof PersonRequest)) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packGenericPersonRequestMessage() - Expected data class PersonRequest, got {0}", m.getMessageData().getClass().getName()); return doc; } if (m.getXml() == null) { // Skip the following if we have pre-formed XML: Person p = null; PersonRequest personRequest = (PersonRequest) m.getMessageData(); p = personRequest.getPerson(); packPerson(personNode, p); if (personRequest.isResponseRequested()) { packTagValue(root, "acceptAckCode", "AL"); // Request "ALways" acknowedge. } } return doc; } /** * Packs a generic HL7 PersonResponse message into a <code>Document</code>. * <p> * Several of the HL7 person-related messages use the same formatting * rules, even though the templates differ. (The templates differ only * in the boilerplate parts that do not concern us directly.) * These messages are: * <p> * CREATE PERSON ACCEPTED <br> * MODIFY PERSON ACCEPTED * * @param m notification message contents to pack * @return packed notification messages */ private Document packGenericPersonResponseMessage(Message m) { Document doc = packTemplate(m); Element root = doc.getDocumentElement(); packHl7Header(root, m); Element personNode = (Element) root.getElementsByTagName("patient").item(0); if (!(m.getMessageData() instanceof PersonResponse)) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packGenericPersonResponseMessage() - Expected data class PersonResponse, got {0}", m.getMessageData().getClass().getName()); return doc; } if (m.getXml() == null) { // Skip the following if we have pre-formed XML: Person p = null; PersonResponse personResponse = (PersonResponse) m.getMessageData(); List<Person> personList = personResponse.getPersonList(); if (personList != null && !personList.isEmpty()) { // Are we responding with person data? p = personResponse.getPersonList().get(0); // Yes, get the person data to return. } else { p = new Person(); // No, return an empty person (needed to clear the default template values.) } packPerson(personNode, p); } return doc; } /** * Packs work messages. * * Several of the Work-related messages use the same formatting * rules, even though the templates differ only on the root tag. * * These messages are: * <p> * GET WORK <br> * WORK DONE <br> * REASSIGN WORK * @param m notification message contents to pack * @return packed notification messages */ private Document packWorkMessage(Message m) { Work work = (Work) m.getMessageData(); // Create instance of DocumentBuilderFactory DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // Get the DocumentBuilder DocumentBuilder db = null; try { db = dbf.newDocumentBuilder(); } catch (ParserConfigurationException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } Document doc = db.newDocument(); // Create a blank Document MessageType messageType = m.getMessageType(); String rootName = messageType.getRootXmlTag();//get's the root tag Element root = doc.createElement(rootName); doc.appendChild(root); // Root element is child of the Document packNewElement(doc, root, "sourceAddress", work.getSourceAddress()); packNewElement(doc, root, "notificationId", work.getNotificationId()); packNewElement(doc, root, "reassignAddress", work.getReassignAddress()); return doc; } /** * Packs a Find Person message into a <code>Document</code>. * Uses HL7 Patient Registry Find Candidates Query, PRPA_IN201305UV02. * * @param m search message contents to pack * @return packed search message */ private Document packFindPersonMessage(Message m) { Document doc = packTemplate(m); Element root = doc.getDocumentElement(); packHl7Header(root, m); // The rest of what we want is in the subtree under <queryByParameter> Element q = (Element) root.getElementsByTagName("queryByParameter").item(0); if (!(m.getMessageData() instanceof PersonRequest)) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packFindPersonMessage() - Expected data class PersonRequest, got {0}", m.getMessageData().getClass().getName()); } if (m.getXml() == null) { // Skip the following if we have pre-formed XML: PersonRequest personRequest = (PersonRequest) m.getMessageData(); Person p = personRequest.getPerson(); packPersonName(q, p, "livingSubjectName"); packTagValueAttribute(q, "livingSubjectAdministrativeGender", "code", packEnum(p.getSex())); packTagValueAttribute(q, "livingSubjectBirthTime", "value", packDate(p.getBirthdate())); packTagValueAttribute(q, "livingSubjectDeceasedTime", "value", packDate(p.getDeathdate())); packLivingSubjectId(q, OID_OTHER_NAME, p.getOtherName()); packLivingSubjectId(q, OID_CLAN_NAME, p.getClanName()); packLivingSubjectId(q, OID_ALIVE_STATUS, packEnum(p.getAliveStatus())); // (Used only on findPerson.) packLivingSubjectId(q, OID_MOTHERS_FIRST_NAME, p.getMothersFirstName()); packLivingSubjectId(q, OID_MOTHERS_MIDDLE_NAME, p.getMothersMiddleName()); packLivingSubjectId(q, OID_MOTHERS_LAST_NAME, p.getMothersLastName()); packLivingSubjectId(q, OID_FATHERS_FIRST_NAME, p.getFathersFirstName()); packLivingSubjectId(q, OID_FATHERS_MIDDLE_NAME, p.getFathersMiddleName()); packLivingSubjectId(q, OKD_FATHERS_LAST_NAME, p.getFathersLastName()); packLivingSubjectId(q, OID_COMPOUND_HEAD_FIRST_NAME, p.getCompoundHeadFirstName()); packLivingSubjectId(q, OID_COMPOUND_HEAD_MIDDLE_NAME, p.getCompoundHeadMiddleName()); packLivingSubjectId(q, OID_COMPOUND_HEAD_LAST_NAME, p.getCompoundHeadLastName()); packLivingSubjectId(q, OID_MARITAL_STATUS, packEnum(p.getMaritalStatus())); packLivingSubjectId(q, OID_CONSENT_SIGNED, packEnum(p.getConsentSigned())); packLivingSubjectId(q, OID_SITE_NAME, p.getSiteName()); // (Used only on findPerson.) packLivingSubjectId(q, OID_VILAGE_NAME, p.getVillageName()); packLivingSubjectPersonIdentifiers(q, p, OID_PATIENT_REGISTRY_ID, PersonIdentifier.Type.patientRegistryId); packLivingSubjectPersonIdentifiers(q, p, OID_MASTER_PATIENT_REGISTRY_ID, PersonIdentifier.Type.masterPatientRegistryId); packLivingSubjectPersonIdentifiers(q, p, OID_CCC_UNIVERSAL_UNIQUE_ID, PersonIdentifier.Type.cccUniqueId); packLivingSubjectPersonIdentifiers(q, p, OID_CCC_LOCAL_PATIENT_ID, PersonIdentifier.Type.cccLocalId); packLivingSubjectPersonIdentifiers(q, p, KISUMU_HDSS_ID, PersonIdentifier.Type.kisumuHdssId); packLivingSubjectFingerprints(q, p, OID_FINGERPRINT_LEFT_INDEX, Fingerprint.Type.leftIndexFinger); packLivingSubjectFingerprints(q, p, OID_FINGERPRINT_LEFT_MIDDLE, Fingerprint.Type.leftMiddleFinger); packLivingSubjectFingerprints(q, p, OID_FINGERPRINT_LEFT_RING, Fingerprint.Type.leftRingFinger); packLivingSubjectFingerprints(q, p, OID_FINGERPRINT_RIGHT_INDEX, Fingerprint.Type.rightIndexFinger); packLivingSubjectFingerprints(q, p, OID_FINGERPRINT_RIGHT_MIDDLE, Fingerprint.Type.rightMiddleFinger); packLivingSubjectFingerprints(q, p, OID_FINGERPRINT_RIGHT_RING, Fingerprint.Type.rightRingFinger); } return doc; } /** * Packs a Find Person Response message into a <code>Document</code>. * Uses HL7 Patient Registry Find Candidates Query Response, PRPA_IN201306UV02. * * @param m search message contents to pack * @return packed response message */ private Document packFindPersonResponseMessage(Message m) { Document doc = packTemplate(m); Element root = doc.getDocumentElement(); packHl7Header(root, m); if (!(m.getMessageData() instanceof PersonResponse)) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packFindPersonResponseMessage() - Expected data class PersonResponse, got {0}", m.getMessageData().getClass().getName()); } if (m.getXml() == null) { // Skip the following if we have pre-formed XML: PersonResponse pr = (PersonResponse) m.getMessageData(); List<Person> personList = pr.getPersonList(); /* * Find the <subject> subtree in the template. If there are no person results returned, remove it. * If there is one result, pack it into the template. If there are more than one results, * clone the <subject> subtree so we will have one to pack for each result. * * Note that we do all the cloning first, before any of the filling in. * This is because template elements may be deleted as a person object is packed -- * we want all the template elements there in case some return person structures * have more properties filled in than others. */ Element subject = (Element) root.getElementsByTagName("subject").item(0); if (personList == null || personList.isEmpty()) { packRemoveNode(subject); } else { List<Element> elementList = new ArrayList<Element>(); elementList.add(subject); // Always the first element in the list. for (int i = 1; i < personList.size(); i++) { // From 2nd person (index 1) onwards... subject = packCloneElement(subject); elementList.add(subject); // Add the elements in order to preserve order for debugging/testing } for (int i = 0; i < personList.size(); i++) { // From 1st person (index 0) ondwards... packCandidate(elementList.get(i), personList.get(i)); } } } return doc; } /** * Packs one of the candidates to return in a FindPerson response message. * * @param e top element of the subtree to contain the details for the candidate. * @param p candidate person information. */ private void packCandidate(Element e, Person p) { packPerson(e, p); packRelatedPersons(e, p); packTagValueAttribute(e, "queryMatchObservation", "value", Integer.toString(p.getMatchScore())); } /** * Packs a standard header for a HL7 V3 message. * Standard header elements include message id, and receiver and sender addresses and names. * @param root root of document template to fill in * @param m message parameters containing data to fill in */ private void packHl7Header(Element root, Message m) { packId(root, OID_MESSAGE_ID, m.getMessageId()); Element receiver = (Element) root.getElementsByTagName("receiver").item(0); packId(receiver, OID_APPLICATION_ADDRESS, m.getDestinationAddress()); packTagValue(receiver, "name", m.getDestinationName()); Element sender = (Element) root.getElementsByTagName("sender").item(0); packId(sender, OID_APPLICATION_ADDRESS, m.getSourceAddress()); packTagValue(sender, "name", m.getSourceName()); } /** * Packs person information into a <code>Document</code> subtree * @param e head of the <code>Document</code> subtree in which this person is to be packed * @param p person data to pack into the subtree */ private void packPerson(Element e, Person p) { if (p == null) { p = new Person(); //p.setLastOneOffVisit(null); } packPersonName(e, p, "name"); packTagAttribute(e, "administrativeGenderCode", "code", packEnum(p.getSex())); packTagAttribute(e, "birthTime", "value", packDate(p.getBirthdate())); packTagAttribute(e, "deceasedTime", "value", packDate(p.getDeathdate())); packId(e, OID_OTHER_NAME, p.getOtherName()); packId(e, OID_CLAN_NAME, p.getClanName()); packId(e, OID_ALIVE_STATUS, packEnum(p.getAliveStatus())); packId(e, OID_MOTHERS_FIRST_NAME, p.getMothersFirstName()); packId(e, OID_MOTHERS_MIDDLE_NAME, p.getMothersMiddleName()); packId(e, OID_MOTHERS_LAST_NAME, p.getMothersLastName()); packId(e, OID_FATHERS_FIRST_NAME, p.getFathersFirstName()); packId(e, OID_FATHERS_MIDDLE_NAME, p.getFathersMiddleName()); packId(e, OKD_FATHERS_LAST_NAME, p.getFathersLastName()); packId(e, OID_COMPOUND_HEAD_FIRST_NAME, p.getCompoundHeadFirstName()); packId(e, OID_COMPOUND_HEAD_MIDDLE_NAME, p.getCompoundHeadMiddleName()); packId(e, OID_COMPOUND_HEAD_LAST_NAME, p.getCompoundHeadLastName()); packId(e, OID_MARITAL_STATUS, packEnum(p.getMaritalStatus())); packId(e, OID_CONSENT_SIGNED, packEnum(p.getConsentSigned())); packId(e, OID_VILAGE_NAME, p.getVillageName()); packId(e, OID_PREVIOUS_VILLAGE_NAME, p.getPreviousVillageName()); packId(e, OID_LAST_MOVE_DATE, packDate(p.getLastMoveDate())); packId(e, OID_EXPECTED_DELIVERY_DATE, packDate(p.getExpectedDeliveryDate())); packId(e, OID_PREGNANCY_END_DATE, packDate(p.getPregnancyEndDate())); packId(e, OID_PREGNANCY_OUTCOME, packEnum(p.getPregnancyOutcome())); packId(e, OID_SITE_NAME, p.getSiteName()); packId(e, OID_FINGERPRINT_MATCHED, packBoolean(p.isFingerprintMatched())); packVisit(e, p.getLastRegularVisit(), OID_REGULAR_VISIT_DATE, OID_REGULAR_VISIT_ADDRESS, OID_REGULAR_VISIT_FACILITY_NAME); packVisit(e, p.getLastOneOffVisit(), OID_ONEOFF_VISIT_DATE, OID_ONEOFF_VISIT_ADDRESS, OID_ONEOFF_VISIT_FACILITY_NAME); packPersonIdentifiers(e, p, OID_PATIENT_REGISTRY_ID, PersonIdentifier.Type.patientRegistryId); packPersonIdentifiers(e, p, OID_MASTER_PATIENT_REGISTRY_ID, PersonIdentifier.Type.masterPatientRegistryId); packPersonIdentifiers(e, p, OID_CCC_UNIVERSAL_UNIQUE_ID, PersonIdentifier.Type.cccUniqueId); packPersonIdentifiers(e, p, OID_CCC_LOCAL_PATIENT_ID, PersonIdentifier.Type.cccLocalId); packPersonIdentifiers(e, p, KISUMU_HDSS_ID, PersonIdentifier.Type.kisumuHdssId); packFingerprints(e, p, OID_FINGERPRINT_LEFT_INDEX, Fingerprint.Type.leftIndexFinger); packFingerprints(e, p, OID_FINGERPRINT_LEFT_MIDDLE, Fingerprint.Type.leftMiddleFinger); packFingerprints(e, p, OID_FINGERPRINT_LEFT_RING, Fingerprint.Type.leftRingFinger); packFingerprints(e, p, OID_FINGERPRINT_RIGHT_INDEX, Fingerprint.Type.rightIndexFinger); packFingerprints(e, p, OID_FINGERPRINT_RIGHT_MIDDLE, Fingerprint.Type.rightMiddleFinger); packFingerprints(e, p, OID_FINGERPRINT_RIGHT_RING, Fingerprint.Type.rightRingFinger); } /** * Packs a person's name. For findPerson the tagname containing the name elements * is "livingSubjectName". For other person messages the tagName is "name". * <p> * The first and middle names are packed in two consecutive <given> nodes. * The last name is packed in the <family> node. * If none of the names are present, remove the whole <name> tag. * Otherwise, remove the tag for any part of the name that is not present. * The exception to this is if the first name is absent and the middle name * is present, keep the first <given> node, but pack it with an * empty string. That way the middle name will still go in the second <given> tag. * * @param e head of the <code>Document</code> subtree in which this person is to be packed * @param p person data to pack into the subtree * @param tagName name of the enclosing element for the person's name */ private void packPersonName(Element e, Person p, String tagName) { Element eName = (Element) e.getElementsByTagName(tagName).item(0); /* * If all three names are null, remove the whole <name> subtree. */ if (p.getFirstName() == null && p.getMiddleName() == null && p.getLastName() == null) { packRemoveNode(eName); } else { /* First name and middle name are both stored as <given> nodes. */ NodeList givenList = eName.getElementsByTagName("given"); /* * Pack the first name. * If the first name is not present, we will take care of that * later, when we know if we have a middle name. */ if (p.getFirstName() != null) { givenList.item(0).setTextContent(p.getFirstName()); } /* * Pack the middle name. * If the first name was null, then either make it empty (if there was a middle name) * or remove it completedly (if we are also removing the middle name.) */ if (p.getMiddleName() != null) { givenList.item(1).setTextContent(p.getMiddleName()); if (p.getFirstName() == null) { givenList.item(0).setTextContent(""); } } else { packRemoveNode(givenList.item(1)); if (p.getFirstName() == null) { packRemoveNode(givenList.item(0)); } } /* * Pack the last name. */ packTagValue(eName, "family", p.getLastName()); } } /** * Packs visit information into a person subtree of a <code>Document</code> * * @param e head of the <code>Document</code> subtree in which this person is to be packed * @param v visit information to pack * @param oidVisitDate OID for the XML id tag containing the visit date * @param oidVisitAddress OID for the XML id tag containing the visit address * @param oidVisitFacilityName OID for the XML id tag containing the facility name */ private void packVisit(Element e, Visit v, String oidVisitDate, String oidVisitAddress, String oidVisitFacilityName) { if (v != null) { packId(e, oidVisitDate, packDate(v.getVisitDate())); packId(e, oidVisitAddress, v.getAddress()); packId(e, oidVisitFacilityName, v.getFacilityName()); } else { packId(e, oidVisitDate, null); packId(e, oidVisitAddress, null); packId(e, oidVisitFacilityName, null); } } /** * Packs all person identifiers of a given type into a person subtree of a <code>Document</code> * <p> * Searches through all the identifiers for a person to find identifiers of the given * type. The first such identifier replaces the template value. Subsequent identifiers * are inserted into clones of the template value. If there is no identifier of the given type, * the template value is removed. * <p> * The patient registry ID type is a special case. It doesn't come from the * list of identifiers, but rather from person.personGuid. * * @param subtree head of the <code>Document</code> subtree in which this person is to be packed * @param p person information containing the list of identifiers * @param oidPersonIdentifier the XML template OID for this person identifier type * @param type the person identifier type */ private void packPersonIdentifiers(Element subtree, Person p, String oidPersonIdentifier, PersonIdentifier.Type type) { Element idElement = commonGetId(subtree, oidPersonIdentifier); if (idElement == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "PersonIdentifier type {0}, OID {1} was not found in the template XML file.", new Object[]{type.name(), oidPersonIdentifier}); return; } boolean idTypeFound = false; if (type == PersonIdentifier.Type.patientRegistryId) { String personGuid = p.getPersonGuid(); if (personGuid != null) { packAttribute(idElement, "extension", personGuid); idTypeFound = true; } } else { if (p.getPersonIdentifierList() != null) { for (PersonIdentifier pi : p.getPersonIdentifierList()) { if (pi.getIdentifierType() == type && pi.getIdentifier() != null) { Element e = idElement; if (idTypeFound) { e = packCloneElement(idElement); } packAttribute(e, "extension", pi.getIdentifier()); idTypeFound = true; } } } } if (!idTypeFound) { packRemoveNode(idElement); } } /** * Packs all person identifiers of a given type into a livingSubjectId subtree of a findPrson request. * <p> * Searches through all the identifiers for a person to find identifiers of the given * type. The first such identifier replaces the livingSubjectId template. Subsequent identifiers * are inserted into clones of the template. If there is no identifier of the given type, * the livingSubjectId template is removed. * * @param subtree head of the <code>Document</code> subtree in which this person is to be packed * @param p person information containing the list of identifiers * @param oidPersonIdentifier the XML template OID for this person identifier type * @param type the person identifier type */ private void packLivingSubjectPersonIdentifiers(Element subtree, Person p, String oidPersonIdentifier, PersonIdentifier.Type type) { Element idElement = commonGetLivingSubjectId(subtree, oidPersonIdentifier); if (idElement == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "LivingSubjectId type {0}, OID {1} was not found in the template XML file.", new Object[]{type.name(), oidPersonIdentifier}); return; } boolean idTypeFound = false; if (p.getPersonIdentifierList() != null) { for (PersonIdentifier pi : p.getPersonIdentifierList()) { if (pi.getIdentifierType() == type && pi.getIdentifier() != null) { Element e = idElement; if (idTypeFound) { e = packCloneElement(idElement); } Element v = (Element) e.getElementsByTagName("value").item(0); if (v == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "LivingSubjectId type {0}, OID {1} had no value element in the template XML file.", new Object[]{type.name(), oidPersonIdentifier}); return; } packAttribute(v, "extension", pi.getIdentifier()); idTypeFound = true; } } } if (!idTypeFound) { packRemoveNode(idElement); } } /** * Packs all fingerprints of a given type into a person subtree of a <code>Document</code> * <p> * Searches through through the person data for all fingerprints of the given type. * The first such fingerprint replaces the template value. Subsequent fingerprints * of the same type are inserted into clones of the template value. If there is no * fingerprint of this type, the template for fingerprints of this type is removed. * <p> * Note that we don't really expect multiple fingerprints of the same type in the * same message. But the list of fingerprints in the <code>Person</code> object * allows for this possibility, as does the XML message template. So this * method also allows for this possibility. * * @param subtree head of the <code>Document</code> subtree in which this person is to be packed * @param p person information containing the list of identifiers * @param oidFingerprint the XML template OID for this fingerprint type * @param type the fingerprint type */ private void packFingerprints(Element subtree, Person p, String oidFingerprint, Fingerprint.Type type) { Element fpElement = commonGetId(subtree, oidFingerprint); if (fpElement == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "Fingerprint type {0}, OID {1} was not found in the template XML file.", new Object[]{type.name(), oidFingerprint}); return; } boolean fpTypeFound = false; if (p.getFingerprintList() != null) { for (Fingerprint f : p.getFingerprintList()) { if (f.getFingerprintType() == type && f.getTemplate() != null) { Element e = fpElement; if (fpTypeFound) { e = packCloneElement(fpElement); } packAttribute(e, "extension", packByteArray(f.getTemplate())); fpTypeFound = true; } } } if (!fpTypeFound) { packRemoveNode(fpElement); } } /** * Packs all fingerprints of a given type into a livingSubjectId subtree of a findPrson request. * <p> * Searches through through the person data for all fingerprints of the given type. * The first such fingerprint replaces the livingSubjectId template. Subsequent fingerprints * of the same type are inserted into clones of the template. If there is no * fingerprint of this type, the template for fingerprints of this type is removed. * <p> * Note that we don't really expect multiple fingerprints of the same type in the * same message. But the list of fingerprints in the <code>Person</code> object * allows for this possibility, as does the XML message template. So this * method also allows for this possibility. * * @param subtree head of the <code>Document</code> subtree in which this person is to be packed * @param p person information containing the list of identifiers * @param oidFingerprint the XML template OID for this fingerprint type * @param type the fingerprint type */ private void packLivingSubjectFingerprints(Element subtree, Person p, String oidFingerprint, Fingerprint.Type type) { Element fpElement = commonGetLivingSubjectId(subtree, oidFingerprint); if (fpElement == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "LivingSubject Fingerprint type {0}, OID {1} was not found in the template XML file.", new Object[]{type.name(), oidFingerprint}); return; } boolean fpTypeFound = false; if (p.getFingerprintList() != null) { for (Fingerprint f : p.getFingerprintList()) { if (f.getFingerprintType() == type && f.getTemplate() != null) { Element e = fpElement; if (fpTypeFound) { e = packCloneElement(fpElement); } Element v = (Element) e.getElementsByTagName("value").item(0); if (v == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "LivingSubject Fingerprint type {0}, OID {1} had no value element in the template XML file.", new Object[]{type.name(), oidFingerprint}); return; } packAttribute(v, "extension", packByteArray(f.getTemplate())); fpTypeFound = true; } } } if (!fpTypeFound) { packRemoveNode(fpElement); } } /** * Packs related person information into a <code>Document</code> subtree * @param subtree head of the <code>Document</code> subtree in which this person is to be packed * @param p person data to pack into the subtree */ private void packRelatedPersons(Element subtree, Person p) { Element eRelation = (Element) subtree.getElementsByTagName("personalRelationship").item(0); List<RelatedPerson> householdMembers = p.getHouseholdMembers(); if (householdMembers == null || householdMembers.isEmpty()) { packRemoveNode(eRelation); } else { List<Element> elementList = new ArrayList<Element>(); elementList.add(eRelation); // Always the first element in the list. for (int i = 1; i < householdMembers.size(); i++) { // From 2nd person (index 1) onwards... elementList.add(packCloneElement(eRelation)); } for (int i = 0; i < householdMembers.size(); i++) { // From 1st person (index 0) ondwards... Element e = elementList.get(i); RelatedPerson rp = householdMembers.get(i); packTagAttribute(e, "code", "value", packEnum(rp.getRelation())); Person per = rp.getPerson(); if (per == null) { per = new Person(); // Empty person, so items below will be empty but avoid nullpointer. } packPersonName(e, per, "name"); packTagAttribute(e, "administrativeGenderCode", "code", packEnum(per.getSex())); packTagAttribute(e, "birthTime", "value", packDate(per.getBirthdate())); } } } /** * Clones an element for packing additional values. Adds the element * as a new child to the same parent, placing it just after the * element that is cloned. If there is white space preceeding the * element to be cloned, that white space is also cloned, to * preserve formatting. * * @param e the element to clone * @return the cloned element */ private Element packCloneElement(Element e) { Element clone = (Element) e.cloneNode(true); Node parent = e.getParentNode(); Node previous = e.getPreviousSibling(); Node next = e.getNextSibling(); Node whiteSpace = null; if (previous != null && previous.getNodeType() == Node.TEXT_NODE && previous.getNodeValue().trim().length() == 0) { whiteSpace = previous.cloneNode(false); // deep clone not needed for whitespace } if (next != null) { if (whiteSpace != null) { parent.insertBefore(whiteSpace, next); } parent.insertBefore(clone, next); } else { if (whiteSpace != null) { parent.appendChild(whiteSpace); } parent.appendChild(clone); } return clone; } /** * Packs an attribute of a block's value element. * <pre> * {@code * <subtree> * .... * <(tag)> * ... * <value (attribute)=(value)/> * ... * </(tag)> * .... * </subtree> * } </pre> * Finds the "value" element within the block and inserts the * value into the named attribute. * * @param subtree Document subtree in which to look for the element * @param tag name of the element under which to pack the value * @param attribute name of the element attribute to receive the value * @param value value to place in the attribute. If null, the element * is removed from the template. */ private void packTagValueAttribute(Element subtree, String tag, String attribute, String value) { Element e = (Element) subtree.getElementsByTagName(tag).item(0); if (e == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packTagValueAttribute() could not find element {0} in the template XML file.", tag); return; } if (value != null) { Element v = (Element) e.getElementsByTagName("value").item(0); if (v == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packTagValueAttribute() could not find ''value'' element under tag {0} in the template XML file.", tag); return; } packAttribute(v, attribute, value); } else { packRemoveNode(e); } } /** * Packs a value into an attribute of a tag. * <pre> * {@code * <subtree> * .... * <(tag) (attribute)=(value)/> * .... * </subtree> * } </pre> * or removes the element if the value is null. * * @param subtree Document subtree in which to look for the element * @param tag name of the element in which to pack the value * @param attribute name of the element attribute to receive the value * @param value value to place in the attribute. If null, the element * is removed from the template. */ private void packTagAttribute(Element subtree, String tag, String attribute, String value) { Element e = (Element) subtree.getElementsByTagName(tag).item(0); if (e == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packTagAttribute() could not find element {0} (with attribute {1}) in the template XML file.", new Object[]{tag, attribute}); return; } packAttribute(e, attribute, value); } /** * Packs data into an element's attribute value: * <pre> * {@code * <tag (attribute)=(value)/> * } </pre> * or removes the element if the value is null. * * @param e the element whose attribute we are to pack * @param attribute the name of the attribute to pack * @param value the value to put in the attribute */ private void packAttribute(Element e, String attribute, String value) { Node attr = e.getAttributeNode(attribute); if (attr == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packAttribute() could not find attribute {0} on element {1} in the template XML file.", new Object[]{attribute, e.getNodeName()}); return; } if (value != null) { attr.setNodeValue(value); } else { packRemoveNode(e); } } /** * Packs data into the value of an element, by element tag. * <pre> * {@code * <subtree> * .... * <(tag)> * (value) * </(tag)> * .... * </subtree> * } </pre> * or removes the element if the value is null. * * @param subtree Document subtree in which to look for the element * @param tag name of the element in which to pack the value * @param value value to pack in the element. If null, the element * is removed from the template. */ private void packTagValue(Element subtree, String tag, String value) { Element e = (Element) subtree.getElementsByTagName(tag).item(0); if (e == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packTagValue() could not find element{0}", tag); return; } if (value != null) { e.setTextContent(value); } else { packRemoveNode(e); } } /** * Packs a value into the extension attribute of an id tag, by OID. * <pre> * {@code * <subtree> * .... * <id root=(oid) extension=(value)> * .... * </subtree> * } </pre> * or removes the id tag if the value is null. * * @param subtree head of subtree within which to look for the id element. * @param oid the root attribute value for the id element we are looking for. * @param value the value to assign to the id tag, or null if the id tag should be removed. */ private void packId(Element subtree, String oid, String value) { Element id = commonGetId(subtree, oid); if (id == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packId() could not find ID with root attribute {0} in the XML template file", oid); return; } packAttribute(id, "extension", value); } /** * Packs a livingSubjectId subtree. * Searches for a block matching the passed (oid) value: * <pre> * {@code * <subtree> * .... * <livingSubjectId> * <value root="(oid)" extension="(value)"> * <semanticsText>LivingSubject.xxx</semanticsText> * </livingSubjectId> * .... * </subtree> * } </pre> * Inserts (value) into the block, or deletes the block if the supplied (value) is null. * * @param subtree head of subtree within which to look for the id element. * @param oid the root attribute value for the id element we are looking for. * @param value the value to assign inside the livingSubjectId block, or null if the block should be removed. */ private void packLivingSubjectId(Element subtree, String oid, String value) { Element id = commonGetLivingSubjectId(subtree, oid); if (id == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packLivingSubjectId() could not find livingSubjectId with root attribute {0} in the XML template file", oid); return; } if (value != null) { Element v = (Element) id.getElementsByTagName("value").item(0); if (v == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packLivingSubjectId() could not find ''value'' element under tag {0} in the template XML file.", oid); return; } packAttribute(v, "extension", value); } else { packRemoveNode(id); } } /** * Loads a XML message template into a <code>Document</code>. * The message template file is assumed to be among the resources available * to this class, in the "messages/" package relative to the package * storing the current class. In other words, the XML message template files * are packed into the .jar file containing this class. * <p> * Note: If the caller has a pre-formed XML message to use as the template * instead of the fixed template, then it will be used instead. * * @param m message to load the template for * @return the loaded template <code>Document</code> */ private Document packTemplate(Message m) { Document doc = null; try { String templateFileName = "/messages/" + m.getMessageType().getTemplateType().name() + ".xml"; InputStream is = null; DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); if (m.getXml() != null) { is = new ByteArrayInputStream(m.getXml().getBytes()); } else { is = XmlPacker.class.getResourceAsStream(templateFileName); if (is == null) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "Unable to open template as resource: " + templateFileName); } } doc = db.parse(is); } catch (SAXException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } catch (IOException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } catch (ParserConfigurationException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } packRemoveComments(doc); // Remove all comments from template to save space when sending. return doc; } /** * Removes all comments from a document. * (Used recursively, removes all comments below this element Node.) * <p> * The purpose of this method is so that XML comments may be used liberally * within an XML template to document the expected values, and these comments * may be removed before sending a protocol message using this template. * <p> * If a comment is on a line by itself, the line is also removed. * More precisely, if a comment node is preceeded by a text node consisting * of nothing but white space, the preceeding text node (as well as the * comment) is removed. This effectively removes the whole line if the * comment is on the line by itself. It also removes any white space * before the comment if the comment is on the end of the line after * other tags. * * @param node the Node below which we remove all comments. */ private void packRemoveComments(Node node) { NodeList childNodes = node.getChildNodes(); for (int c = 0; c < childNodes.getLength(); c++) { Node child = childNodes.item(c); switch (child.getNodeType()) { case Node.COMMENT_NODE: c -= packRemoveNode(child); break; case Node.ELEMENT_NODE: packRemoveComments(childNodes.item(c)); // Recursively remove comments. break; default: break; // Other node types we don't expect to have child comments. } } } /** * Removes a node from the document node tree. * <p> * If the node is on a line by itself, the line is also removed. * More precisely, if the node is preceeded by a text node consisting * of nothing but white space, the preceeding text node (as well as the * node) is removed. This effectively removes the whole line if the * node is on the line by itself. It also removes any white space * before the node if the node is on the end of the line after * another node. * * @param n * @return the number of nodes removed (1 if only the node was removed, * 2 if a whitespace-only text node before it was also removed.) * This may be useful to our caller if they are stepping through a * list of nodes using an index. It tells them how many to subtract * from the index to compensate for the deleted nodes and still * properly evaluate all the nodes in the list. */ private int packRemoveNode(Node n) { int numberRemoved = 1; Node parent = n.getParentNode(); Node previous = n.getPreviousSibling(); parent.removeChild(n); if (previous != null && previous.getNodeType() == Node.TEXT_NODE && previous.getNodeValue().trim().length() == 0) { parent.removeChild(previous); numberRemoved = 2; } return numberRemoved; } /** * Packs a Send Log Entry message into a document * Uses LogEntry message type. * * @param m message to be packed * @return DOM Document structure */ private Document packLogEntryMessage(Message m) { if (!(m.getMessageData() instanceof LogEntry)) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "packLogEntryMessage() - Expected data class LogEntry, got {0}", m.getMessageData().getClass().getName()); } LogEntry logEntry = (LogEntry) m.getMessageData(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // Create instance of DocumentBuilderFactory DocumentBuilder db = null; // Get the DocumentBuilder try { db = dbf.newDocumentBuilder(); } catch (ParserConfigurationException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } Document doc = db.newDocument(); // Create a blank Document Element root = doc.createElement("LogEntry"); // Create the root element doc.appendChild(root); // Root element is child of the Document packNewElement(doc, root, "sourceAddress", Mediator.getProperty("Instance.Address")); packNewElement(doc, root, "sourceName", Mediator.getProperty("Instance.Name")); packNewElement(doc, root, "messageId", m.getMessageId()); packNewElement(doc, root, "severity", logEntry.getSeverity()); packNewElement(doc, root, "class", logEntry.getClassName()); packNewElement(doc, root, "dateTime", packDateTime(logEntry.getDateTime())); packNewElement(doc, root, "message", logEntry.getMessage()); return doc; } /** * Packs a value into a new <code>Element</code>, and links it to a parent <code>Element</code>. * If the value is null , the new element is not added * * @param doc the document we are packing into * @param parent parent element for our new element * @param elementName name of the new element to create * @param value value of the new element to create */ private void packNewElement(Document doc, Element parent, String elementName, String value) { if (value != null) { Element element = doc.createElement(elementName); element.setTextContent(value); parent.appendChild(element); } } /** * Packs a binary array of bytes into a hexadecimal-encoded string. * If the byte array is <code>null</code>, returns <code>null</code>. * * @param byteArray binary array of bytes to pack * @return the array of bytes encoded as a hexadecimal string */ private String packByteArray(byte[] byteArray) { String result = null; if (byteArray != null) { char[] c = new char[byteArray.length * 2]; int j = 0; for (byte b : byteArray) { c[j++] = packHexDigit((b & 0xF0) >>> 4); c[j++] = packHexDigit(b & 0x0F); } result = new String(c); } return result; } /** * Packs an integer value 0-15 into a single hex digit 0-F. * * @param val integer value 0-15 to pack * @return hex digit 0-F */ private static char packHexDigit(int val) { if (val < 10) { return (char) ('0' + val); } else { return (char) ('A' + val - 10); } } /** * Packs the value of an enumerated type into a string. * If the enumerated type is <code>null</code>, returns <code>null</code>. * * @param e the enumerated value to be packed * @return the original enumerated value, packed into a string */ private String packEnum(Enum e) { if (e != null) { return e.name(); } else { return null; } } /** * Packs the value of a boolean into a string. * If the value is <code>false</code>, returns <code>null</code>. * * @param b the boolean value to be packed * @return the string, "true" if b is true, otherwise null. */ private String packBoolean(boolean b) { if (b) { return Boolean.toString(b); } else { return null; } } /** * Packs a <code>Date</code> (not including time) into a string. * If the date is <code>null</code>, returns <code>null</code>. * * @param date the date value to be packed * @return <code>String</code> containing the packed date. */ private String packDate(Date date) { String dateString = null; if (date != null) { dateString = SIMPLE_DATE_FORMAT.format(date); } return dateString; } /** * Packs a <code>Date</code> (including time) into a string. * * @param dateTime the date and time value to be packed * @return <code>String</code> containing the packed date and time. */ private String packDateTime(Date dateTime) { String dateString = null; if (dateTime != null) { dateString = SIMPLE_DATE_TIME_FORMAT.format(dateTime); } return dateString; } /* * --------------------------------------------------------------------------------------- * * U N P A C K M E T H O D S * * --------------------------------------------------------------------------------------- */ /** * Unpacks a XML string into an object. * * @param m Message to unpack */ void unpack(Message m) { Document doc = unpackXml(m.getXml()); unpackDocument(m, doc); } /** * Unpacks a XML string into DOM Document structure * * @param xml String containing XML request message * @return the DOM Document structure */ Document unpackXml(String xml) { Document doc = null; try { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); InputStream is = new ByteArrayInputStream(xml.getBytes()); doc = db.parse(is); is.close(); } catch (ParserConfigurationException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "Error parsing XML of length " + xml.length() + ":\n" + xml, ex); } catch (SAXException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "Error parsing XML of length " + xml.length() + ":\n" + xml, ex); } catch (IOException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, "Error parsing XML of length " + xml.length() + ":\n" + xml, ex); } return doc; } /** * Unpacks a DOM Document structure into message data * * @param m the message to unpack * @param doc the DOM Document structure to decode */ void unpackDocument(Message m, Document doc) { Element root = doc.getDocumentElement(); String rootName = root.getTagName(); MessageType messageType = MessageTypeRegistry.find(rootName); m.setMessageType(messageType); switch (messageType.getTemplateType()) { case findPerson: unpackFindPersonMessage(m, root); break; case findPersonResponse: unpackFindPersonResponseMessage(m, root); break; case createPerson: // Uses unpackGenericPersonMessage(), below. case modifyPerson: // Uses unpackGenericPersonMessage(), below. case notifyPersonChanged: unpackGenericPersonRequestMessage(m, root); break; case createPersonAccepted: // Uses unpackGenericPersonResponseMessage(), below. case modifyPersonAccepted: unpackGenericPersonResponseMessage(m, root); break; case logEntry: unpackLogEntryMessage(m, root); break; case getWork: // Uses unpackWorkMessage(), below. case workDone: // Uses unpackWorkMessage(), below. case reassignWork: unpackWorkMessage(m, root); break; } } /** * Unpacks a generic HL7 person-related request message into a <code>Document</code>. * <p> * Several of the HL7 person-related request messages use the same formatting * rules, even though the templates differ. (The templates differ only * in the boilerplate parts that do not concern us directly.) * These messages are: * <p> * CREATE PERSON <br> * MODIFY PERSON <br> * CREATE PERSON ACCEPTED <br> * MODIFY PERSON ACCEPTED <br> * NOTIFY PERSON CHANGED * * @param m the message contents to fill in * @param e root node of the person message <code>Document</code> parsed from XML */ private void unpackGenericPersonRequestMessage(Message m, Element e) { PersonRequest personRequest = new PersonRequest(); m.setMessageData(personRequest); Person p = new Person(); personRequest.setPerson(p); unpackHl7Header(m, e); Element ePerson = (Element) e.getElementsByTagName("patient").item(0); unpackPerson(p, ePerson); if (unpackTagValue(e, "acceptAckCode").equals("AL")) { personRequest.setResponseRequested(true); } } /** * Unpacks a generic HL7 person-related response message into a <code>Document</code>. * <p> * Several of the HL7 person-related response messages use the same formatting * rules, even though the templates differ. (The templates differ only * in the boilerplate parts that do not concern us directly.) * These messages are: * <p> * CREATE PERSON ACCEPTED <br> * MODIFY PERSON ACCEPTED <br> * * @param m the message contents to fill in * @param e root node of the person message <code>Document</code> parsed from XML */ private void unpackGenericPersonResponseMessage(Message m, Element e) { PersonResponse personResponse = new PersonResponse(); m.setMessageData(personResponse); List<Person> personList = new ArrayList<Person>(); personResponse.setPersonList(personList); Person p = new Person(); personList.add(p); unpackHl7Header(m, e); Element ePerson = (Element) e.getElementsByTagName("patient").item(0); unpackPerson(p, ePerson); } /** * Unpacks a find person request <code>Document</code> into message data. * Uses HL7 Patient Registry Find Candidates Query, PRPA_IN201305UV02. * * @param m the message contents to fill in * @param e root of the person message <code>Document</code> parsed from XML */ private void unpackFindPersonMessage(Message m, Element e) { PersonRequest personRequest = new PersonRequest(); m.setMessageData(personRequest); Person p = new Person(); personRequest.setPerson(p); unpackHl7Header(m, e); Element q = (Element) e.getElementsByTagName("queryByParameter").item(0); Element el = (Element) q.getElementsByTagName("livingSubjectName").item(0); if (el != null) { unpackPersonName(p, el, "value"); } p.setSex((Person.Sex) unpackEnum(Person.Sex.values(), unpackTagValueAttribute(e, "livingSubjectAdministrativeGender", "code"))); p.setBirthdate(unpackDate(unpackTagValueAttribute(e, "livingSubjectBirthTime", "value"))); p.setDeathdate(unpackDate(unpackTagValueAttribute(e, "livingSubjectDeceasedTime", "value"))); p.setOtherName(unpackLivingSubjectId(q, OID_OTHER_NAME)); p.setClanName(unpackLivingSubjectId(q, OID_CLAN_NAME)); p.setAliveStatus((Person.AliveStatus) unpackEnum(Person.AliveStatus.values(), unpackLivingSubjectId(q, OID_ALIVE_STATUS))); p.setMothersFirstName(unpackLivingSubjectId(q, OID_MOTHERS_FIRST_NAME)); p.setMothersMiddleName(unpackLivingSubjectId(q, OID_MOTHERS_MIDDLE_NAME)); p.setMothersLastName(unpackLivingSubjectId(q, OID_MOTHERS_LAST_NAME)); p.setFathersFirstName(unpackLivingSubjectId(q, OID_FATHERS_FIRST_NAME)); p.setFathersMiddleName(unpackLivingSubjectId(q, OID_FATHERS_MIDDLE_NAME)); p.setFathersLastName(unpackLivingSubjectId(q, OKD_FATHERS_LAST_NAME)); p.setCompoundHeadFirstName(unpackLivingSubjectId(q, OID_COMPOUND_HEAD_FIRST_NAME)); p.setCompoundHeadMiddleName(unpackLivingSubjectId(q, OID_COMPOUND_HEAD_MIDDLE_NAME)); p.setCompoundHeadLastName(unpackLivingSubjectId(q, OID_COMPOUND_HEAD_LAST_NAME)); p.setMaritalStatus((Person.MaritalStatus) unpackEnum(Person.MaritalStatus.values(), unpackLivingSubjectId(q, OID_MARITAL_STATUS))); p.setConsentSigned((Person.ConsentSigned) unpackEnum(Person.ConsentSigned.values(), unpackLivingSubjectId(q, OID_CONSENT_SIGNED))); p.setSiteName(unpackLivingSubjectId(q, OID_SITE_NAME)); p.setVillageName(unpackLivingSubjectId(q, OID_VILAGE_NAME)); unpackLivingSubjectPersonIdentifiers(p, q, OID_PATIENT_REGISTRY_ID, PersonIdentifier.Type.patientRegistryId); unpackLivingSubjectPersonIdentifiers(p, q, OID_MASTER_PATIENT_REGISTRY_ID, PersonIdentifier.Type.masterPatientRegistryId); unpackLivingSubjectPersonIdentifiers(p, q, OID_CCC_UNIVERSAL_UNIQUE_ID, PersonIdentifier.Type.cccUniqueId); unpackLivingSubjectPersonIdentifiers(p, q, OID_CCC_LOCAL_PATIENT_ID, PersonIdentifier.Type.cccLocalId); unpackLivingSubjectPersonIdentifiers(p, q, KISUMU_HDSS_ID, PersonIdentifier.Type.kisumuHdssId); unpackLivingSubjectFingerprints(p, q, OID_FINGERPRINT_LEFT_INDEX, Fingerprint.Type.leftIndexFinger); unpackLivingSubjectFingerprints(p, q, OID_FINGERPRINT_LEFT_MIDDLE, Fingerprint.Type.leftMiddleFinger); unpackLivingSubjectFingerprints(p, q, OID_FINGERPRINT_LEFT_RING, Fingerprint.Type.leftRingFinger); unpackLivingSubjectFingerprints(p, q, OID_FINGERPRINT_RIGHT_INDEX, Fingerprint.Type.rightIndexFinger); unpackLivingSubjectFingerprints(p, q, OID_FINGERPRINT_RIGHT_MIDDLE, Fingerprint.Type.rightMiddleFinger); unpackLivingSubjectFingerprints(p, q, OID_FINGERPRINT_RIGHT_RING, Fingerprint.Type.rightRingFinger); } /** * Unpacks a find person response <code>Document</code> into message data. * Uses HL7 Patient Registry Find Candidates Query Response, PRPA_IN201306UV02. * * @param m the message contents to fill in * @param e root of the person message <code>Document</code> parsed from XML */ private void unpackFindPersonResponseMessage(Message m, Element e) { PersonResponse personResponse = new PersonResponse(); m.setMessageData(personResponse); unpackHl7Header(m, e); NodeList nodeList = e.getElementsByTagName("subject"); int personCount = nodeList.getLength(); if (personCount != 0) { List<Person> personList = new ArrayList<Person>(personCount); personResponse.setPersonList(personList); for (int i = 0; i < personCount; i++) { Person p = new Person(); Element el = (Element) nodeList.item(i); unpackCandidate(p, el); personList.add(p); } } } /** * Unpack a candidate in a findPerson response. * * @param p the person data to fill in * @param e head of the <code>Document</code> subtree in which this person is found */ private void unpackCandidate(Person p, Element e) { unpackPerson(p, e); unpackRelatedPersons(p, e); p.setMatchScore(unpackInt(unpackTagValueAttribute(e, "queryMatchObservation", "value"))); } /** * Unpacks related persons into person data * * @param p the person data to fill in * @param e head of the <code>Document</code> subtree in which this person is found */ private void unpackRelatedPersons(Person p, Element e) { NodeList nodeList = e.getElementsByTagName("personalRelationship"); int relatedPersonCount = nodeList.getLength(); if (relatedPersonCount != 0) { List<RelatedPerson> relatedPersonList = new ArrayList<RelatedPerson>(relatedPersonCount); p.setHouseholdMembers(relatedPersonList); for (int i = 0; i < relatedPersonCount; i++) { RelatedPerson rp = new RelatedPerson(); relatedPersonList.add(rp); Element el = (Element) nodeList.item(i); // Set the type of relation: rp.setRelation((RelatedPerson.Relation) unpackEnum(RelatedPerson.Relation.values(), unpackTagAttribute(el, "code", "value"))); // Set the details of the new person: Person per = new Person(); rp.setPerson(per); unpackPersonName(per, el, "name"); per.setSex((Person.Sex) unpackEnum(Person.Sex.values(), unpackTagAttribute(el, "administrativeGenderCode", "code"))); per.setBirthdate(unpackDate(unpackTagAttribute(el, "birthTime", "value"))); } } } /** * Unpacks a standard HL7 message header into message data. * * @param m the message contents to fill in * @param e root of the person message <code>Document</code> parsed from XML */ private void unpackHl7Header(Message m, Element e) { m.setMessageId(unpackId(e, OID_MESSAGE_ID)); Element receiver = (Element) e.getElementsByTagName("receiver").item(0); if (receiver != null) { m.setDestinationAddress(unpackId(receiver, OID_APPLICATION_ADDRESS)); m.setDestinationName(unpackTagValue(receiver, "name")); } Element sender = (Element) e.getElementsByTagName("sender").item(0); if (sender != null) { m.setSourceAddress(unpackId(sender, OID_APPLICATION_ADDRESS)); m.setSourceName(unpackTagValue(sender, "name")); } } /** * Unpacks a person document subtree into person data * * @param p the person data to fill in * @param e head of the <code>Document</code> subtree in which this person is found */ private void unpackPerson(Person p, Element e) { unpackPersonName(p, e, "name"); p.setSex((Person.Sex) unpackEnum(Person.Sex.values(), unpackTagAttribute(e, "administrativeGenderCode", "code"))); p.setBirthdate(unpackDate(unpackTagAttribute(e, "birthTime", "value"))); p.setDeathdate(unpackDate(unpackTagAttribute(e, "deceasedTime", "value"))); p.setOtherName(unpackId(e, OID_OTHER_NAME)); p.setClanName(unpackId(e, OID_CLAN_NAME)); p.setAliveStatus((Person.AliveStatus) unpackEnum(Person.AliveStatus.values(), unpackId(e, OID_ALIVE_STATUS))); p.setMothersFirstName(unpackId(e, OID_MOTHERS_FIRST_NAME)); p.setMothersMiddleName(unpackId(e, OID_MOTHERS_MIDDLE_NAME)); p.setMothersLastName(unpackId(e, OID_MOTHERS_LAST_NAME)); p.setFathersFirstName(unpackId(e, OID_FATHERS_FIRST_NAME)); p.setFathersMiddleName(unpackId(e, OID_FATHERS_MIDDLE_NAME)); p.setFathersLastName(unpackId(e, OKD_FATHERS_LAST_NAME)); p.setCompoundHeadFirstName(unpackId(e, OID_COMPOUND_HEAD_FIRST_NAME)); p.setCompoundHeadMiddleName(unpackId(e, OID_COMPOUND_HEAD_MIDDLE_NAME)); p.setCompoundHeadLastName(unpackId(e, OID_COMPOUND_HEAD_LAST_NAME)); p.setMaritalStatus((Person.MaritalStatus) unpackEnum(Person.MaritalStatus.values(), unpackId(e, OID_MARITAL_STATUS))); p.setConsentSigned((Person.ConsentSigned) unpackEnum(Person.ConsentSigned.values(), unpackId(e, OID_CONSENT_SIGNED))); p.setVillageName(unpackId(e, OID_VILAGE_NAME)); p.setPreviousVillageName(unpackId(e, OID_PREVIOUS_VILLAGE_NAME)); p.setLastMoveDate(unpackDate(unpackId(e, OID_LAST_MOVE_DATE))); p.setExpectedDeliveryDate(unpackDate(unpackId(e, OID_EXPECTED_DELIVERY_DATE))); p.setPregnancyEndDate(unpackDate(unpackId(e, OID_PREGNANCY_END_DATE))); p.setPregnancyOutcome((Person.PregnancyOutcome) unpackEnum(Person.PregnancyOutcome.values(), unpackId(e, OID_PREGNANCY_OUTCOME))); p.setSiteName(unpackId(e, OID_SITE_NAME)); p.setFingerprintMatched(unpackBoolean(unpackId(e, OID_FINGERPRINT_MATCHED))); p.setLastRegularVisit(unpackVisit(e, OID_REGULAR_VISIT_DATE, OID_REGULAR_VISIT_ADDRESS, OID_REGULAR_VISIT_FACILITY_NAME)); p.setLastOneOffVisit(unpackVisit(e, OID_ONEOFF_VISIT_DATE, OID_ONEOFF_VISIT_ADDRESS, OID_ONEOFF_VISIT_FACILITY_NAME)); unpackPersonIdentifiers(p, e, OID_PATIENT_REGISTRY_ID, PersonIdentifier.Type.patientRegistryId); unpackPersonIdentifiers(p, e, OID_MASTER_PATIENT_REGISTRY_ID, PersonIdentifier.Type.masterPatientRegistryId); unpackPersonIdentifiers(p, e, OID_CCC_UNIVERSAL_UNIQUE_ID, PersonIdentifier.Type.cccUniqueId); unpackPersonIdentifiers(p, e, OID_CCC_LOCAL_PATIENT_ID, PersonIdentifier.Type.cccLocalId); unpackPersonIdentifiers(p, e, KISUMU_HDSS_ID, PersonIdentifier.Type.kisumuHdssId); unpackFingerprints(p, e, OID_FINGERPRINT_LEFT_INDEX, Fingerprint.Type.leftIndexFinger); unpackFingerprints(p, e, OID_FINGERPRINT_LEFT_MIDDLE, Fingerprint.Type.leftMiddleFinger); unpackFingerprints(p, e, OID_FINGERPRINT_LEFT_RING, Fingerprint.Type.leftRingFinger); unpackFingerprints(p, e, OID_FINGERPRINT_RIGHT_INDEX, Fingerprint.Type.rightIndexFinger); unpackFingerprints(p, e, OID_FINGERPRINT_RIGHT_MIDDLE, Fingerprint.Type.rightMiddleFinger); unpackFingerprints(p, e, OID_FINGERPRINT_RIGHT_RING, Fingerprint.Type.rightRingFinger); } /** * Unpacks a person name into a <code>Person</code> object. * For findPerson the tagname containing the name elements is * "livingSubjectName". For other person messages the tagName is "name". * * @param p the person data into which to put the person name. * @param e head of the <code>Document</code> subtree in which this person is found * @param tagName name of the enclosing element for the person's name */ private void unpackPersonName(Person p, Element e, String tagName) { Element eName = (Element) e.getElementsByTagName(tagName).item(0); if (eName != null) { NodeList givenList = eName.getElementsByTagName("given"); if (givenList.item(0) != null) { p.setFirstName(givenList.item(0).getTextContent()); if (givenList.item(1) != null) { p.setMiddleName(givenList.item(1).getTextContent()); } } p.setLastName(unpackTagValue(eName, "family")); } } /** * Unpacks visit information into a <code>Visit</code>. If any of the * visit information is present, allocates a <code>Visit</code> object * and sets the information into the object. If none of the visit * information is present, returns <code>null</code>. * * @param e head of the <code>Document</code> subtree in which this visit is found * @param oidVisitDate OID for the XML id tag containing the visit date * @param oidVisitAddress OID for the XML id tag containing the visit address * @param oidVisitFacilityName OID for the XML id tag containing the facility name * @return v unpacked visit data */ private Visit unpackVisit(Element e, String oidVisitDate, String oidVisitAddress, String oidVisitFacilityName) { Visit v = null; Date visitDate = unpackDate(unpackId(e, oidVisitDate)); String address = unpackId(e, oidVisitAddress); String facilityName = unpackId(e, oidVisitFacilityName); if (address != null || visitDate != null || facilityName != null) { v = new Visit(); v.setVisitDate(visitDate); v.setAddress(address); v.setFacilityName(facilityName); } return v; } /** * Unpacks all ID-tagged person identifiers of a given type. * <p> * Searches through all the person identifiers in a <code>Document</code> subtree * to find identifiers of the given type. For each such identifier, allocates * a <code>PersonIdentifier</code> object and attaches it to the <code>Person</code> object. * <p> * An identifier of type patientRegistryId is unpacked into the person.personGuid field. * All other identifiers are unpacked into the array of PersonIdentifier. * * @param p person information * @param e head of the <code>Document</code> subtree in which * these person identifiers are to be found * @param oidPersonIdentifier the XML template OID for this person identifier type * @param type the person identifier type */ private void unpackPersonIdentifiers(Person p, Element e, String oidPersonIdentifier, PersonIdentifier.Type type) { List<Element> idList = unpackGetIdList(e, oidPersonIdentifier); for (Element id : idList) { if (type == PersonIdentifier.Type.patientRegistryId) { p.setPersonGuid(unpackAttribute(id, "extension")); } else { PersonIdentifier pi = new PersonIdentifier(); pi.setIdentifier(unpackAttribute(id, "extension")); pi.setIdentifierType(type); if (p.getPersonIdentifierList() == null) { p.setPersonIdentifierList(new ArrayList<PersonIdentifier>()); } p.getPersonIdentifierList().add(pi); } } } /** * Unpacks all LivingSubjectID-tagged person identifiers of a given type. * <p> * Searches through all the LivingSubject person identifiers in a <code>Document</code> subtree * to find identifiers of the given type. For each such identifier, allocates * a <code>PersonIdentifier</code> object and attaches it to the <code>Person</code> object. * * @param p person information * @param e head of the <code>Document</code> subtree in which * these person identifiers are to be found * @param oidPersonIdentifier the XML template OID for this person identifier type * @param type the person identifier type */ private void unpackLivingSubjectPersonIdentifiers(Person p, Element e, String oidPersonIdentifier, PersonIdentifier.Type type) { List<Element> idList = unpackGetLivingSubjectIdList(e, oidPersonIdentifier); for (Element id : idList) { Element v = (Element) id.getElementsByTagName("value").item(0); // unpackGetLivingSubjectIdList() guarantees this exists. PersonIdentifier pi = new PersonIdentifier(); pi.setIdentifier(unpackAttribute(v, "extension")); pi.setIdentifierType(type); if (p.getPersonIdentifierList() == null) { p.setPersonIdentifierList(new ArrayList<PersonIdentifier>()); } p.getPersonIdentifierList().add(pi); } } /** * Unpacks all ID-tagged fingerprints of a given type. * <p> * Searches through all the person identifiers in a <code>Document</code> subtree * to find identifiers of the given type. For each such identifier, allocates * a <code>PersonIdentifier</code> object and attaches it to the <code>Person</code> object. * <p> * Note that we don't really expect multiple fingerprints of the same type in the * same message. But the list of fingerprints in the <code>Person</code> object * allows for this possibility, as does the XML message template. So this * method also allows for this possibility. * * @param p person information * @param e head of the <code>Document</code> subtree in which * these fingerprints are to be found * @param oidFingerprint the XML template OID for this fingerprint type * @param type fingerprint type */ private void unpackFingerprints(Person p, Element e, String oidFingerprint, Fingerprint.Type type) { List<Element> idList = unpackGetIdList(e, oidFingerprint); for (Element id : idList) { Fingerprint f = new Fingerprint(); f.setTemplate(unpackByteArray(unpackAttribute(id, "extension"))); f.setFingerprintType(type); if (p.getFingerprintList() == null) { p.setFingerprintList(new ArrayList<Fingerprint>()); } p.getFingerprintList().add(f); } } /** * Unpacks all LivingSubjectID-tagged fingerprints of a given type. * <p> * Searches through all the person identifiers in a <code>Document</code> subtree * to find identifiers of the given type. For each such identifier, allocates * a <code>PersonIdentifier</code> object and attaches it to the <code>Person</code> object. * <p> * Note that we don't really expect multiple fingerprints of the same type in the * same message. But the list of fingerprints in the <code>Person</code> object * allows for this possibility, as does the XML message template. So this * method also allows for this possibility. * * @param p person information * @param e head of the <code>Document</code> subtree in which * these fingerprints are to be found * @param oidFingerprint the XML template OID for this fingerprint type * @param type fingerprint type */ private void unpackLivingSubjectFingerprints(Person p, Element e, String oidFingerprint, Fingerprint.Type type) { List<Element> idList = unpackGetLivingSubjectIdList(e, oidFingerprint); for (Element id : idList) { Element v = (Element) id.getElementsByTagName("value").item(0); // unpackGetLivingSubjectIdList() guarantees this exists. Fingerprint f = new Fingerprint(); f.setTemplate(unpackByteArray(unpackAttribute(v, "extension"))); f.setFingerprintType(type); if (p.getFingerprintList() == null) { p.setFingerprintList(new ArrayList<Fingerprint>()); } p.getFingerprintList().add(f); } } /** * Finds a list of <id> elements with a given "root" attribute value. * * @param subtree head of the subtree in which to search * @param name root attribute value to search for * @return the list of elements (empty list if none are found) */ private List<Element> unpackGetIdList(Element subtree, String name) { NodeList idList = subtree.getElementsByTagName("id"); List<Element> returnList = new ArrayList<Element>(); for (int i = 0; i < idList.getLength(); i++) { Element id = (Element) idList.item(i); Node aRoot = id.getAttributeNode("root"); if (aRoot != null && aRoot.getNodeValue().equals(name)) { returnList.add(id); } } return returnList; } /** * Finds a list of <LivingSubjectId> elements whose value element * has a given "root" attribute value. * * @param subtree head of the subtree in which to search * @param name root attribute value to search for * @return the list of elements (empty list if none are found) */ private List<Element> unpackGetLivingSubjectIdList(Element subtree, String name) { NodeList idList = subtree.getElementsByTagName("livingSubjectId"); List<Element> returnList = new ArrayList<Element>(); for (int i = 0; i < idList.getLength(); i++) { Element id = (Element) idList.item(i); Element v = (Element) id.getElementsByTagName("value").item(0); if (v != null) { Node aRoot = v.getAttributeNode("root"); if (aRoot != null && aRoot.getNodeValue().equals(name)) { returnList.add(id); } } } return returnList; } /** * Unpacks an element's attribute value, by element tag. * <pre> * {@code * <subtree> * .... * <(tag) (attribute)=(value)/> * .... * </subtree> * } </pre> * * @param subtree Document subtree in which to look for the element * @param name name of the element from which to unpack the value * @param attribute name of the element attribute containing the value * @return the attribute value. If the element was not found, returns null. */ private String unpackTagAttribute(Element subtree, String name, String attribute) { Element e = (Element) subtree.getElementsByTagName(name).item(0); if (e != null) { return unpackAttribute(e, attribute); } else { return null; } } /** * Unpacks an attribute of a block's value element. * <pre> * {@code * <subtree> * .... * <(tag)> * ... * <value (attribute)=(value)/> * ... * </(tag)> * .... * </subtree> * } </pre> * * @param subtree Document subtree in which to look for the block * @param tag name of the block from which to unpack the value * @param attribute name of the element attribute containing the value * @return the attribute value. If the element was not found, returns null. */ private String unpackTagValueAttribute(Element subtree, String tag, String attribute) { Element e = (Element) subtree.getElementsByTagName(tag).item(0); if (e != null) { Element v = (Element) e.getElementsByTagName("value").item(0); if (v != null) { return unpackAttribute(v, attribute); } } return null; } /** * Unpacks an element's attribute value: * <pre> * {@code * <tag (attribute)=(value)/> * } </pre> * * @param e element from which to unpack the attribute * @param attribute name of the attribute in which to find the value * @return value of the attribute, or null if the attribute was not present. * */ private String unpackAttribute(Element e, String attribute) { Node attr = e.getAttributeNode(attribute); if (attr != null) { return attr.getNodeValue(); } else { return null; } } /** * Unpacks data from an element value. * <pre> * {@code * <subtree> * .... * <(tag)> * (value) * </(tag)> * .... * </subtree> * } </pre> * * @param subtree Document subtree in which to look for the element * @param tag name of the element from which to unpack the value * @return value value of the element. If the element was not found, * returns null. */ private String unpackTagValue(Element subtree, String tag) { Element e = (Element) subtree.getElementsByTagName(tag).item(0); if (e != null) { return e.getTextContent(); } else { return null; } } /** * Unpacks the extension value of an id tag, by OID. * <pre> * {@code * <subtree> * .... * <id root=(oid) extension=(value)> * .... * </subtree> * } </pre> * <p> * * @param subtree head of subtree within which to look for the id element. * @param oid the root attribute value for the id element we are looking for. * @return value of the extension attribute, or null if tag not found. */ private String unpackId(Element subtree, String oid) { Element id = commonGetId(subtree, oid); if (id != null) { Node aExtension = id.getAttributeNode("extension"); if (aExtension != null) { return aExtension.getNodeValue(); } } return null; } /** * Unpacks a livingSubjectId subtree. * Searches for a block matching the passed (oid) value: * <pre> * {@code * <subtree> * .... * <livingSubjectId> * <value root="(oid)" extension="(value)"> * <semanticsText>LivingSubject.xxx</semanticsText> * </livingSubjectId> * .... * </subtree> * } *</pre> * Returns the extension value if found, or null if not. * * @param subtree head of subtree within which to look for the livingSubjectId element. * @param oid the root attribute value for the id element we are looking for. * @return value of the extension attribute, or null if tag not found. */ private String unpackLivingSubjectId(Element subtree, String oid) { Element id = commonGetLivingSubjectId(subtree, oid); if (id != null) { Element v = (Element) id.getElementsByTagName("value").item(0); if (v != null) { Node aExtension = v.getAttributeNode("extension"); if (aExtension != null) { return aExtension.getNodeValue(); } } } return null; } /** * Unpacks a Log Entry <code>Document</code> into message data. * Uses LogEntry message type. * * @param m the message contents to fill in * @param e root of the person message <code>Document</code> parsed from XML */ private void unpackLogEntryMessage(Message m, Element e) { LogEntry logEntry = new LogEntry(); m.setMessageData(logEntry); logEntry.setSeverity(e.getElementsByTagName("severity").item(0).getTextContent()); logEntry.setClassName(e.getElementsByTagName("class").item(0).getTextContent()); logEntry.setDateTime(unpackDateTime(e.getElementsByTagName("dateTime").item(0).getTextContent())); logEntry.setMessage(e.getElementsByTagName("message").item(0).getTextContent()); logEntry.setInstance(e.getElementsByTagName("sourceAddress").item(0).getTextContent()); } /** * Unpacks a Work message <code>Document</code> into message data. * Uses Work message type. * * @param m the message contents to fill in * @param e root of the person message <code>Document</code> parsed from XML */ private void unpackWorkMessage(Message m, Element e) { Work work = new Work(); m.setMessageData(work); work.setSourceAddress(e.getElementsByTagName("sourceAddress").item(0).getTextContent()); work.setNotificationId(e.getElementsByTagName("notificationId").item(0).getTextContent()); work.setReassignAddress(e.getElementsByTagName("reassignAddress").item(0).getTextContent()); } /** * Unpacks a hexadecimal-encoded string into a binary byte array. * * @param hex the hexadecimal string to unpack * @return the resulting binary byte array. * Returns null if the hex string was null. */ private byte[] unpackByteArray(String hex) { byte[] bytes = null; if (hex != null) { bytes = new byte[hex.length() / 2]; for (int i = 0; i < hex.length(); i += 2) { bytes[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16); } } return bytes; } /** * Given a string and a set of enumerated values, find which enumerated * member has a name matching the string. * * @param values the set of enumerated values in which to search * @param text text to match against the value names * @return the enumerated value if there was a match, otherwise null */ private Enum unpackEnum(Enum[] values, String text) { for (Enum e : values) { if (e.name().equalsIgnoreCase(text)) { return e; } } return null; } /** * Parse a string to a integer value. If the string is null, return zero. * * @param text text to parse into an integer. * @return the integer value if could be parsed, otherwise zero. */ private int unpackInt(String text) { int returnInt = 0; if (text != null) { try { returnInt = Integer.parseInt(text); } catch (NumberFormatException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.WARNING, "Can't parse integer '" + text + "'", ex); } } return returnInt; } /** * Parse a string to a boolean value. If the string is null, return false. * * @param text text to parse into a boolean. * @return the boolean value if is true, otherwise return false */ private boolean unpackBoolean(String text) { return Boolean.parseBoolean(text); } /** * Unpacks a <code>String</code> date into a <code>Date</code> * * @param sDate contains the date in <code>String</code> format * @return the date in <code>Date</code> format. * Returns null if the date string was null. */ private Date unpackDate(String sDate) { Date returnDate = null; if (sDate != null) { try { returnDate = SIMPLE_DATE_FORMAT.parse(sDate); } catch (ParseException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } } return returnDate; } /** * Unpacks a <code>String</code> date and time into a <code>Date</code> * * @param sDateTime contains date and time * @return the date and time in <code>Date</code> format * Returns null if the date and time string was null. */ private Date unpackDateTime(String sDateTime) { Date returnDateTime = null; if (sDateTime != null) { try { returnDateTime = SIMPLE_DATE_TIME_FORMAT.parse(sDateTime); } catch (ParseException ex) { Logger.getLogger(XmlPacker.class.getName()).log(Level.SEVERE, null, ex); } } return returnDateTime; } /* * --------------------------------------------------------------------------------------- * * C O M M O N M E T H O D S * * --------------------------------------------------------------------------------------- */ /** * Finds an id element with a given root OID attribute value, * or <code>null</code> if not found. * <pre> * {@code * <id root=(oid) ...> * } </pre> * * @param subtree head of the subtree in which to search * @param oid root attribute value to search for * @return the element if found, otherwise null */ private Element commonGetId(Element subtree, String oid) { // Coding note: In the current DOM implementation, the NodeList.getLength() // method is a relatively costly way to loop, if the loop may be terminated // before all the nodes are accessed. This is because getLength() // causes the internal code to loop through all the nodes just to return // the count that exist as the getLength() result. // // Instead, it is more efficient to loop through the nodes // in a NodeList to find the one we are looking for, or until we reach // a null value signifying the end of the list. This saves time if we find // the node we are looking for before we reach the end of the list. NodeList idList = subtree.getElementsByTagName("id"); Element id; for (int i = 0; (id = (Element)idList.item(i)) != null; i++) { Node aRoot = id.getAttributeNode("root"); if (aRoot != null && aRoot.getNodeValue().equals(oid)) { return id; } } return null; } /** * Finds a livingSubjectId element matching a root attribute OID value. * <pre> * {@code * <livingSubjectId> * <value root="(oid)" ...> * <semanticsText>LivingSubject.xxx</semanticsText> * </livingSubjectId> * } </pre> * * @param subtree head of the subtree in which to search * @param oid root attribute value to search for * @return the element if found, otherwise null */ private Element commonGetLivingSubjectId(Element subtree, String oid) { NodeList idList = subtree.getElementsByTagName("livingSubjectId"); Element id; for (int i = 0; (id = (Element)idList.item(i)) != null; i++) { Element eVal = (Element) id.getElementsByTagName("value").item(0); if (eVal != null) { Node aRoot = eVal.getAttributeNode("root"); if (aRoot != null && aRoot.getNodeValue().equals(oid)) { return id; } } } return null; } }