View Javadoc
1   /*
2    The contents of this file are subject to the Mozilla Public License Version 1.1
3    (the "License"); you may not use this file except in compliance with the License.
4    You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    Software distributed under the License is distributed on an "AS IS" basis,
6    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    specific language governing rights and limitations under the License.
8   
9    The Original Code is "MllpDecoderState.java".  Description:
10   "State machine that handles reading MLLP frames"
11  
12   The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   2013.  All Rights Reserved.
14  
15   Contributor(s): ______________________________________.
16  
17   Alternatively, the contents of this file may be used under the terms of the
18   GNU General Public License (the "GPL"), in which case the provisions of the GPL are
19   applicable instead of those above.  If you wish to allow use of your version of this
20   file only under the terms of the GPL and not to allow others to use your version
21   of this file under the MPL, indicate your decision by deleting  the provisions above
22   and replace  them with the notice and other provisions required by the GPL License.
23   If you do not delete the provisions above, a recipient may use your version of
24   this file under either the MPL or the GPL.
25   */
26  
27  package ca.uhn.hl7v2.llp;
28  
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.OutputStream;
32  import java.net.SocketException;
33  import java.net.SocketTimeoutException;
34  
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import static ca.uhn.hl7v2.llp.MllpConstants.END_BYTE1;
39  import static ca.uhn.hl7v2.llp.MllpConstants.END_BYTE2;
40  import static ca.uhn.hl7v2.llp.MllpConstants.START_BYTE;
41  
42  /**
43   * MllpDecoderState implements a small state machine that handles reading bytes at the various
44   * positions in the MLLP input stream, thereby promoting the state if the MLLP marker bytes
45   * are encountered.
46   *
47   * @author Christian Ohr
48   */
49  enum MllpDecoderState {
50  
51      START(START_BYTE, true) {
52          @Override
53          protected MllpDecoderState proceed() {
54              return BODY;
55          }
56  
57          @Override
58          protected void handleEndOfStream() throws IOException {
59              LOG.info("End of input stream reached.");
60              throw new SocketException("End of input stream reached before message starts");
61          }
62  
63      },
64      BODY(END_BYTE1, false) {
65          @Override
66          protected MllpDecoderState proceed() {
67              return PREPARE_END;
68          }
69  
70          @Override
71          protected void handleEndOfStream() throws LLPException {
72              throw new LLPException("MLLP protocol violation - Stream ends in the message body");
73          }
74  
75      },
76      PREPARE_END(END_BYTE2, true) {
77          @Override
78          protected MllpDecoderState proceed() {
79              return END;
80          }
81  
82          @Override
83          protected void handleEndOfStream() throws LLPException {
84              throw new LLPException("MLLP protocol violation - Stream ends before LLP end byte");
85          }
86      },
87      END(0, false) {
88          @Override
89          protected MllpDecoderState proceed() {
90              return END;
91          }
92  
93          @Override
94          protected void handleEndOfStream() {
95          }
96  
97          @Override
98          MllpDecoderState read(InputStream in, OutputStream out) throws LLPException {
99              throw new LLPException("Internal error - reading after end of message");
100         }
101     };
102 
103     private final int nextStateByte; // byte required for state transition
104     private final boolean mustChangeState; // next byte must be nextStateByte
105 
106     private static final Logger LOG = LoggerFactory.getLogger(MllpDecoderState.class);
107 
108 
109     MllpDecoderState(int nextStateByte, boolean mustChangeState) {
110         this.nextStateByte = nextStateByte;
111         this.mustChangeState = mustChangeState;
112     }
113 
114     /**
115      * Reads data from the input stream, records the actual payload and promtes the
116      * state if applicable.
117      *
118      * @param in  input
119      * @param out recorded data
120      * @return next decoder state
121      * @throws SocketTimeoutException If the socket times out
122      * @throws IOException
123      * @throws LLPException
124      */
125     MllpDecoderState read(InputStream in, OutputStream out) throws SocketTimeoutException, IOException, LLPException {
126         int c;
127         try {
128             if ((c = in.read()) == -1) {
129                 handleEndOfStream();
130             } else {
131                 LowerLayerProtocol.logCharacterReceived(c);
132             }
133             if (c == nextStateByte) {
134             	return proceed();
135             }
136             if (mustChangeState) {
137                 throw new LLPException("MLLP protocol violation - Expected byte '" + nextStateByte +
138                         "' in state " + this + " but was '" + c + "'");
139             }
140             out.write(c);
141         } catch (SocketException e) {
142             LOG.info("SocketException on read() attempt.  Socket appears to have been closed: " + e.getMessage());
143             throw e;
144         }
145         return this;
146     }
147 
148     /**
149      * Proceed to the next state
150      *
151      * @return next decoder state
152      */
153     protected abstract MllpDecoderState proceed();
154 
155     /**
156      * Handle occurences of unexpected ends of streams
157      *
158      * @throws IOException
159      * @throws LLPException
160      */
161     protected abstract void handleEndOfStream() throws IOException, LLPException;
162 
163 }