package org.wso2.carbon.inbound.endpoint.protocol.hl7.core; import ca.uhn.hl7v2.HL7Exception; import org.apache.axis2.AxisFault; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.synapse.MessageContext; import org.apache.synapse.SynapseConstants; import org.apache.synapse.inbound.InboundEndpoint; import org.apache.synapse.inbound.InboundEndpointConstants; import org.apache.synapse.inbound.InboundProcessorParams; import org.apache.synapse.inbound.InboundResponseSender; import org.apache.synapse.mediators.base.SequenceMediator; import org.apache.synapse.transport.customlogsetter.CustomLogSetter; import org.wso2.carbon.inbound.endpoint.protocol.hl7.context.MLLPContext; import org.wso2.carbon.inbound.endpoint.protocol.hl7.util.Axis2HL7Constants; import org.wso2.carbon.inbound.endpoint.protocol.hl7.util.HL7ExecutorServiceFactory; import org.wso2.carbon.inbound.endpoint.protocol.hl7.util.HL7MessageUtils; import java.nio.charset.CharsetDecoder; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * Copyright (c) 2015, 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. */ public class HL7Processor implements InboundResponseSender { private static final Log log = LogFactory.getLog(HL7Processor.class); private ScheduledExecutorService executorService = HL7ExecutorServiceFactory.getExecutorService(); private Map<String, Object> parameters; private InboundProcessorParams params; private String inSequence; private String onErrorSequence; private boolean autoAck = true; private int timeOut; public HL7Processor(Map<String, Object> parameters) { this.parameters = parameters; params = (InboundProcessorParams) parameters.get(MLLPConstants.INBOUND_PARAMS); inSequence = params.getInjectingSeq(); onErrorSequence = params.getOnErrorSeq(); if (params.getProperties().getProperty(MLLPConstants.PARAM_HL7_AUTO_ACK).equals("false")) { autoAck = false; } timeOut = HL7MessageUtils.getInt(MLLPConstants.PARAM_HL7_TIMEOUT, params); } /** * HL7 Request Processing logic * @param mllpContext * @throws Exception - catch any generic exceptions or else I/O Reactor may shutdown. */ public void processRequest(final MLLPContext mllpContext) throws Exception { mllpContext.setRequestTime(System.currentTimeMillis()); // Prepare Synapse Context for message injection MessageContext synCtx; try { synCtx = HL7MessageUtils.createSynapseMessageContext(mllpContext.getHl7Message(), params); } catch (HL7Exception e) { handleException(mllpContext, e.getMessage()); return; } catch (AxisFault e) { handleException(mllpContext, e.getMessage()); return; } mllpContext.setMessageId(synCtx.getMessageID()); synCtx.setProperty("inbound.endpoint.name", params.getName()); InboundEndpoint inboundEndpoint = synCtx.getConfiguration().getInboundEndpoint(params.getName()); CustomLogSetter.getInstance().setLogAppender(inboundEndpoint.getArtifactContainerName()); synCtx.setProperty(MLLPConstants.HL7_INBOUND_MSG_ID, synCtx.getMessageID()); // If not AUTO ACK, we need response invocation through this processor if (!autoAck) { synCtx.setProperty(SynapseConstants.IS_INBOUND, true); synCtx.setProperty(InboundEndpointConstants.INBOUND_ENDPOINT_RESPONSE_WORKER, this); synCtx.setProperty(MLLPConstants.MLLP_CONTEXT, mllpContext); } addProperties(synCtx, mllpContext); SequenceMediator injectSeq = (SequenceMediator) synCtx.getEnvironment().getSynapseConfiguration().getSequence(inSequence); if (injectSeq == null) { log.error("Could not find inbound sequence '" + inSequence + "'."); handleException(mllpContext, "Could not find inbound sequence."); return; } else if (!injectSeq.isInitialized()){ injectSeq.init(synCtx.getEnvironment()); } injectSeq.setErrorHandler(onErrorSequence); if (!autoAck && timeOut > 0) { executorService.schedule(new TimeoutHandler(mllpContext, synCtx.getMessageID()), timeOut, TimeUnit.MILLISECONDS); } CallableTask task = new CallableTask(synCtx, injectSeq); executorService.submit(task); } public void processError(final MLLPContext mllpContext, final Exception ex) { mllpContext.setRequestTime(System.currentTimeMillis()); // Prepare Synapse Context for message injection MessageContext synCtx; try { if (mllpContext.getRequestBuffer() != null) { synCtx = HL7MessageUtils. createErrorMessageContext(mllpContext.getRequestBuffer().toString(), ex, params); } else { synCtx = HL7MessageUtils. createErrorMessageContext("The message received is not parseable", ex, params); } } catch (HL7Exception e) { handleException(mllpContext, e.getMessage()); return; } catch (AxisFault e) { handleException(mllpContext, e.getMessage()); return; } mllpContext.setMessageId(synCtx.getMessageID()); synCtx.setProperty("inbound.endpoint.name", params.getName()); InboundEndpoint inboundEndpoint = synCtx.getConfiguration().getInboundEndpoint(params.getName()); CustomLogSetter.getInstance().setLogAppender(inboundEndpoint.getArtifactContainerName()); synCtx.setProperty(MLLPConstants.HL7_INBOUND_MSG_ID, synCtx.getMessageID()); // If not AUTO ACK, we need response invocation through this processor if (!autoAck) { synCtx.setProperty(SynapseConstants.IS_INBOUND, true); synCtx.setProperty(InboundEndpointConstants.INBOUND_ENDPOINT_RESPONSE_WORKER, this); synCtx.setProperty(MLLPConstants.MLLP_CONTEXT, mllpContext); } addProperties(synCtx, mllpContext); SequenceMediator injectSeq = (SequenceMediator) synCtx.getEnvironment().getSynapseConfiguration().getSequence(onErrorSequence); if (injectSeq == null) { log.error("Could not find inbound error sequence '" + onErrorSequence + "'."); handleException(mllpContext, "Could not find inbound error sequence."); return; } else if (!injectSeq.isInitialized()){ injectSeq.init(synCtx.getEnvironment()); } if (!autoAck && timeOut > 0) { executorService.schedule(new TimeoutHandler(mllpContext, synCtx.getMessageID()), timeOut, TimeUnit.MILLISECONDS); } CallableTask task = new CallableTask(synCtx, injectSeq); executorService.submit(task); } /** * We need to add several context properties that HL7 Axis2 transport sender depends on (also the * application/edi-hl7 formatter). * @param synCtx */ private void addProperties(MessageContext synCtx, MLLPContext context) { org.apache.axis2.context.MessageContext axis2MsgCtx = ((org.apache.synapse.core.axis2.Axis2MessageContext) synCtx).getAxis2MessageContext(); axis2MsgCtx.setProperty(Axis2HL7Constants.HL7_MESSAGE_OBJECT, context.getHl7Message()); if (params.getProperties().getProperty(MLLPConstants.PARAM_HL7_BUILD_RAW_MESSAGE) != null) { axis2MsgCtx.setProperty(Axis2HL7Constants.HL7_BUILD_RAW_MESSAGE, Boolean.valueOf( params.getProperties().getProperty(MLLPConstants.PARAM_HL7_BUILD_RAW_MESSAGE))); } if (params.getProperties().getProperty(MLLPConstants.PARAM_HL7_PASS_THROUGH_INVALID_MESSAGES) != null) { axis2MsgCtx.setProperty(Axis2HL7Constants.HL7_PASS_THROUGH_INVALID_MESSAGES, Boolean.valueOf( params.getProperties().getProperty(MLLPConstants.PARAM_HL7_PASS_THROUGH_INVALID_MESSAGES))); } if (parameters.get(MLLPConstants.HL7_CHARSET_DECODER) != null) { axis2MsgCtx.setProperty(Axis2HL7Constants.HL7_MESSAGE_CHARSET, ((CharsetDecoder) parameters.get(MLLPConstants.HL7_CHARSET_DECODER)).charset().displayName()); } // Below is expensive, it is in HL7 Axis2 transport but we should not depend on this! //axis2MsgCtx.setProperty(Axis2HL7Constants.HL7_RAW_MESSAGE_PROPERTY_NAME, context.getCodec()); } public Map<String, Object> getInboundParameterMap() { return parameters; } @Override public void sendBack(MessageContext messageContext) { MLLPContext mllpContext = (MLLPContext) messageContext.getProperty(MLLPConstants.MLLP_CONTEXT); sendBack(messageContext, mllpContext); } private void sendBack(MessageContext messageContext, MLLPContext mllpContext) { if (messageContext.getProperty(MLLPConstants.HL7_INBOUND_MSG_ID) != null && !mllpContext.getMessageId().equals(messageContext.getProperty(MLLPConstants.HL7_INBOUND_MSG_ID))) { log.warn("Response ID does not match request ID. Response may have been received after timeout."); return; } try { if ((((String) messageContext.getProperty(Axis2HL7Constants.HL7_RESULT_MODE)) != null) && ((String) messageContext.getProperty(Axis2HL7Constants.HL7_RESULT_MODE)).equals(Axis2HL7Constants.HL7_RESULT_MODE_NACK)) { String nackMessage = (String) messageContext.getProperty(Axis2HL7Constants.HL7_NACK_MESSAGE); mllpContext.setNackMode(true); mllpContext.setHl7Message(HL7MessageUtils.createNack(mllpContext.getHl7Message(), nackMessage)); } else { // if HL7_APPLICATION_ACK is set then we are going to send the auto-generated ACK based on // the HL7 request, so we do not set the payload contents as context HL7 Message. if (messageContext.getProperty(Axis2HL7Constants.HL7_APPLICATION_ACK) != null && messageContext.getProperty(Axis2HL7Constants.HL7_APPLICATION_ACK).equals("true")) { mllpContext.setApplicationAck(true); } else { mllpContext.setHl7Message(HL7MessageUtils.payloadToHL7Message(messageContext, params)); } } mllpContext.requestOutput(); } catch (NoSuchElementException e) { log.error("Could not find HL7 response in required XML format. Please ensure XML payload contains response inside message tags with namespace http://wso2.org/hl7.", e); handleException(mllpContext, "Error while generating HL7 response. Not in required format."); } catch (HL7Exception e) { log.error("Error while generating HL7 ACK response from payload.", e); handleException(mllpContext, "Error while generating ACK from payload."); } } public boolean isAutoAck() { return autoAck; } private void handleException(MLLPContext mllpContext, String msg) { if (mllpContext.isAutoAck()) { try { mllpContext.setNackMode(true); mllpContext.setHl7Message(HL7MessageUtils.createNack(mllpContext.getHl7Message(), msg)); mllpContext.requestOutput(); } catch (HL7Exception e) { log.error("Error while generating NACK response.", e); } } else { processError(mllpContext, new Exception(msg)); } } private class TimeoutHandler implements Runnable { private MLLPContext context; private String messageId; public TimeoutHandler(MLLPContext context, String messageId) { this.context = context; this.messageId = messageId; } public void run() { if (messageId.equals(context.getMessageId())) { try { log.warn("Timed out while waiting for HL7 Response to be generated."); context.setHl7Message(HL7MessageUtils.createNack(context.getHl7Message(), "Timed out while waiting for HL7 Response to be generated.")); context.setMessageId("TIMEOUT"); context.requestOutput(); } catch (HL7Exception e) { log.error("Could not generate timeout NACK response.", e); } } } } }