Coverage Report - ca.uhn.hl7v2.llp.ExtendedMinLLPReader
 
Classes in this File Line Coverage Branch Coverage Complexity
ExtendedMinLLPReader
88%
77/87
78%
36/46
5.222
 
 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 Initial Developer of the Original Code is University Health Network. Copyright (C) 
 10  
 2001.  All Rights Reserved. 
 11  
 
 12  
 Contributor(s): Jens Kristian Villadsen from Cetrea A/S
 13  
 
 14  
 Alternatively, the contents of this file may be used under the terms of the 
 15  
 GNU General Public License (the "GPL"), in which case the provisions of the GPL are 
 16  
 applicable instead of those above.  If you wish to allow use of your version of this 
 17  
 file only under the terms of the GPL and not to allow others to use your version 
 18  
 of this file under the MPL, indicate your decision by deleting  the provisions above 
 19  
 and replace  them with the notice and other provisions required by the GPL License.  
 20  
 If you do not delete the provisions above, a recipient may use your version of 
 21  
 this file under either the MPL or the GPL. 
 22  
 
 23  
 */
 24  
 
 25  
 package ca.uhn.hl7v2.llp;
 26  
 
 27  
 import static ca.uhn.hl7v2.llp.MinLLPReader.*;
 28  
 
 29  
 import java.io.ByteArrayInputStream;
 30  
 import java.io.ByteArrayOutputStream;
 31  
 import java.io.IOException;
 32  
 import java.io.InputStream;
 33  
 import java.io.InputStreamReader;
 34  
 import java.net.SocketException;
 35  
 import java.net.SocketTimeoutException;
 36  
 import java.nio.charset.Charset;
 37  
 
 38  
 import org.slf4j.Logger;
 39  
 import org.slf4j.LoggerFactory;
 40  
 
 41  
 import ca.uhn.hl7v2.HL7Exception;
 42  
 import ca.uhn.hl7v2.parser.EncodingNotSupportedException;
 43  
 import ca.uhn.hl7v2.preparser.PreParser;
 44  
 
 45  
 /**
 46  
  * Charset-aware MLLP stream reader
 47  
  * 
 48  
  * @see ExtendedMinLowerLayerProtocol
 49  
  * @author Jens Kristian Villadsen from Cetrea A/S
 50  
  */
 51  
 public class ExtendedMinLLPReader implements HL7Reader
 52  
 {
 53  
 
 54  1
         private static final Logger log = LoggerFactory.getLogger(ExtendedMinLLPReader.class);
 55  
 
 56  
         private InputStream inputStream;
 57  
         private Charset myLastCharset;
 58  
         private InputStreamReader myReader;
 59  
 
 60  
         /**
 61  
          * Creates a MinLLPReader with no setup - setInputStream must be set later.
 62  
          */
 63  
         public ExtendedMinLLPReader()
 64  
         {
 65  0
                 super();
 66  0
         }
 67  
 
 68  
         /**
 69  
          * Creates a MinLLPReader which reads from the given InputStream. The stream is assumed to be an ASCII bit stream.
 70  
          */
 71  
         public ExtendedMinLLPReader(InputStream in) throws IOException
 72  12
         {
 73  12
                 setInputStream(in);
 74  12
         }
 75  
 
 76  
         /**
 77  
          * Closes the underlying BufferedReader.
 78  
          */
 79  
         public synchronized void close() throws java.io.IOException
 80  
         {
 81  0
                 myReader.close();
 82  0
         }
 83  
 
 84  
         private Charset getCharacterEncoding(InputStream in) throws IOException
 85  
         {
 86  13
                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 87  13
                 int next = in.read();
 88  5884
                 while((next != -1 || bos.size() == 0) && next != END_MESSAGE && next != LAST_CHARACTER)
 89  
                 {
 90  5871
                         bos.write(next);
 91  5871
                         next = in.read();
 92  
                 }
 93  13
                 bos.flush();
 94  
                 
 95  
                 try
 96  
                 {
 97  
                         String firstLine;
 98  13
                         if ((bos.toByteArray()[0] == -2 && bos.toByteArray()[1] == -1) ||
 99  
                                         bos.toByteArray()[1] == -2 && bos.toByteArray()[0] == -1) {
 100  
                                 
 101  
                                 // if the string is little endian, then we will be missing the second byte of the 
 102  
                                 // last character (a "\r"), so add it manually
 103  3
                                 if (bos.toByteArray()[1] == -2 && bos.toByteArray()[0] == -1) {
 104  1
                                         bos.write(0);
 105  
                                 }
 106  
                                 
 107  3
                                 firstLine = bos.toString("UTF-16");
 108  
                         } else {
 109  10
                                 firstLine = bos.toString("US-ASCII");
 110  
                         }
 111  
                         
 112  
                         String[] fields;
 113  
                         try {
 114  13
                                 fields = PreParser.getFields(firstLine, "MSH-18(0)");
 115  2
                         } catch (HL7Exception e) {
 116  2
                                 log.warn("Failed to parse MSH segment. Defaulting to US-ASCII", e);
 117  2
                                 return Charset.forName("US-ASCII");
 118  11
                         }
 119  11
                         String charset = stripNonLowAscii(fields[0]);
 120  
                         Charset javaCs;
 121  
                         try {
 122  11
                                 javaCs = CharSetUtil.convertHL7CharacterEncodingToCharSetvalue(charset);
 123  0
                         } catch (EncodingNotSupportedException e) {
 124  0
                                 log.warn("Invalid or unsupported charset in MSH-18: \"{}\". Defaulting to US-ASCII", e);
 125  0
                                 return Charset.forName("US-ASCII");
 126  11
                         }                        
 127  11
                         log.debug("Detected MSH-18 value \"{}\" so using charset {}", charset, javaCs.displayName());                        
 128  11
                         return javaCs;
 129  
                 }
 130  
 //                catch(Exception e)
 131  
 //                {
 132  
 //                        log.warn("Nonvalid charset - defaulting to US-ASCII", e);
 133  
 //                }
 134  
                 finally
 135  
                 {
 136  13
                         bos.close();
 137  
                 }
 138  
         }
 139  
 
 140  
         private String stripNonLowAscii(String theString) {
 141  11
                 if (theString == null) return "";
 142  9
                 StringBuilder b = new StringBuilder();
 143  
                 
 144  103
                 for (int i = 0; i < theString.length(); i++) {
 145  94
                         char next = theString.charAt(i);
 146  94
                         if (next > 0 && next < 127) {
 147  92
                                 b.append(next);
 148  
                         }
 149  
                 }
 150  
                 
 151  9
                 return b.toString();
 152  
         }
 153  
 
 154  
         /**
 155  
          * @return the lastCharset
 156  
          */
 157  
         public Charset getLastCharset() {
 158  9
                 return myLastCharset;
 159  
         }
 160  
 
 161  
         public synchronized String getMessage() throws LLPException, IOException
 162  
         {
 163  19
                 ByteArrayOutputStream baos = verifyAndCopyToOutputStream(this.inputStream);
 164  
 
 165  17
                 if(baos == null)
 166  4
                         return null;
 167  
                 
 168  13
                 byte[] byteArray = baos.toByteArray();
 169  13
                 myLastCharset = getCharacterEncoding(new ByteArrayInputStream(byteArray));
 170  
 
 171  13
                 myReader = new InputStreamReader(new ByteArrayInputStream(byteArray), myLastCharset);
 172  13
                 baos.close();
 173  
 
 174  13
                 StringBuilder s_buffer = new StringBuilder();
 175  
 
 176  13
                 int c = myReader.read();
 177  5142
                 while(c != -1)
 178  
                 {
 179  5129
                         s_buffer.append((char) c);
 180  5129
                         c = myReader.read();
 181  
                 }
 182  13
                 return s_buffer.toString();
 183  
         }
 184  
 
 185  
         /**
 186  
          * Sets the InputStream from which to read messages. The InputStream must be set before any calls to <code>getMessage()</code>.
 187  
          */
 188  
         public synchronized void setInputStream(InputStream in) throws IOException
 189  
         {
 190  12
                 this.inputStream = in;
 191  12
         }
 192  
 
 193  
         private ByteArrayOutputStream verifyAndCopyToOutputStream(InputStream stream) throws IOException, LLPException
 194  
         {
 195  19
                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 196  19
                 boolean end_of_message = false;
 197  
 
 198  
                 int c;
 199  
                 try
 200  
                 {
 201  19
                         c = stream.read();
 202  
                 }
 203  1
                 catch(SocketException e)
 204  
                 {
 205  1
                         log.info("SocketException on read() attempt.  Socket appears to have been closed: {}", e.getMessage());
 206  1
                         throw e;
 207  
                 }
 208  4
                 catch(SocketTimeoutException e)
 209  
                 {
 210  4
                         log.debug("SocketTimeoutException on read() attempt.");
 211  4
                         return null;
 212  14
                 }
 213  
                 // trying to read when there is no data (stream may have been closed at other end)
 214  14
                 if(c == -1)
 215  
                 {
 216  1
                         log.info("End of input stream reached.");
 217  1
                         throw new SocketException("End of input stream reached");
 218  
                 }
 219  13
                 LowerLayerProtocol.logCharacterReceived(c);
 220  
 
 221  13
                 if(c != START_MESSAGE)
 222  
                 {
 223  0
                         throw new LLPException("Message violates the " + "minimal lower layer protocol: no start of message indicator " + "received. Received: " + c);
 224  
                 }
 225  
 
 226  6378
                 while(!end_of_message)
 227  
                 {
 228  6365
                         c = stream.read();
 229  
 
 230  6365
                         if(c == -1)
 231  
                         {
 232  0
                                 throw new LLPException("Message violates the " + "minimal lower protocol: message terminated without " + "a terminating character.");
 233  
                         }
 234  6365
                         LowerLayerProtocol.logCharacterReceived(c);
 235  
 
 236  6365
                         if(c == END_MESSAGE)
 237  
                         {
 238  
                                 // subsequent character should be a carriage return
 239  13
                                 c = stream.read();
 240  13
                                 if(c >= 0)
 241  13
                                         LowerLayerProtocol.logCharacterReceived(c);
 242  13
                                 if(c != LAST_CHARACTER)
 243  
                                 {
 244  0
                                         throw new LLPException("Message " + "violates the minimal lower layer protocol: " + "message terminator not followed by a return " + "character.");
 245  
                                 }
 246  13
                                 end_of_message = true;
 247  
                         }
 248  
                         else
 249  
                         {
 250  
                                 // the character wasn't the end of message, append it to the message
 251  6352
                                 bos.write(c);
 252  
                         }
 253  
                 }
 254  
 
 255  13
                 bos.flush();
 256  13
                 return bos;
 257  
         }
 258  
 }