Java tutorial
/* * This code is subject to the HIEOS License, Version 1.0 * * Copyright(c) 2008-2009 Vangent, Inc. All rights reserved. * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package com.vangent.hieos.services.xds.registry.transactions; import com.vangent.hieos.services.xds.registry.storedquery.PatientIdentityFeedRegistryStoredQuerySupport; import com.vangent.hieos.xutil.db.support.SQLConnectionWrapper; import com.vangent.hieos.xutil.services.framework.XBaseTransaction; import com.vangent.hieos.xutil.metadata.structure.MetadataSupport; import com.vangent.hieos.xutil.xlog.client.XLogMessage; import com.vangent.hieos.adt.db.AdtRecordBean; import com.vangent.hieos.adt.db.AdtJdbcConnection; import com.vangent.hieos.services.xds.registry.backend.BackendRegistry; import com.vangent.hieos.xutil.atna.ATNAAuditEvent; import com.vangent.hieos.xutil.atna.ATNAAuditEventPatientIdentityFeed; import com.vangent.hieos.xutil.atna.ATNAAuditEventPatientIdentityFeed.EventActionCode; import com.vangent.hieos.xutil.hl7.date.Hl7Date; import com.vangent.hieos.xutil.exception.ExceptionUtil; // ATNA. import com.vangent.hieos.xutil.atna.XATNALogger; // Third-party. import com.vangent.hieos.xutil.exception.XPathHelperException; import javax.xml.namespace.QName; import org.apache.log4j.Logger; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.OMAbstractFactory; import org.apache.axiom.om.OMFactory; import org.apache.axiom.om.OMNamespace; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.UUID; import org.apache.commons.codec.binary.Base64; // Exceptions. import com.vangent.hieos.xutil.exception.XdsInternalException; import com.vangent.hieos.xutil.xconfig.XConfigActor; import com.vangent.hieos.xutil.xml.XPathHelper; import java.sql.SQLException; /** * * @author Bernie Thuman */ public class RegistryPatientIdentityFeed extends XBaseTransaction { private final static Logger logger = Logger.getLogger(RegistryPatientIdentityFeed.class); // Type type of message received. public enum MessageType { PatientRegistryRecordAdded, PatientRegistryRecordUpdated, PatientRegistryDuplicatesResolved, PatientRegistryRecordUnmerged }; // XPath expressions: private final static String XPATH_PATIENT = "//*/ns:controlActProcess/ns:subject/ns:registrationEvent/ns:subject1/ns:patient[1]"; private final static String XPATH_PRIOR_REGISTRATION_PATIENT_ID = "//*/ns:controlActProcess/ns:subject/ns:registrationEvent/ns:replacementOf/ns:priorRegistration/ns:subject1/ns:priorRegisteredRole/ns:id[1]"; private final static String XPATH_PRIOR_REGISTRATION_PATIENT_ALL_IDS = "//*/ns:controlActProcess/ns:subject/ns:registrationEvent/ns:replacementOf/ns:priorRegistration/ns:subject1/ns:priorRegisteredRole/ns:id"; private boolean errorDetected = false; private String _patientId = "NOT ON REQUEST"; private AdtJdbcConnection _adtConn = null; /** * * @param log_message */ public RegistryPatientIdentityFeed(XLogMessage log_message) { this.log_message = log_message; } /** * Processs Patient ID Feeds for HL7 v3 messages * * @param request * @param messageType * @return */ public OMElement run(OMElement request, MessageType messageType) { OMElement result = null; Exception ex = null; EventActionCode eventActionCode = EventActionCode.CREATE; try { _adtConn = this.adtGetDatabaseConnection(); // Get ADT connection. switch (messageType) { case PatientRegistryRecordAdded: eventActionCode = EventActionCode.CREATE; this.processPatientRegistryRecordAdded(request); break; case PatientRegistryRecordUpdated: eventActionCode = EventActionCode.UPDATE; this.processPatientRegistryRecordUpdated(request); break; case PatientRegistryDuplicatesResolved: eventActionCode = EventActionCode.UPDATE; // FIXME: Need to fixup how ATNA is handled; ATNA delete not generated for // subsumed patient id. this.processPatientRegistyDuplicatesResolved(request); break; case PatientRegistryRecordUnmerged: eventActionCode = EventActionCode.UPDATE; this.processPatientRegistryRecordUnmerged(request); break; } //this.logResponse(result, !this.errorDetected /* success */); } catch (PatientIdentityFeedException feedException) { ex = feedException; } catch (XdsInternalException internalException) { ex = internalException; } catch (Exception e) { ex = e; this.logException(e.getMessage()); // Some lower level exception. } finally { if (_adtConn != null) { _adtConn.closeConnection(); } } // Generate the response. result = this.generateACK(request, (ex != null) ? ex.getMessage() : null); this.logResponse(result, !this.errorDetected /* success */); // ATNA log (Start) OMElement idNode = this.getFirstChildWithName(request, "id"); String messageId = (idNode != null) ? idNode.getAttributeValue(new QName("root")) : "UNKNOWN"; this.auditPatientIdentityFeed(ATNAAuditEvent.IHETransaction.ITI44, this._patientId, (messageId != null) ? messageId : "UNKNOWN", eventActionCode, this.errorDetected ? ATNAAuditEvent.OutcomeIndicator.MINOR_FAILURE : ATNAAuditEvent.OutcomeIndicator.SUCCESS, null /* sourceIdentity */, null /* sourceIP */); // ATNA log (Stop) return result; } /** * Process Patient ID Feeds for HL7 v2 messages * @param request * @param messageType * @return */ public OMElement run_Simple(OMElement patientFeedRequest) { OMElement result = null; Exception ex = null; if (log_message.isLogEnabled()) { // Log the raw message. OMElement rawMessageNode = this.getFirstChildWithName(patientFeedRequest, "RawMessage"); if (rawMessageNode != null) { byte[] base64 = Base64.decodeBase64(rawMessageNode.getText().getBytes()); this.logInfo("Raw Message", new String(base64)); } OMElement sourceIPNode = this.getFirstChildWithName(patientFeedRequest, "SourceIPAddress"); String sourceIP = sourceIPNode.getText(); this.logInfo("Source IP", sourceIP); } // First determine what kind of request we have. OMElement actionNode = this.getFirstChildWithName(patientFeedRequest, "Action"); String action = actionNode.getText().toUpperCase(); if (log_message.isLogEnabled()) { String logServiceText = log_message.getTestMessage() + "-" + action; log_message.setTestMessage(logServiceText); } ATNAAuditEventPatientIdentityFeed.EventActionCode eventActionCode = EventActionCode.CREATE; try { _adtConn = this.adtGetDatabaseConnection(); // Get ADT connection. if (action.equals("ADD")) { eventActionCode = EventActionCode.CREATE; this.processPatientRegistryRecordAdded_Simple(patientFeedRequest); } else if (action.equals("MERGE")) { // FIXME: Need to fixup how ATNA is handled; ATNA delete not generated for // subsumed patient id. eventActionCode = EventActionCode.UPDATE; this.processPatientRegistyDuplicatesResolved_Simple(patientFeedRequest); } else { throw new PatientIdentityFeedException("Action not known"); } } catch (PatientIdentityFeedException feedException) { ex = feedException; } catch (XdsInternalException internalException) { ex = internalException; } catch (Exception e) { ex = e; this.logException(e.getMessage()); // Some lower level exception. } finally { if (_adtConn != null) { _adtConn.closeConnection(); } } /* FIXME */ result = patientFeedRequest; this.logResponse(null /* response */, !this.errorDetected /* success */); // Generate the response. result = this.createPatientFeedResponse_Simple((ex != null) ? ex.getMessage() : null); this.logResponse(result, !this.errorDetected /* success */); // ATNA log (Start) String messageId = this.getPatientFeedRequestNodeText(patientFeedRequest, "MessageControlId"); this.auditPatientIdentityFeed(ATNAAuditEvent.IHETransaction.ITI8, this._patientId, (messageId != null) ? messageId : "UNKNOWN", eventActionCode, this.errorDetected ? ATNAAuditEvent.OutcomeIndicator.MINOR_FAILURE : ATNAAuditEvent.OutcomeIndicator.SUCCESS, this.getPatientFeedRequestNodeText(patientFeedRequest, "SourceIdentity") /* sourceIdentity */, this.getPatientFeedRequestNodeText(patientFeedRequest, "SourceIPAddress") /* sourceIP */); // ATNA log (Stop) return result; } /** * * @param PRPA_IN201301UV02_Message * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void processPatientRegistryRecordAdded(OMElement PRPA_IN201301UV02_Message) throws PatientIdentityFeedException, XdsInternalException { // Pull out the patient from the request. OMElement patientNode = this.selectSingleNode(PRPA_IN201301UV02_Message, RegistryPatientIdentityFeed.XPATH_PATIENT); OMElement idNode = this.getFirstChildWithName(patientNode, "id"); if (idNode != null) { this._patientId = this.getPatientIdFromIIType(idNode); this.logInfo("Patient ID", this._patientId); // First see if the patient id already exists. if (!this.adtPatientExists(this._patientId)) { this.adtAddPatientId(this._patientId); } else { // Patient Id already exists (ignore request). throw this.logException("Patient ID " + this._patientId + " already exists - skipping ADD!"); } } else { throw this.logException("No patient id found on request"); } } /** * * @param patientFeedRequest * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void processPatientRegistryRecordAdded_Simple(OMElement patientFeedRequest) throws PatientIdentityFeedException, XdsInternalException { // Pull out the patient from the request. String id = this.getPatientFeedRequestNodeText(patientFeedRequest, "PatientId"); if (id != null) { this._patientId = id; this.logInfo("Patient ID", this._patientId); // First see if the patient id already exists. if (!this.adtPatientExists(this._patientId)) { this.adtAddPatientId(this._patientId); } else { // Patient Id already exists (ignore request). throw this.logException("Patient ID " + this._patientId + " already exists - skipping ADD!"); } } else { throw this.logException("No patient id found on request"); } } /** * * @param PRPA_IN201302UV02_Message * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void processPatientRegistryRecordUpdated(OMElement PRPA_IN201302UV02_Message) throws PatientIdentityFeedException, XdsInternalException { // Pull out the patient from the request. OMElement patientNode = this.selectSingleNode(PRPA_IN201302UV02_Message, RegistryPatientIdentityFeed.XPATH_PATIENT); OMElement idNode = this.getFirstChildWithName(patientNode, "id"); if (idNode != null) { this._patientId = this.getPatientIdFromIIType(idNode); this.logInfo("Patient ID", this._patientId); // First see if the patient id exists and is active. if (this.adtDoesActivePatientExist(this._patientId)) { // BHT - Turn into NO-OP (should not support UPDATE). // this.adtAddPatientId(patientNode, this._patientId, true /* updateMode */); } else { // Patient Id does not exist or is not active (ignore request). throw this.logException( "Patient ID " + this._patientId + " does not exist or is not active - skipping UPDATE!"); } } else { throw this.logException("No patient id found on request"); } } /** * * @param PRPA_IN201304UV02_Message * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void processPatientRegistyDuplicatesResolved(OMElement PRPA_IN201304UV02_Message) throws PatientIdentityFeedException, XdsInternalException { OMElement patientNode = this.selectSingleNode(PRPA_IN201304UV02_Message, RegistryPatientIdentityFeed.XPATH_PATIENT); OMElement idNode = this.getFirstChildWithName(patientNode, "id"); String survivingPatientId = this.getPatientIdFromIIType(idNode); // Get the patient Id that will be subsumed (this is the duplicate). String priorRegistrationPatientId = this.getPriorRegistrationPatientId(PRPA_IN201304UV02_Message); this.doMerge(survivingPatientId, priorRegistrationPatientId); } /** * * @param patientFeedRequest * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void processPatientRegistyDuplicatesResolved_Simple(OMElement patientFeedRequest) throws PatientIdentityFeedException, XdsInternalException { String survivingPatientId = this.getPatientFeedRequestNodeText(patientFeedRequest, "PatientId"); // Get the patient Id that will be subsumed (this is the duplicate). String priorRegistrationPatientId = this.getPatientFeedRequestNodeText(patientFeedRequest, "MergePatientId"); this.doMerge(survivingPatientId, priorRegistrationPatientId); } /** * * @param PRPA_IN201304UV02UNMERGE_Message * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void processPatientRegistryRecordUnmerged(OMElement PRPA_IN201304UV02UNMERGE_Message) throws PatientIdentityFeedException, XdsInternalException { // Get the Active patient Id OMElement patientNode = this.selectSingleNode(PRPA_IN201304UV02UNMERGE_Message, RegistryPatientIdentityFeed.XPATH_PATIENT); OMElement idNode = this.getFirstChildWithName(patientNode, "id"); String survivingPatientId = this.getPatientIdFromIIType(idNode); // Get the patient Id that will be unmerged. String priorRegistrationPatientId = this.getPriorRegistrationPatientId(PRPA_IN201304UV02UNMERGE_Message); this.doUnmerge(PRPA_IN201304UV02UNMERGE_Message, survivingPatientId, priorRegistrationPatientId); } /** * * @param survivingPatientId * @param priorRegistrationPatientId */ private void doMerge(String survivingPatientId, String priorRegistrationPatientId) throws PatientIdentityFeedException, XdsInternalException { // Check existance of surviving patient id on request. if (survivingPatientId == null) { throw this.logException("Surviving Patient ID not present on request- skipping MERGE!"); } this._patientId = survivingPatientId; this.logInfo("Patient ID (Surviving)", survivingPatientId); // Check existance of priorRegistration on request. if (priorRegistrationPatientId == null) { throw this.logException( "Prior Registration (to be subsumed) Patient ID not present on request - skipping MERGE!"); } this.logInfo("Prior Registration Patient ID (Subsumed)", priorRegistrationPatientId); // See if they are the same patient (if so, skip merge). if (survivingPatientId.equals(priorRegistrationPatientId)) { throw this.logException( "Surviving Patient ID and Prior Registration (to be subsumed) ID are the same - skipping MERGE!"); } // Check that both the Surviving and to be Subsumed Patients both exist and are Active if (!this.adtDoesActivePatientExist(survivingPatientId)) { throw this.logException("Surviving Patient ID " + survivingPatientId + " is not active or is not known to registry - skipping MERGE!"); } if (!this.adtDoesActivePatientExist(priorRegistrationPatientId)) { throw this.logException("Prior Registration (to be subsumed) Patient ID " + priorRegistrationPatientId + " is not active or is not known to registry - skipping MERGE!"); } // Now, we can do some updates. // TODO: updates are in 2 databases may need 2 phase commit*** // First get a list of all external identifier ids that will be affected by the merge List externalIdentifierIds = regGetExternalIdentifierIDs(priorRegistrationPatientId); logger.debug("Number of Objects being merged: " + externalIdentifierIds.size()); // Take care of the registry by updating the patient id on the external identifiers. this.regUpdateExternalIdentifiers(survivingPatientId, priorRegistrationPatientId); // Create a history of the Merge this.adtCreateMergeHistory(survivingPatientId, priorRegistrationPatientId, "M", externalIdentifierIds); // Disable the ADT record for the priorRegistrationPatientId. this.adtUpdatePatientStatus(priorRegistrationPatientId, "I"); } /** * * @param PRPA_IN201304UV02UNMERGE_Message * @param survivingPatientId * @param unmergedPatientId * @throws com.vangent.hieos.services.xds.registry.transactions.RegistryPatientIdentityFeed.PatientIdentityFeedException * @throws XdsInternalException */ private void doUnmerge(OMElement PRPA_IN201304UV02UNMERGE_Message, String survivingPatientId, String unmergedPatientId) throws PatientIdentityFeedException, XdsInternalException { // Check existance of active patient id on request. if (survivingPatientId == null) { throw this.logException("Active Patient ID not present on request- skipping UNMERGE!"); } this._patientId = survivingPatientId; this.logInfo("Patient ID (Active) ", survivingPatientId); // Check existance of the patient being unmerged on request. if (unmergedPatientId == null) { throw this.logException( "Prior Registration (to be unmerged) Patient ID not present on request - skipping UNMERGE!"); } this.logInfo("Prior Registration (to be unmerged or split) Patient ID ", unmergedPatientId); // See if they are the same patient (if so, skip unmerge). if (survivingPatientId.equals(unmergedPatientId)) { throw this.logException( "Active Patient ID and Patient being Unmerged ID are the same - skipping UNMERGE!"); } // Check that the Active (survived) patient identifier is still active if (!this.adtDoesActivePatientExist(survivingPatientId)) { throw this.logException("Survived (Active) Patient ID " + survivingPatientId + " is not active or is not known to registry - skipping UNMERGE!"); } // Check to see if the patient id to "unmerge" is active. If so, it is an error. String unmergedPatientIdStatus = this.adtGetPatientStatus(unmergedPatientId); if ((unmergedPatientIdStatus != null) && unmergedPatientIdStatus.equals("A")) { throw this.logException( "Patient ID to be unmerged " + unmergedPatientId + " is active - skipping UNMERGE!"); } // Check to see if the patient id to "unmerge" exists at all. if (unmergedPatientIdStatus == null) { // Treat as a "SPLIT" this.doSplit(PRPA_IN201304UV02UNMERGE_Message, survivingPatientId, unmergedPatientId); } else { // Check if there is a record of prior registration (to be unmerged) patient id being merged into the surviving patient id // Also check that this is the most recent merge - only the most recent merge can be unmerged if (!this.adtCheckMergeHistory(survivingPatientId, unmergedPatientId)) { throw this.logException("Invalid UnMerge Request for Surviving Patient ID " + survivingPatientId + " and UnMerged Patient: " + unmergedPatientId + " - skipping UNMERGE!"); } // Patients IDs are valid, now perform the unmerge // Get a list of all external identifier ids that were affected by the merge List externalIdentifierIds = adtRetrieveMergedRecords(survivingPatientId, unmergedPatientId); logger.debug("Number of Objects being unmerged: " + externalIdentifierIds.size()); // Update the registry by updating the patient id on the external // identifiers involved in the previous merge. this.regUpdateExternalIdentifiers(survivingPatientId, unmergedPatientId, externalIdentifierIds); // Create a history of the unmerge this.adtCreateMergeHistory(survivingPatientId, unmergedPatientId, "U", externalIdentifierIds); // Activate the ADT Patient record for the unmerged Patient. this.adtUpdatePatientStatus(unmergedPatientId, "A"); } } /** * Perform a SPLIT operation. All records designated in the PriorRegistration * will be moved to the new patient id. * * @param PRPA_IN201304UV02UNMERGE_Message The received UNMERGE message. * @param activePatientId The active patient identifier (fully qualified). * @param newPatientId The new patient identifier (fully qualified). * @throws com.vangent.hieos.services.xds.registry.transactions.RegistryPatientIdentityFeed.PatientIdentityFeedException * @throws XdsInternalException */ private void doSplit(OMElement PRPA_IN201304UV02UNMERGE_Message, String activePatientId, String newPatientId) throws PatientIdentityFeedException, XdsInternalException { this.logInfo("SPLIT (New Patient)", newPatientId); // Add new patient ID to ADT. this.adtAddPatientId(newPatientId); // Get list of all prior registration id nodes. List priorRegistrationIdNodes = this.getPriorRegistrationPatientIdNodes(PRPA_IN201304UV02UNMERGE_Message); int priorRegistrationIdNodesSize = priorRegistrationIdNodes.size(); // Now SPLIT out records (only from document sources (in root of ids on prior registration) // on the request). // Prepare list of external identifier ids (uuids). List<String> externalIdentifierIds = new ArrayList<String>(); if (priorRegistrationIdNodesSize > 1) { // Get all document source ids that are subject to the split. List<String> documentSourceIds = new ArrayList<String>(); for (int i = 1; i < priorRegistrationIdNodesSize; i++) { OMElement idNode = (OMElement) priorRegistrationIdNodes.get(i); //String extension = idNode.getAttributeValue(new QName("extension")); String documentSourceId = idNode.getAttributeValue(new QName("root")); // Add selected document source ids to the list (skip any dups). if (!documentSourceIds.contains(documentSourceId)) { documentSourceIds.add(documentSourceId); } } // Update the registry by updating the patient id on the external // identifiers involved in the split. if (documentSourceIds.size() > 0) { externalIdentifierIds = this.getExternalIdentifiersToSplitOut(activePatientId, documentSourceIds); if (externalIdentifierIds.size() > 0) { // Found some records that are subject to the split. this.regUpdateExternalIdentifiers(activePatientId, newPatientId, externalIdentifierIds); } } } // Create a history of the SPLIT (always record the SPLIT even though records // may not have been moved to the new patient id). this.adtCreateMergeHistory(activePatientId, newPatientId, "S", externalIdentifierIds); } // Helper methods: /** * Get all external identifier UUIDs related to the supplied "activePatientId" * and list of document source identifiers. * * @param activePatientId Fully qualified patient identifier. * @param documentSourceIds List of document source identifiers. * @return List of UUIDs for ExternalIdentifiers found in Registry. */ private List<String> getExternalIdentifiersToSplitOut(String activePatientId, List documentSourceIds) throws PatientIdentityFeedException { BackendRegistry backendRegistry = new BackendRegistry(log_message); PatientIdentityFeedRegistryStoredQuerySupport sq = new PatientIdentityFeedRegistryStoredQuerySupport(null, log_message, backendRegistry); List<String> externalIdentifierIds = new ArrayList<String>(); try { externalIdentifierIds = sq.getExternalIdentifiersToSplitOut(activePatientId, documentSourceIds); } catch (XdsInternalException ex) { throw this.logException(ex.getMessage()); } finally { try { // No exception - simply commit (which will release the connection). backendRegistry.commit(); } catch (XdsInternalException ex) { throw new PatientIdentityFeedException(ex.getMessage()); } } return externalIdentifierIds; } /** * * @param patientFeedRequest * @param name * @return */ String getPatientFeedRequestNodeText(OMElement patientFeedRequest, String name) { String result = null; OMElement node = this.getFirstChildWithName(patientFeedRequest, name); if (node != null) { result = node.getText(); } return result; } /** * * @param idNode * @return */ String getPatientIdFromIIType(OMElement idNode) { if (idNode == null) { return null; // GUARD: Early exit. } String extension = idNode.getAttributeValue(new QName("extension")); String root = idNode.getAttributeValue(new QName("root")); String patientId = this.formattedPatientId(root, extension); // DEBUG (Start) logger.debug(" extension = " + extension); logger.debug(" root = " + root); logger.debug("*** Patient ID = " + patientId); // DEBUG (Stop) return patientId; } /** * * @param rootNode * @return */ String getPriorRegistrationPatientId(OMElement rootNode) { OMElement idNode = this.selectSingleNode(rootNode, RegistryPatientIdentityFeed.XPATH_PRIOR_REGISTRATION_PATIENT_ID); String patientId = null; if (idNode != null) { patientId = this.getPatientIdFromIIType(idNode); } return patientId; } /** * * @param rootNode * @return */ private List getPriorRegistrationPatientIdNodes(OMElement rootNode) { return this.selectNodes(rootNode, RegistryPatientIdentityFeed.XPATH_PRIOR_REGISTRATION_PATIENT_ALL_IDS); } /** * NOTE (BHT): Obviously, this routine is long and leborious. Should change over to a light-weight * HL7 v3 Java Library (JAXB generated code may be an option, but on first attempt, code generated * was massive. * * @param request * @return */ private OMElement generateACK(OMElement request, String errorString) { OMFactory omfactory = OMAbstractFactory.getOMFactory(); OMNamespace ns = omfactory.createOMNamespace("urn:hl7-org:v3", "ns"); // MCCI_IN000002UV01: OMElement ackResponseNode = omfactory.createOMElement("MCCI_IN000002UV01", ns); ackResponseNode.addAttribute("ITSVersion", "XML_1.0", null); // /MCCI_IN000002UV01/id: OMElement idNode = omfactory.createOMElement("id", ns); ackResponseNode.addChild(idNode); idNode.addAttribute("root", this.getUUID(), null); // /MCCI_IN000002UV01/creationTime: OMElement creationTimeNode = omfactory.createOMElement("creationTime", ns); ackResponseNode.addChild(creationTimeNode); creationTimeNode.addAttribute("value", this.getHL7Date(), null); /* Transmission Wrapper */ // /MCCI_IN000002UV01/versionCode (OK): OMElement versionCodeNode = omfactory.createOMElement("versionCode", ns); ackResponseNode.addChild(versionCodeNode); versionCodeNode.addAttribute("code", "V3PR1", null); // Denotes HL7v3. // /MCCI_IN000002UV01/interactionId (?): OMElement interactionIdNode = omfactory.createOMElement("interactionId", ns); ackResponseNode.addChild(interactionIdNode); interactionIdNode.addAttribute("displayable", "true", null); interactionIdNode.addAttribute("extension", "MCCI_IN000002UV01", null); interactionIdNode.addAttribute("root", "2.16.840.1.113883", null); // Denotes an HL7v3 interaction. // /MCCI_IN000002UV01/processingCode (?): OMElement processingCodeNode = omfactory.createOMElement("processingCode", ns); ackResponseNode.addChild(processingCodeNode); processingCodeNode.addAttribute("code", "P", null); // ????? Should value be "T"? // /MCCI_IN000002UV01/processingModeCode (OK): OMElement processingModeCodeNode = omfactory.createOMElement("processingModeCode", ns); ackResponseNode.addChild(processingModeCodeNode); processingModeCodeNode.addAttribute("code", "T", null); // //MCCI_IN000002UV01/acceptAckCode (OK): OMElement acceptAckCodeNode = omfactory.createOMElement("acceptAckCode", ns); ackResponseNode.addChild(acceptAckCodeNode); acceptAckCodeNode.addAttribute("code", "NE", null); // Build "Receiver" (Patient Identity Source): OMElement senderOnRequestNode = MetadataSupport.firstChildWithLocalName(request, "sender"); OMElement senderDeviceOnRequestNode = MetadataSupport.firstChildWithLocalName(senderOnRequestNode, "device"); // /MCCI_IN000002UV01/receiver OMElement receiverNode = omfactory.createOMElement("receiver", ns); ackResponseNode.addChild(receiverNode); receiverNode.addAttribute("typeCode", "RCV", null); // /MCCI_IN000002UV01/receiver/device receiverNode.addChild(senderDeviceOnRequestNode.cloneOMElement()); // Sender is now receiver in ACK. // Build "Sender" (the Registry): // /MCCI_IN000002UV01/sender OMElement senderNode = omfactory.createOMElement("sender", ns); ackResponseNode.addChild(senderNode); senderNode.addAttribute("typeCode", "SND", null); // Add "device" for Sender: // /MCCI_IN000002UV01/sender/device OMElement deviceNode = omfactory.createOMElement("device", ns); senderNode.addChild(deviceNode); deviceNode.addAttribute("classCode", "DEV", null); deviceNode.addAttribute("determinerCode", "INSTANCE", null); // /MCCI_IN000002UV01/sender/device/id idNode = omfactory.createOMElement("id", ns); deviceNode.addChild(idNode); idNode.addAttribute("root", this.getRegistryReceiverDeviceId(), null); // /MCCI_IN000002UV01/sender/device/name OMElement nameNode = omfactory.createOMElement("name", ns); deviceNode.addChild(nameNode); nameNode.setText(this.getRegistryReceiverDeviceName()); // /MCCI_IN000002UV01/acknowledgement: OMElement ackNode = omfactory.createOMElement("acknowledgement", ns); ackResponseNode.addChild(ackNode); // /MCCI_IN000002UV01/acknowledgement/typeCode OMElement typeCodeNode = omfactory.createOMElement("typeCode", ns); ackNode.addChild(typeCodeNode); if (errorString == null) { // Accept Acknoweledgement Commit Accept typeCodeNode.addAttribute("code", "CA", null); } else { // Accept Acknoweledgement Commit Error typeCodeNode.addAttribute("code", "CE", null); } // /MCCI_IN000002UV01/acknowledgement/targetMessage OMElement targetMessageNode = omfactory.createOMElement("targetMessage", ns); ackNode.addChild(targetMessageNode); // /MCCI_IN000002UV01/acknowledgement/targetMessage/id // Need to put in the "id" from the request in the ACK. OMElement idNodeOnRequest = MetadataSupport.firstChildWithLocalName(request, "id"); targetMessageNode.addChild(idNodeOnRequest.cloneOMElement()); // FOR ERROR REPORTING: if (errorString != null) { // /MCCI_IN000002UV01/acknowledgement/acknowledgementDetail OMElement acknowledgementDetail = omfactory.createOMElement("acknowledgementDetail", ns); ackNode.addChild(acknowledgementDetail); // /MCCI_IN000002UV01/acknowledgement/acknowledgementDetail/text OMElement textNode = omfactory.createOMElement("text", ns); acknowledgementDetail.addChild(textNode); textNode.setText(errorString); } return ackResponseNode; } /** * * @param errorString * @return */ private OMElement createPatientFeedResponse_Simple(String errorString) { // <PatientFeedResponse> // <DeviceId>TBD</DeviceId> // <DeviceName>TBD</DeviceName> // <!-- 0 - Pass, 1 - Fail --> // <ResponseCode>0</ResponseCode> // <ResponseText>TBD</ResponseText> // <ErrorText>TBD</ErrorText> // </PatientFeedResponse> OMFactory fact = OMAbstractFactory.getOMFactory(); OMNamespace ns = this.getHIEOS_OMNamespace(fact); OMElement patientFeedResponseNode = fact.createOMElement("PatientFeedResponse", ns); // PatientFeedResponse/DeviceId OMElement deviceIdNode = fact.createOMElement("DeviceId", ns); deviceIdNode.setText(this.getRegistryReceiverDeviceId()); patientFeedResponseNode.addChild(deviceIdNode); // PatientFeedResponse/DeviceName OMElement deviceNameNode = fact.createOMElement("DeviceName", ns); deviceNameNode.setText(this.getRegistryReceiverDeviceName()); patientFeedResponseNode.addChild(deviceNameNode); // PatientFeedResponse/ResponseCode OMElement responseCodeNode = fact.createOMElement("ResponseCode", ns); patientFeedResponseNode.addChild(responseCodeNode); // PatientFeedResponse/ResponseText OMElement responseTextNode = fact.createOMElement("ResponseText", ns); patientFeedResponseNode.addChild(responseTextNode); // No set the text in ResponseText and ErrorText (on error). if (errorString == null) { responseCodeNode.setText("0"); responseTextNode.setText("PASS"); } else { responseCodeNode.setText("1"); responseTextNode.setText("FAIL"); // PatientFeedResponse/ErrorText OMElement errorTextNode = fact.createOMElement("ErrorText", ns); patientFeedResponseNode.addChild(errorTextNode); errorTextNode.setText(errorString); } // Return the response node. return patientFeedResponseNode; } /** * * @param fact * @return */ private OMNamespace getHIEOS_OMNamespace(OMFactory fact) { String hieos_uri = "urn:hieos:1.0"; return fact.createOMNamespace(hieos_uri, "hieos"); } /** * * @return Value of the property, null if not found. */ private String getRegistryReceiverDeviceId() { return this.getXConfigRegistryProperty("ReceiverDeviceId"); } /** * * @return Value of the property, null if not found. */ private String getRegistryReceiverDeviceName() { return this.getXConfigRegistryProperty("ReceiverDeviceName"); } /** * * @param rootNode * @param xpathExpression * @return */ private OMElement selectSingleNode(OMElement rootNode, String xpathExpression) { OMElement resultNode = null; try { resultNode = XPathHelper.selectSingleNode(rootNode, xpathExpression, "urn:hl7-org:v3"); } catch (XPathHelperException e) { this.logInternalException(e, "Problem with xpathExpression: " + xpathExpression); } return resultNode; } /** * * @param rootNode * @param xpathExpression * @return */ private List selectNodes(OMElement rootNode, String xpathExpression) { List<OMElement> resultNodes = null; try { resultNodes = XPathHelper.selectNodes(rootNode, xpathExpression, "urn:hl7-org:v3"); } catch (XPathHelperException e) { this.logInternalException(e, "Problem with xpathExpression: " + xpathExpression); } return resultNodes; } /** * * @param rootNode * @param localName * @return */ private OMElement getFirstChildWithName(OMElement rootNode, String localName) { OMElement resultNode = null; if (rootNode != null) { resultNode = MetadataSupport.firstChildWithLocalName(rootNode, localName); } return resultNode; } /** * * @param domain * @param pid * @return */ private String formattedPatientId(String domain, String pid) { // fd8c812cae1645e^^^&1.3.6.1.4.1.21367.2009.1.2.315&ISO return (pid + "^^^" + this.formattedAssigningAuthority(domain)); } /** * * @param domain * @return */ private String formattedAssigningAuthority(String domain) { //fd8c812cae1645e^^^&1.3.6.1.4.1.21367.2009.1.2.315&ISO return "&" + domain + "&ISO"; } /** * Return a UUID. * * @return The UUID as a String. */ private String getUUID() { return UUID.randomUUID().toString(); //return UuidAllocator.allocate(); /* UUIDFactory factory = UUIDFactory.getInstance(); UUID uuid = factory.newUUID(); return uuid.toString(); */ } // ADT methods (keep here for now, but should move ultimately into a well factored structure. /** * * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private AdtJdbcConnection adtGetDatabaseConnection() throws XdsInternalException { // Open the connection to the ADT database. AdtJdbcConnection conn = null; try { conn = new AdtJdbcConnection(); } catch (Exception e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem getting ADT database connection"); } return conn; } /** * Diasables or Activates a Patient record * @param patientId */ private void adtUpdatePatientStatus(String patientId, String status) throws XdsInternalException { String uuid = this.adtGetPatientUUID(patientId); try { _adtConn.updateAdtRecordStatus(uuid, status); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem updating status for patient ID = " + patientId); } } /** * * @param patientId * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void adtAddPatientId(String patientId) throws XdsInternalException { // Get the ADT record: AdtRecordBean arb; //String uuid; // Are we in "update" mode (i.e. patient id exists already) // We are in "insert mode" (i.e. patient id does not exist) arb = new AdtRecordBean(); //uuid = arb.getPatientUUID(); // This is a new UUID. arb.setPatientStatus("A"); // Set the patient status. arb.setPatientId(patientId); // Set the patient id. // Get the demographic data. /* OMElement patientPersonNode = this.getFirstChildWithName(patientNode, "patientPerson"); if (patientPersonNode == null) { this.logInfo("Note", "Request does not contain <patientPerson>"); // Just keep going since we do have a patient id. } else { // Update patient demographic data. // BHT: NO LONGER STORE DEMOGRAPHICS }*/ // Store (which should have at least the patient id) to the database. this.adtSavePatientRecord(arb); } /** * * @param arb * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void adtSavePatientRecord(AdtRecordBean arb) throws XdsInternalException { try { arb.saveToDatabase(); } catch (Exception e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem saving patient record"); } } /** * * @param patientId * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private boolean adtPatientExists(String patientId) throws XdsInternalException { boolean patientExists = false; try { patientExists = _adtConn.doesIdExist(patientId); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem checking for patient ID existence = " + patientId); } return patientExists; } /** * * @param patientId * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private String adtGetPatientUUID(String patientId) throws XdsInternalException { String uuid = null; try { uuid = _adtConn.getPatientUUID(patientId); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem getting patient UUID for patient ID existence = " + patientId); } return uuid; } /** * * @param patientId * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private boolean adtDoesActivePatientExist(String patientId) throws XdsInternalException { boolean patientActive = false; try { patientActive = _adtConn.doesActiveIdExist(patientId); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem checking if active patient exists = " + patientId); } return patientActive; } /** * * @param patientId * @return * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private String adtGetPatientStatus(String patientId) throws XdsInternalException { String status = null; try { status = _adtConn.getPatientStatus(patientId); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem getting Patient Status for patient ID existence = " + patientId); } return status; } /** * Creates a history of the Patient IDs and external identifiers IDs involved in a merge or unmerge * * @param survivingPatientId * @param priorRegistrationPatientId * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void adtCreateMergeHistory(String survivingPatientId, String priorRegistrationPatientId, String action, List externalIdentifierIds) throws XdsInternalException { try { _adtConn.createMergeHistory(survivingPatientId, priorRegistrationPatientId, action, externalIdentifierIds); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem creating merge history for Patient IDs = " + survivingPatientId + " / " + priorRegistrationPatientId); } } /** * Retreives the external identifier IDs involved in a merge or unmerge * * @param survivingPatientId * @param priorRegistrationPatientId * @param action - M (merge) or U (unmerge) * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private List<String> adtRetrieveMergedRecords(String survivingPatientId, String priorRegistrationPatientId) throws XdsInternalException { List<String> externalIdentifierIds = new ArrayList<String>(); try { externalIdentifierIds = _adtConn.retrieveMergedRecords(survivingPatientId, priorRegistrationPatientId, "M"); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem retrieving merge history for Patient IDs = " + survivingPatientId + " / " + priorRegistrationPatientId); } return externalIdentifierIds; } /** * Checks if a patient to be unmerged has been previously merged into the surviving patient * * @param survivingPatientId * @param priorRegistrationPatientId * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private boolean adtCheckMergeHistory(String survivingPatientId, String priorRegistrationPatientId) throws XdsInternalException { boolean merge = false; try { merge = _adtConn.isPatientMerged(survivingPatientId, priorRegistrationPatientId); } catch (SQLException e) { throw this.logInternalException(e, "ADT EXCEPTION: Problem checking merge history for Patient IDs = " + survivingPatientId + " / " + priorRegistrationPatientId); } return merge; } // Registry helper methods. /** * * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private Connection regGetDatabaseConnection() throws XdsInternalException { return new SQLConnectionWrapper().getConnection(SQLConnectionWrapper.registryJNDIResourceName); } /** * * @param survivingPatientId * @param priorRegistrationPatientId * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void regUpdateExternalIdentifiers(String survivingPatientId, String priorRegistrationPatientId) throws XdsInternalException { Connection conn = this.regGetDatabaseConnection(); PreparedStatement preparedStatement = null; try { preparedStatement = conn.prepareStatement("UPDATE EXTERNALIDENTIFIER SET VALUE = ? WHERE VALUE = ?"); preparedStatement.setString(1, survivingPatientId); preparedStatement.setString(2, priorRegistrationPatientId); preparedStatement.executeUpdate(); } catch (SQLException e) { throw this.logInternalException(e, "REGISTRY EXCEPTION: Problem with updating Registry external identifiers"); } finally { try { if (preparedStatement != null) { preparedStatement.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { logger.error("Could not close connection", e); } } } /** * This method updates the patient id on the external identifiers during an unmerge action * The list of externalIdentifier Ids for the affected records is provided as a parameter. * * @param survivingPatientId * @param priorRegistrationPatientId * @param externalIdentifierIds * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private void regUpdateExternalIdentifiers(String currentPatientId, String newPatientId, List<String> externalIdentifierIds) throws XdsInternalException { Connection conn = this.regGetDatabaseConnection(); PreparedStatement preparedStatement = null; try { preparedStatement = conn .prepareStatement("UPDATE EXTERNALIDENTIFIER SET VALUE = ? WHERE VALUE = ? AND ID = ?"); for (String id : externalIdentifierIds) { preparedStatement.setString(1, newPatientId); preparedStatement.setString(2, currentPatientId); preparedStatement.setString(3, id); preparedStatement.executeUpdate(); } } catch (SQLException e) { throw this.logInternalException(e, "REGISTRY EXCEPTION: Problem with updating Registry external identifiers for an unmerge"); } finally { try { if (preparedStatement != null) { preparedStatement.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { logger.error("Could not close connection", e); } } } /** * Retrieves a list of ExternalIdentifier IDs associated with a Patient Id * - each ExternalIdentifier is associated with an ExternalObject (Document) or * a RegistryPackage (SubmissionSet or Folder) * * @param priorRegistrationPatientId * @return List (ExternalIdentifierIDs) * @throws com.vangent.hieos.xutil.exception.XdsInternalException */ private List regGetExternalIdentifierIDs(String priorRegistrationPatientId) throws XdsInternalException { Connection conn = this.regGetDatabaseConnection(); PreparedStatement preparedStatement = null; ResultSet rs = null; List externalIds = new ArrayList<String>(); try { preparedStatement = conn.prepareStatement("SELECT ID FROM EXTERNALIDENTIFIER WHERE VALUE = ?"); preparedStatement.setString(1, priorRegistrationPatientId); rs = preparedStatement.executeQuery(); while (rs.next()) { externalIds.add(rs.getString(1)); } return externalIds; } catch (SQLException e) { throw this.logInternalException(e, "REGISTRY EXCEPTION: Problem retrieving Registry external identifier ids"); } finally { try { if (rs != null) { rs.close(); } if (preparedStatement != null) { preparedStatement.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { logger.error("Could not close connection", e); } } } /** * * @param propertyName * @return */ private String getXConfigRegistryProperty(String propertyName) { String propertyValue = null; XConfigActor registryConfig = this.getConfigActor(); if (registryConfig != null) { propertyValue = registryConfig.getProperty(propertyName); } return propertyValue; } /** * * @return */ private String getHL7Date() { return Hl7Date.now(); } // All of the log methods below should not generate exceptions if problems occur. /** * * @param transaction * @param patientId * @param messageId * @param eventActionCode * @param outcome * @param sourceIdentity * @param sourceIP */ private void auditPatientIdentityFeed(ATNAAuditEvent.IHETransaction transaction, String patientId, String messageId, EventActionCode eventActionCode, ATNAAuditEvent.OutcomeIndicator outcome, String sourceIdentity, String sourceIP) { try { XATNALogger xATNALogger = new XATNALogger(); if (xATNALogger.isPerformAudit()) { ATNAAuditEventPatientIdentityFeed auditEvent = new ATNAAuditEventPatientIdentityFeed(); auditEvent.setTransaction(transaction); auditEvent.setActorType(ATNAAuditEvent.ActorType.REGISTRY); auditEvent.setPatientId(patientId); auditEvent.setMessageId(messageId); auditEvent.setEventActionCode(eventActionCode); auditEvent.setOutcomeIndicator(outcome); auditEvent.setSourceIP(sourceIP); auditEvent.setSourceIdentity(sourceIdentity); xATNALogger.audit(auditEvent); } } catch (Exception e) { this.logInternalException(e, "Error trying to perform ATNA logging for Patient Identity Feed"); } } /** * * @param response * @param status */ private void logResponse(OMElement response, boolean status) { if (response != null) { log_message.addOtherParam("Response", response); } log_message.setPass(status); } /** * * @param errorString */ private void logError(String errorString) { this.errorDetected = true; // Make note of a problem. log_message.addErrorParam("Errors", errorString); logger.error(errorString); } /** * * @param errorString * @return */ private PatientIdentityFeedException logException(String errorString) { this.logError(errorString); return new PatientIdentityFeedException(errorString); } /** * * @param e * @param errorString * @return */ private XdsInternalException logInternalException(Exception e, String errorString) { String exceptionString = ExceptionUtil.exception_details(e, errorString); this.logError(exceptionString); return new XdsInternalException(errorString); } /** * * @param logLabel * @param infoString */ private void logInfo(String logLabel, String infoString) { log_message.addOtherParam(logLabel, infoString); logger.debug(logLabel + " : " + infoString); } /** * * @return */ @Override public boolean getStatus() { return !this.errorDetected; } // Inner class public class PatientIdentityFeedException extends Exception { public PatientIdentityFeedException(String msg) { super(msg); } } }