CAMEL-9876 - fix IndexOutOfBounds exception when MSH-18 isn't in message
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/57f9f49c Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/57f9f49c Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/57f9f49c Branch: refs/heads/camel-2.17.x Commit: 57f9f49c2292aeecf0c38c7005a1ffea9d91bd8f Parents: f043e47 Author: Quinn Stevenson <qu...@pronoia-solutions.com> Authored: Wed Apr 13 11:40:22 2016 -0600 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Apr 14 07:42:47 2016 +0200 ---------------------------------------------------------------------- .../component/mllp/MllpTcpServerConsumer.java | 113 ++++++++++-------- ...llpTcpServerConsumerAcknowledgementTest.java | 114 +++++++++++++++++++ 2 files changed, 181 insertions(+), 46 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/57f9f49c/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/MllpTcpServerConsumer.java ---------------------------------------------------------------------- diff --git a/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/MllpTcpServerConsumer.java b/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/MllpTcpServerConsumer.java index 95a2d22..c025f80 100644 --- a/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/MllpTcpServerConsumer.java +++ b/components/camel-mllp/src/main/java/org/apache/camel/component/mllp/MllpTcpServerConsumer.java @@ -543,7 +543,6 @@ public class MllpTcpServerConsumer extends DefaultConsumer { private void populateHl7DataHeaders(Exchange exchange, Message message, byte[] hl7MessageBytes) { // Find the end of the MSH and indexes of the fields in the MSH to populate message headers final byte fieldSeparator = hl7MessageBytes[3]; - final byte componentSeparator = hl7MessageBytes[4]; int endOfMSH = -1; List<Integer> fieldSeparatorIndexes = new ArrayList<>(10); // We need at least 10 fields to create the acknowledgment @@ -551,11 +550,16 @@ public class MllpTcpServerConsumer extends DefaultConsumer { if (fieldSeparator == hl7MessageBytes[i]) { fieldSeparatorIndexes.add(i); } else if (SEGMENT_DELIMITER == hl7MessageBytes[i]) { + // If the MSH Segment doesn't have a trailing field separator, add one so the field can be extracted into a header + if (fieldSeparator != hl7MessageBytes[i - 1]) { + fieldSeparatorIndexes.add(i); + } endOfMSH = i; break; } } + String messageBodyForDebugging = new String(hl7MessageBytes); if (-1 == endOfMSH) { // TODO: May want to throw some sort of an Exception here log.error("Population of message headers failed - unable to find the end of the MSH segment"); @@ -563,54 +567,71 @@ public class MllpTcpServerConsumer extends DefaultConsumer { log.debug("Populating the message headers"); Charset charset = Charset.forName(IOHelper.getCharsetName(exchange)); - // MSH-3 - message.setHeader(MLLP_SENDING_APPLICATION, new String(hl7MessageBytes, fieldSeparatorIndexes.get(1) + 1, - fieldSeparatorIndexes.get(2) - fieldSeparatorIndexes.get(1) - 1, charset)); - // MSH-4 - message.setHeader(MLLP_SENDING_FACILITY, new String(hl7MessageBytes, fieldSeparatorIndexes.get(2) + 1, - fieldSeparatorIndexes.get(3) - fieldSeparatorIndexes.get(2) - 1, charset)); - // MSH-5 - message.setHeader(MLLP_RECEIVING_APPLICATION, new String(hl7MessageBytes, fieldSeparatorIndexes.get(3) + 1, - fieldSeparatorIndexes.get(4) - fieldSeparatorIndexes.get(3) - 1, - charset)); - // MSH-6 - message.setHeader(MLLP_RECEIVING_FACILITY, new String(hl7MessageBytes, fieldSeparatorIndexes.get(4) + 1, - fieldSeparatorIndexes.get(5) - fieldSeparatorIndexes.get(4) - 1, - charset)); - // MSH-7 - message.setHeader(MLLP_TIMESTAMP, new String(hl7MessageBytes, fieldSeparatorIndexes.get(5) + 1, - fieldSeparatorIndexes.get(6) - fieldSeparatorIndexes.get(5) - 1, charset)); - // MSH-8 - message.setHeader(MLLP_SECURITY, new String(hl7MessageBytes, fieldSeparatorIndexes.get(6) + 1, - fieldSeparatorIndexes.get(7) - fieldSeparatorIndexes.get(6) - 1, charset)); - // MSH-9 - message.setHeader(MLLP_MESSAGE_TYPE, new String(hl7MessageBytes, fieldSeparatorIndexes.get(7) + 1, - fieldSeparatorIndexes.get(8) - fieldSeparatorIndexes.get(7) - 1, charset)); - // MSH-10 - message.setHeader(MLLP_MESSAGE_CONTROL, new String(hl7MessageBytes, fieldSeparatorIndexes.get(8) + 1, - fieldSeparatorIndexes.get(9) - fieldSeparatorIndexes.get(8) - 1, charset)); - // MSH-11 - message.setHeader(MLLP_PROCESSING_ID, new String(hl7MessageBytes, fieldSeparatorIndexes.get(9) + 1, - fieldSeparatorIndexes.get(10) - fieldSeparatorIndexes.get(9) - 1, charset)); - // MSH-12 - message.setHeader(MLLP_VERSION_ID, new String(hl7MessageBytes, fieldSeparatorIndexes.get(10) + 1, - fieldSeparatorIndexes.get(11) - fieldSeparatorIndexes.get(10) - 1, charset)); - // MSH-18 - message.setHeader(MLLP_CHARSET, new String(hl7MessageBytes, fieldSeparatorIndexes.get(16) + 1, - fieldSeparatorIndexes.get(17) - fieldSeparatorIndexes.get(16) - 1, charset)); - - for (int i = fieldSeparatorIndexes.get(7) + 1; i < fieldSeparatorIndexes.get(8); ++i) { - if (componentSeparator == hl7MessageBytes[i]) { - // MSH-9.1 - message.setHeader(MLLP_EVENT_TYPE, new String(hl7MessageBytes, fieldSeparatorIndexes.get(7) + 1, - i - fieldSeparatorIndexes.get(7) - 1, charset)); - // MSH-9.2 - message.setHeader(MLLP_TRIGGER_EVENT, new String(hl7MessageBytes, i + 1, - fieldSeparatorIndexes.get(8) - i - 1, charset)); - break; + for (int i = 2; i < fieldSeparatorIndexes.size(); ++i) { + int startingFieldSeparatorIndex = fieldSeparatorIndexes.get(i - 1); + int endingFieldSeparatorIndex = fieldSeparatorIndexes.get(i); + + // Only populate the header if there's data in the HL7 field + if (endingFieldSeparatorIndex - startingFieldSeparatorIndex > 1) { + String headerName = null; + switch (i) { + case 2: // MSH-3 + headerName = MLLP_SENDING_APPLICATION; + break; + case 3: // MSH-4 + headerName = MLLP_SENDING_FACILITY; + break; + case 4: // MSH-5 + headerName = MLLP_RECEIVING_APPLICATION; + break; + case 5: // MSH-6 + headerName = MLLP_RECEIVING_FACILITY; + break; + case 6: // MSH-7 + headerName = MLLP_TIMESTAMP; + break; + case 7: // MSH-8 + headerName = MLLP_SECURITY; + break; + case 8: // MSH-9 + headerName = MLLP_MESSAGE_TYPE; + break; + case 9: // MSH-10 + headerName = MLLP_MESSAGE_CONTROL; + break; + case 10: // MSH-11 + headerName = MLLP_PROCESSING_ID; + break; + case 11: // MSH-12 + headerName = MLLP_VERSION_ID; + break; + case 17: // MSH-18 + headerName = MLLP_CHARSET; + break; + default: + // Not processing this field + continue; + } + + String headerValue = new String(hl7MessageBytes, startingFieldSeparatorIndex + 1, + endingFieldSeparatorIndex - startingFieldSeparatorIndex - 1, + charset); + message.setHeader(headerName, headerValue); + + // For MSH-9, set a couple more headers + if (i == 8) { + // final byte componentSeparator = hl7MessageBytes[4]; + String componentSeparator = new String(hl7MessageBytes, 4, 1, charset); + String[] components = headerValue.split(String.format("\\Q%s\\E", componentSeparator), 3); + message.setHeader(MLLP_EVENT_TYPE, components[0]); + if (2 <= components.length) { + message.setHeader(MLLP_TRIGGER_EVENT, components[1]); + } + } } } } + } @Override http://git-wip-us.apache.org/repos/asf/camel/blob/57f9f49c/components/camel-mllp/src/test/java/org/apache/camel/component/mllp/MllpTcpServerConsumerAcknowledgementTest.java ---------------------------------------------------------------------- diff --git a/components/camel-mllp/src/test/java/org/apache/camel/component/mllp/MllpTcpServerConsumerAcknowledgementTest.java b/components/camel-mllp/src/test/java/org/apache/camel/component/mllp/MllpTcpServerConsumerAcknowledgementTest.java new file mode 100644 index 0000000..33ce3c8 --- /dev/null +++ b/components/camel-mllp/src/test/java/org/apache/camel/component/mllp/MllpTcpServerConsumerAcknowledgementTest.java @@ -0,0 +1,114 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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.apache.camel.component.mllp; + +import java.util.concurrent.TimeUnit; + +import org.apache.camel.CamelContext; +import org.apache.camel.EndpointInject; +import org.apache.camel.LoggingLevel; +import org.apache.camel.builder.NotifyBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.mock.MockEndpoint; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.test.AvailablePortFinder; +import org.apache.camel.test.junit.rule.mllp.MllpClientResource; +import org.apache.camel.test.junit.rule.mllp.MllpJUnitResourceException; +import org.apache.camel.test.junit4.CamelTestSupport; +import org.junit.Rule; +import org.junit.Test; + +import static org.apache.camel.component.mllp.MllpConstants.MLLP_EVENT_TYPE; +import static org.apache.camel.component.mllp.MllpConstants.MLLP_RECEIVING_APPLICATION; +import static org.apache.camel.component.mllp.MllpConstants.MLLP_RECEIVING_FACILITY; +import static org.apache.camel.component.mllp.MllpConstants.MLLP_SENDING_APPLICATION; +import static org.apache.camel.component.mllp.MllpConstants.MLLP_SENDING_FACILITY; +import static org.apache.camel.component.mllp.MllpConstants.MLLP_TRIGGER_EVENT; +import static org.apache.camel.component.mllp.MllpConstants.MLLP_VERSION_ID; +import static org.apache.camel.test.mllp.Hl7MessageGenerator.generateMessage; + +public class MllpTcpServerConsumerAcknowledgementTest extends CamelTestSupport { + @Rule + public MllpClientResource mllpClient = new MllpClientResource(); + + @EndpointInject(uri = "mock://result") + MockEndpoint result; + + @Override + protected CamelContext createCamelContext() throws Exception { + DefaultCamelContext context = (DefaultCamelContext) super.createCamelContext(); + + context.setUseMDCLogging(true); + context.setName(this.getClass().getSimpleName()); + + return context; + } + + @Override + protected RouteBuilder createRouteBuilder() { + + mllpClient.setMllpHost("localhost"); + mllpClient.setMllpPort(AvailablePortFinder.getNextAvailable()); + + return new RouteBuilder() { + int connectTimeout = 500; + int responseTimeout = 5000; + + @Override + public void configure() throws Exception { + String routeId = "mllp-test-receiver-route"; + + onCompletion() + .toF("log:%s?level=INFO&showAll=true", routeId) + .log(LoggingLevel.INFO, routeId, "Test route complete"); + + fromF("mllp://%s:%d?autoAck=true&connectTimeout=%d&receiveTimeout=%d", + mllpClient.getMllpHost(), mllpClient.getMllpPort(), connectTimeout, responseTimeout) + .routeId(routeId) + .to(result); + + } + }; + } + + @Test + public void testReceiveSingleMessage() throws Exception { + final String testMessage = "MSH|^~\\&|APP_A|FAC_A|^org^sys||||ADT^A04^ADT_A04|||2.6" + '\r' + + "PID|1||1100832^^^^PI||TEST^FIG||98765432|U||R|435 MAIN STREET^^LONGMONT^CO^80503||123-456-7890|||S" + '\r' + + '\r' + '\n'; + + final String expectedAcknowledgement = "MSH|^~\\&|^org^sys||APP_A|FAC_A|||ACK^A04^ADT_A04|||2.6" + '\r' + "MSA|AA|" + '\r' + '\n'; + + result.expectedMessageCount(1); + result.expectedHeaderReceived(MLLP_SENDING_APPLICATION, "APP_A"); + result.expectedHeaderReceived(MLLP_SENDING_FACILITY, "FAC_A"); + result.expectedHeaderReceived(MLLP_RECEIVING_APPLICATION, "^org^sys"); + result.expectedHeaderReceived(MLLP_EVENT_TYPE, "ADT"); + result.expectedHeaderReceived(MLLP_TRIGGER_EVENT, "A04"); + result.expectedHeaderReceived(MLLP_VERSION_ID, "2.6"); + + mllpClient.connect(); + + String acknowledgement = mllpClient.sendMessageAndWaitForAcknowledgement(testMessage, 10000); + + assertEquals("Unexpected Acknowledgement", expectedAcknowledgement, acknowledgement); + + assertMockEndpointsSatisfied(10, TimeUnit.SECONDS); + } + +} +