/* * Copyright (c) 2005-2012, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * 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 org.wso2.carbon.business.messaging.hl7.common; import ca.uhn.hl7v2.HL7Exception; import ca.uhn.hl7v2.conf.ProfileException; import ca.uhn.hl7v2.conf.check.DefaultValidator; import ca.uhn.hl7v2.conf.parser.ProfileParser; import ca.uhn.hl7v2.conf.spec.RuntimeProfile; import ca.uhn.hl7v2.model.DataTypeException; import ca.uhn.hl7v2.model.Message; import ca.uhn.hl7v2.model.v22.message.ACK; import ca.uhn.hl7v2.parser.DefaultXMLParser; import ca.uhn.hl7v2.parser.EncodingNotSupportedException; import ca.uhn.hl7v2.parser.Parser; import ca.uhn.hl7v2.parser.PipeParser; import ca.uhn.hl7v2.validation.impl.NoValidation; import ca.uhn.hl7v2.util.idgenerator.InMemoryIDGenerator; import org.apache.axiom.om.OMElement; import org.apache.axis2.AxisFault; import org.apache.axis2.context.MessageContext; import org.apache.axis2.description.AxisService; import org.apache.axis2.description.Parameter; import org.apache.axis2.description.ParameterInclude; import org.apache.axis2.transport.base.ParamUtils; import org.apache.axis2.wsdl.WSDLConstants; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.business.messaging.hl7.common.HL7Constants.MessageEncoding; import org.wso2.carbon.business.messaging.hl7.common.HL7Constants.MessageType; import org.wso2.carbon.business.messaging.hl7.common.data.MessageData; import org.wso2.carbon.business.messaging.hl7.common.data.conf.HL7MessagePublisherConfig; import org.wso2.carbon.business.messaging.hl7.common.data.conf.ServerConfig; import org.wso2.carbon.business.messaging.hl7.common.data.publisher.HL7EventPublisher; import javax.xml.namespace.QName; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Arrays; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** * This class represents an HL7 message processing context. */ public class HL7ProcessingContext { private static final Log log = LogFactory.getLog(HL7ProcessingContext.class); private boolean publisherEnabled; private boolean autoAck; private boolean validateMessage; private int timeOutVal; private RuntimeProfile conformanceProfile; private PipeParser pipeParser; private Parser xmlparser; private DefaultValidator defaultValidator; private HL7MessagePreprocessor messagePreprocessor; private BlockingQueue<Message> applicationResponses; private boolean isPassThroughInvalidMessages; private boolean isBuildRawMessages; private HL7MessagePublisherConfig hl7PublisherConfig; private HL7EventPublisher eventPublisher; public HL7ProcessingContext(boolean autoAck, boolean validateMessage,boolean publisherEnabled , String conformanceProfileURL, String messagePreprocessorClass,String serverUrl,String secureServerUrl, String serverPassword, String serverUsername, boolean passThroughInvalidMessages, boolean buildRawMessages) throws HL7Exception { this.autoAck = autoAck; this.validateMessage = validateMessage; this.publisherEnabled = publisherEnabled; this.defaultValidator = new DefaultValidator(); this.applicationResponses = new LinkedBlockingQueue<Message>(); this.xmlparser = new DefaultXMLParser(); this.isPassThroughInvalidMessages = passThroughInvalidMessages; this.isBuildRawMessages = buildRawMessages; this.eventPublisher = new HL7EventPublisher(new ServerConfig(secureServerUrl,serverUrl, serverUsername,serverPassword)); if (conformanceProfileURL != null) { this.conformanceProfile = this.createConformanceProfile(conformanceProfileURL); } if (messagePreprocessorClass != null) { try { this.messagePreprocessor = (HL7MessagePreprocessor) Class.forName( messagePreprocessorClass).newInstance(); } catch (Exception e) { throw new HL7Exception("Error creating message preprocessor: " + e.getMessage(), e); } } if (this.getMessagePreprocessor() != null) { this.pipeParser = new PipeParser() { public Message parse(String message) throws HL7Exception { message = getMessagePreprocessor().process(message, MessageType.V2X, MessageEncoding.ER7); return super.parse(message); } }; } else { this.pipeParser = new PipeParser(); } // PipeParser is always set with NoValidation context, other wise invalid messages will result in halting // execution. this.getPipeParser().setValidationContext(new NoValidation()); //Improve the performance for generating ID. this.getPipeParser().getParserConfiguration().setIdGenerator(new InMemoryIDGenerator()); } public HL7ProcessingContext(ParameterInclude params) throws HL7Exception, AxisFault { this(ParamUtils.getOptionalParamBoolean(params, HL7Constants.HL7_AUTO_ACKNOWLEDGE, true), ParamUtils.getOptionalParamBoolean(params, HL7Constants.HL7_VALIDATE_MESSAGE, true), ParamUtils.getOptionalParamBoolean(params, HL7Constants.HL7_MESSAGE_PUBLISHER_ENABLE, false), ParamUtils.getOptionalParam(params, HL7Constants.HL7_CONFORMANCE_PROFILE_PATH), ParamUtils.getOptionalParam(params, HL7Constants.HL7_MESSAGE_PREPROCESSOR_CLASS), ParamUtils.getOptionalParam(params, HL7Constants.HL7_MESSAGE_PUBLISHER_SERVER_URL), ParamUtils.getOptionalParam(params, HL7Constants.HL7_MESSAGE_PUBLISHER_SECURE_SERVER_URL), ParamUtils.getOptionalParam(params, HL7Constants.HL7_MESSAGE_PUBLISHER_SERVER_PASSWORD), ParamUtils.getOptionalParam(params, HL7Constants.HL7_MESSAGE_PUBLISHER_SERVER_USERNAME), ParamUtils.getOptionalParamBoolean(params, HL7Constants.HL7_PASS_THROUGH_INVALID_MESSAGES, true), ParamUtils.getOptionalParamBoolean(params, HL7Constants.HL7_BUILD_RAW_MESSAGE, true)); } public HL7ProcessingContext(AxisService service) throws HL7Exception { this(extractServiceBooleanParam(service, HL7Constants.HL7_AUTO_ACKNOWLEDGE, true), extractServiceBooleanParam(service, HL7Constants.HL7_VALIDATE_MESSAGE, true), extractServiceBooleanParam(service, HL7Constants.HL7_MESSAGE_PUBLISHER_ENABLE, false), extractServiceStringParam(service, HL7Constants.HL7_CONFORMANCE_PROFILE_PATH), extractServiceStringParam(service, HL7Constants.HL7_MESSAGE_PREPROCESSOR_CLASS), extractServiceStringParam(service, HL7Constants.HL7_MESSAGE_PUBLISHER_SERVER_URL), extractServiceStringParam(service, HL7Constants.HL7_MESSAGE_PUBLISHER_SECURE_SERVER_URL), extractServiceStringParam(service, HL7Constants.HL7_MESSAGE_PUBLISHER_SERVER_PASSWORD), extractServiceStringParam(service, HL7Constants.HL7_MESSAGE_PUBLISHER_SERVER_USERNAME), extractServiceBooleanParam(service, HL7Constants.HL7_PASS_THROUGH_INVALID_MESSAGES, true), extractServiceBooleanParam(service, HL7Constants.HL7_BUILD_RAW_MESSAGE, true)); } public HL7MessagePreprocessor getMessagePreprocessor() { return messagePreprocessor; } private static boolean extractServiceBooleanParam(AxisService service, String name, boolean defaultVal) { if (service != null) { Parameter param = service.getParameter(name); if (param != null) { Object value = param.getValue(); if (value != null) { return Boolean.parseBoolean(value.toString()); } } } return defaultVal; } private static String extractServiceStringParam(AxisService service, String name) { if (service != null) { Parameter param = service.getParameter(name); if (param != null) { Object value = param.getValue(); if (value != null) { return value.toString(); } } } return null; } private Message createDefaultNackMessage(String errorMsg) throws DataTypeException { ACK ack = new ACK(); ack.getMSH().getFieldSeparator().setValue( HL7Constants.HL7_DEFAULT_FIELD_SEPARATOR); ack.getMSH().getEncodingCharacters().setValue( HL7Constants.HL7_DEFAULT_ENCODING_CHARS); ack.getMSH().getReceivingApplication().setValue( HL7Constants.HL7_DEFAULT_RECEIVING_APPLICATION); ack.getMSH().getReceivingFacility().setValue( HL7Constants.HL7_DEFAULT_RECEIVING_FACILITY); ack.getMSH().getProcessingID().setValue( HL7Constants.HL7_DEFAULT_PROCESSING_ID); ack.getMSA().getAcknowledgementCode().setValue(HL7Constants.HL7_DEFAULT_ACK_CODE_AR); ack.getMSA().getMessageControlID().setValue(HL7Constants.HL7_DEFAULT_MESSAGE_CONTROL_ID); ack.getERR().getErrorCodeAndLocation(0).getCodeIdentifyingError(). getIdentifier().setValue(errorMsg); return ack; } public DefaultValidator getDefaultValidator() { return defaultValidator; } private RuntimeProfile createConformanceProfile(String conformanceProfileURL) throws HL7Exception { InputStream in = null; try { in = new URL(conformanceProfileURL).openStream(); RuntimeProfile profile = new ProfileParser(false).parse(HL7Utils.streamToString(in)); return profile; } catch (Exception e) { throw new HL7Exception("Error creating conformance profile: " + e.getMessage(), e); } finally { if (in != null) { try { in.close(); } catch (Exception e) { log.error(e); } } } } public PipeParser getPipeParser() { return pipeParser; } public boolean isAutoAck() { return autoAck; } public boolean isValidateMessage() { return validateMessage; } public boolean isPassThroughInvalidMessages() { return this.isPassThroughInvalidMessages; } public boolean isBuildRawMessages() { return this.isBuildRawMessages; } public RuntimeProfile getConformanceProfile() { return conformanceProfile; } public void offerApplicationResponses(Message message, MessageContext messageContext) throws HL7Exception, AxisFault { String resultMode = (String) messageContext.getProperty(HL7Constants.HL7_RESULT_MODE); if (resultMode != null) { handleAutoAckNack(resultMode, messageContext, message); }else{ applicationResponses.offer(message); } } public Message parseMessage(String hl7TextMsg) throws HL7Exception, ProfileException { return this.getPipeParser().parse(hl7TextMsg); } public void checkConformanceProfile(Message message) throws HL7Exception { RuntimeProfile profile = this.getConformanceProfile(); if (profile != null) { try { HL7Exception[] errors = this.getDefaultValidator().validate(message, profile.getMessage()); //Throw an exception if there are errors if(errors.length > 0){ throw new HL7Exception(Arrays.toString(errors), HL7Exception.UNSUPPORTED_MESSAGE_TYPE); } } catch (ProfileException e) { throw new HL7Exception(e.getMessage(), e); } } } public void initMessageContext(Message message, MessageContext msgCtx) { msgCtx.setProperty(HL7Constants.HL7_MESSAGE_OBJECT, message); msgCtx.setProperty(HL7Constants.HL7_PASS_THROUGH_INVALID_MESSAGES, isPassThroughInvalidMessages()); msgCtx.setProperty(HL7Constants.HL7_BUILD_RAW_MESSAGE, isBuildRawMessages()); publishMessage(message,msgCtx); try { msgCtx.setProperty(HL7Constants.HL7_RAW_MESSAGE_PROPERTY_NAME, message.encode()); } catch (HL7Exception e) { log.error("Error while encoding for RAW HL7 message in EDI format"); } } public void publishMessage(Message message, MessageContext msgCtx) { if (isPublisherEnabled()) { try { hl7PublisherConfig = new HL7MessagePublisherConfig(); MessageData messageData = hl7PublisherConfig.createMessage(message, msgCtx); eventPublisher.publish(messageData); } catch (HL7Exception e) { log.error("Error in publishing the message", e); } } } public Message createAck(Message hl7Msg) throws HL7Exception { try { return hl7Msg.generateACK(); } catch (IOException e) { throw new HL7Exception(e); } } public Message createNack(Message hl7Msg, String errorMsg) throws HL7Exception { if (errorMsg == null) { errorMsg = ""; } if (hl7Msg == null) { return this.createDefaultNackMessage(errorMsg); } else { try { return hl7Msg.generateACK(HL7Constants.HL7_MSA_ERROR_FIELD_VALUE, new HL7Exception(errorMsg)); } catch (IOException e) { throw new HL7Exception(e); } } } /** * Handle the AUTOACK, NACK messages,which are to be sent back to the client * @param ctx * @param hl7Msg * @return * @throws HL7Exception * @throws AxisFault */ public Message handleHL7Result(MessageContext ctx, Message hl7Msg) throws HL7Exception, AxisFault { String resultMode = (String) ctx.getProperty(HL7Constants.HL7_RESULT_MODE); String applicationAck = (String) ctx.getProperty(HL7Constants.HL7_APPLICATION_ACK); if (resultMode != null) { return handleAutoAckNack(resultMode,ctx,hl7Msg); } else if (this.isAutoAck()) { return this.createAck(hl7Msg); } /* * no one ACKed or NACKed, want to create a application ACK message so we * will return the result from the downstream application. */ return handleApplicationACK(applicationAck, hl7Msg); } /** * Handle Auto ack/nack properties in INFLOW and OUT FLOW * * @param resultMode * @param ctx * @param hl7Msg * @return * @throws HL7Exception * @throws AxisFault */ private Message handleAutoAckNack(String resultMode, MessageContext ctx, Message hl7Msg) throws HL7Exception, AxisFault { Message msg; String applicationAck = (String) ctx.getProperty(HL7Constants.HL7_APPLICATION_ACK); if (HL7Constants.HL7_RESULT_MODE_ACK.equals(resultMode)) { if (ctx.getFLOW() == 1) { // it is inflow if (!(("true").equalsIgnoreCase(applicationAck))) { //Since Result Mode is ACK and ACK Sends to Client. Response from back end is removed //And HL7_APPLICATION_ACK is not enabled, Content will be removed from the Queue //if it is failed to remove, System get memory Leak applicationResponses.clear(); } msg = this.createAck(hl7Msg); return msg; } else { MessageContext requestMessageCtx = ctx.getOperationContext() .getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE); Message requesthl7message = xmlPayloadToHL7Message(requestMessageCtx); msg = this.createAck(requesthl7message); applicationResponses.offer(msg); } } else if (HL7Constants.HL7_RESULT_MODE_NACK.equals(resultMode)) { String nackMessage = (String) ctx.getProperty(HL7Constants.HL7_NACK_MESSAGE); if (nackMessage == null) { nackMessage = ""; } if (ctx.getFLOW() == 1) { msg = this.createNack(hl7Msg, nackMessage); if (!(("true").equalsIgnoreCase(applicationAck))) { //Since Result Mode is NACK and NACK Sends to Client. Response from back end is removed //And HL7_APPLICATION_ACK is not enabled, Content will be removed from the Queue //if it is failed to remove, System get memory Leak applicationResponses.clear(); } return msg; } else { MessageContext requestMessageCtx = ctx.getOperationContext() .getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE); Message requesthl7message = xmlPayloadToHL7Message(requestMessageCtx); msg = this.createNack(requesthl7message, nackMessage); applicationResponses.offer(msg); } } else { return this.createNack(hl7Msg, "Application Error: ACK/NACK was not explicitly returned"); } return null; } /** * ACK or NACK property is not set, want to create an application ACK * message so we will return the result from the downstream application. If * application-ack is set to 'false', we will generate a nack response, * since user has not set either auto-ack property or application-ack * property. */ private Message handleApplicationACK(String applicationAck, Message hl7Msg) throws HL7Exception { String errMsg = "User has not set any property to indicate the system, whether it has to " + "generate ACK message or not."; if (("true").equalsIgnoreCase(applicationAck)) { try { Message response = applicationResponses.poll(timeOutVal, TimeUnit.MILLISECONDS); return response; } catch (InterruptedException e) { e.printStackTrace(); } } else { try { applicationResponses.poll(timeOutVal,TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("Error During handle Application Ack - Removing from Queue", e); } } return this.createNack(hl7Msg, errMsg); } /** * Get the hl7message from the messagecontext * @param ctx * @return * @throws HL7Exception */ private Message xmlPayloadToHL7Message(MessageContext ctx) throws HL7Exception { OMElement hl7MsgEl = (OMElement) ctx.getEnvelope().getBody().getChildrenWithName(new QName(HL7Constants.HL7_NAMESPACE, HL7Constants.HL7_MESSAGE_ELEMENT_NAME)) .next(); String hl7XMLPayload = hl7MsgEl.getFirstElement().toString(); Message msg = null; try { msg = this.xmlparser.parse(hl7XMLPayload); return msg; } catch (EncodingNotSupportedException e) { log.error("Encoding error in the message",e); throw new HL7Exception("Encoding error in the message: " + e.getMessage(), e); } catch (DataTypeException e) { // Make this as warning.Since some remote systems require enriched messages that violate some HL7 //rules it would be nice to be able to still send the message. log.warn("Rule validation fails."+ e); if (!(this.isValidateMessage())){ this.xmlparser.setValidationContext(new NoValidation()); msg = this.xmlparser.parse(hl7XMLPayload); return msg; } } catch (HL7Exception e) { log.error("Error in the Message :" , e); throw new HL7Exception("Encoding error in the message: " + e.getMessage(), e); } return msg; } public boolean isPublisherEnabled() { return publisherEnabled; } public void setPublisherEnabled(boolean publisherEnabled) { this.publisherEnabled = publisherEnabled; } public void setHl7PublisherConfig(HL7MessagePublisherConfig hl7PublisherConfig) { this.hl7PublisherConfig = hl7PublisherConfig; } /** * @param timeOut Transport Timeout value */ public void setTimeOutVal(int timeOut) { this.timeOutVal = timeOut; } }