/** * 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.hl7; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.IoSession; import org.apache.mina.filter.codec.CumulativeProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * HL7MLLPDecoder that is aware that a HL7 message can span several TCP packets. * In addition, it avoids rescanning packets by keeping state in the IOSession. */ class HL7MLLPDecoder extends CumulativeProtocolDecoder { private static final Logger LOG = LoggerFactory .getLogger(HL7MLLPDecoder.class); private static final String CHARSET_DECODER = HL7MLLPDecoder.class .getName() + ".charsetdecoder"; private static final String DECODER_STATE = HL7MLLPDecoder.class.getName() + ".STATE"; private final HL7MLLPConfig config; HL7MLLPDecoder(HL7MLLPConfig config) { this.config = config; } @Override protected boolean doDecode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) { // Scan the buffer of start and/or end bytes boolean foundEnd = this.scan(session, in); // Write HL7 string or wait until message end arrives or buffer ends if (foundEnd) { this.writeString(session, in, out); } else { LOG.debug("No complete message in this packet"); } return foundEnd; } private void writeString(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) { DecoderState state = this.decoderState(session); if (state.posStart == 0) { LOG.warn("No start byte found, reading from beginning of data"); } // start reading from the buffer after the start markers in.position(state.posStart); try { String body = in.getString(state.length(), this.charsetDecoder(session)); if (LOG.isDebugEnabled()) { LOG .debug( "Decoded HL7 from byte stream of length {} to String of length {}", state.length(), body.length()); } out.write(body); // Avoid redelivery of scanned message state.reset(); } catch (CharacterCodingException e) { throw new RuntimeException(e); } } private CharsetDecoder charsetDecoder(IoSession session) { // convert to string using the charset decoder CharsetDecoder decoder = (CharsetDecoder) session.getAttribute(CHARSET_DECODER); if (decoder == null) { decoder = this.config.getCharset().newDecoder(); session.setAttribute(CHARSET_DECODER, decoder); } return decoder; } /** * Scans the buffer for start and end bytes and stores its position in the * session state object. * * @return {@code true} if the end bytes were found, {@code false} otherwise */ private boolean scan(IoSession session, ByteBuffer in) { DecoderState state = this.decoderState(session); // Start scanning where we left in.position(state.current); LOG.debug("Start scanning buffer at position " + in.position()); while (in.hasRemaining()) { byte b = in.get(); // Check start byte if (b == this.config.getStartByte()) { if (state.posStart > 0 || state.waitingForEndByte2) { LOG.warn("Ignoring message start at position " + in.position() + " before previous message has ended."); } else { state.posStart = in.position(); state.waitingForEndByte2 = false; if (LOG.isDebugEnabled()) { LOG.debug( "Message starts at position {}", state.posStart); } } } // Check end byte1 if (b == this.config.getEndByte1()) { if (!state.waitingForEndByte2 && state.posStart > 0) { state.waitingForEndByte2 = true; } else { LOG.warn("Ignoring unexpected 1st end byte " + b + ". Expected 2nd endpoint " + this.config.getEndByte2()); } } // Check end byte2 if (b == this.config.getEndByte2() && state.waitingForEndByte2) { state.posEnd = in.position() - 2; // use -2 to skip these // last 2 end markers state.waitingForEndByte2 = false; if (LOG.isDebugEnabled()) { LOG.debug("Message ends at position {}", state.posEnd); } break; } } // Remember where we are state.current = in.position(); in.rewind(); return state.posEnd > 0; } private DecoderState decoderState(IoSession session) { DecoderState decoderState = (DecoderState) session.getAttribute(DECODER_STATE); if (decoderState == null) { decoderState = new DecoderState(); session.setAttribute(DECODER_STATE, decoderState); } return decoderState; } @Override public void dispose(IoSession session) { session.removeAttribute(CHARSET_DECODER); session.removeAttribute(DECODER_STATE); } /** * Holds the state of the decoding process */ private static class DecoderState { int posStart; int posEnd; int current; boolean waitingForEndByte2; int length() { return this.posEnd - this.posStart; } void reset() { this.posStart = 0; this.posEnd = 0; this.waitingForEndByte2 = false; } } }