001    /*
002     * HL7ServerTest.java
003     */
004    
005    package ca.uhn.hl7v2.app;
006    
007    import java.io.BufferedReader;
008    import java.io.FileNotFoundException;
009    import java.io.IOException;
010    import java.io.InputStream;
011    import java.io.InputStreamReader;
012    import java.io.OutputStream;
013    import java.io.PushbackReader;
014    import java.io.Reader;
015    import java.net.Socket;
016    import java.util.ArrayList;
017    import java.util.GregorianCalendar;
018    import java.util.regex.Matcher;
019    import java.util.regex.Pattern;
020    
021    import org.apache.commons.cli.CommandLine;
022    import org.apache.commons.cli.CommandLineParser;
023    import org.apache.commons.cli.HelpFormatter;
024    import org.apache.commons.cli.Options;
025    import org.apache.commons.cli.ParseException;
026    import org.apache.commons.cli.PosixParser;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    
030    import ca.uhn.hl7v2.parser.Parser;
031    import ca.uhn.hl7v2.parser.PipeParser;
032    
033    /**
034     * Helper class used to send messages from a flat file to 
035     * an ip and port. 
036     * 
037     * Messasges are sent using MLLP and ACK protocal
038     * 
039     * @author Laura Bright
040     * @author Neal Acharya
041     * 
042     * @version $Revision: 1.2 $ updated on $Date: 2009/03/18 23:27:58 $ by $Author: jamesagnew $
043     */
044    public class HL7ServerTestHelper {
045        
046        private static final Log ourLog = LogFactory.getLog(HL7ServerTestHelper.class);
047        
048        private static final String HL7_START_OF_MESSAGE = "\u000b";
049        private static final String HL7_SEGMENT_SEPARATOR = "\r";
050        private static final String HL7_END_OF_MESSGAE = "\u001c";
051        
052        private int receivedAckCount;
053    
054        private String host = null;
055        
056        private int port = 0;
057    
058        private Socket socket = null;
059    
060        private OutputStream os = null;
061        private InputStream is = null;
062        
063        public HL7ServerTestHelper(String host, int port) {
064            this.host = host;
065            this.port = port;
066        }
067    
068        /*
069         * 
070         */
071        public void openSocket() throws IOException{
072            socket = new Socket(host, port);
073            socket.setSoLinger(true, 1000);
074            
075            os = socket.getOutputStream();
076            is = socket.getInputStream();
077        }
078        
079        /**
080         * 
081         *
082         */
083        public void closeSocket() {
084            try {
085                Socket sckt = socket;
086                socket = null;
087                if (sckt != null)
088                    sckt.close();
089            }
090            catch (Exception e) {
091                ourLog.error(e);
092            }
093        }
094     
095        
096        public int process( InputStream theMsgInputStream ) throws FileNotFoundException, IOException
097        {
098            Parser hapiParser = new PipeParser(); 
099            
100            BufferedReader in =
101                new BufferedReader( 
102                    new CommentFilterReader( new InputStreamReader( theMsgInputStream ) )
103                );
104                    
105            StringBuffer rawMsgBuffer = new StringBuffer();
106            
107            //String line = in.readLine();
108            int c = 0;
109                    while( (c = in.read()) >= 0) {
110                            rawMsgBuffer.append( (char) c);
111                    }
112                    
113                    String[] messages = getHL7Messages(rawMsgBuffer.toString());
114            int retVal = 0;
115            
116            //start time
117            long startTime = new GregorianCalendar().getTimeInMillis(); 
118                
119                
120                    for (int i = 0; i < messages.length; i++) {
121                            sendMessage(messages[i]);       
122                            readAck();      
123                retVal++;
124                    }
125            
126            //end time
127            long endTime =  new GregorianCalendar().getTimeInMillis();
128            
129            //elapsed time
130            long elapsedTime = (endTime - startTime) / 1000;
131            
132            ourLog.info(retVal + " messages sent.");
133            ourLog.info("Elapsed Time in seconds:  " + elapsedTime);
134            return retVal;
135                            
136                /*line = line.trim();
137                
138                if ( line.length()!=0 ) {
139                    rawMsgBuffer.append( line );
140                    rawMsgBuffer.append( HL7_SEGMENT_SEPARATOR );
141                }
142                else {
143                    if (rawMsgBuffer.length()!=0) {
144                        String rawMsg = rawMsgBuffer.toString();
145                        sendMessage( rawMsg );
146                        //clear buffer 
147                        rawMsgBuffer = new StringBuffer(); 
148                        //do not wait for ACK, we just want to feed the Hl7Server
149                        
150                        //TODO look into this, the HL7Server should perform better. JMS integration should fix this.
151                        
152                        try {
153                            //wait a sec, give some time to the HL7Server
154                            Thread.sleep(1000); //1 seconds
155                        }
156                        catch (InterruptedException e) {
157                        }
158                    }
159                }
160                                        
161                line = in.readLine();    
162            }*/
163            
164        }
165        
166            private String readAck() throws IOException
167            {
168                    StringBuffer stringbuffer = new StringBuffer();
169                    int i = 0;
170                    do {
171                            i = is.read();
172                            if (i == -1)
173                                    return null;
174                
175                            stringbuffer.append((char) i);
176                    }
177                    while (i != 28);        
178                    return stringbuffer.toString();
179            }
180        
181        
182        
183            /** 
184             * Given a string that contains HL7 messages, and possibly other junk, 
185             * returns an array of the HL7 messages.  
186             * An attempt is made to recognize segments even if there is other 
187             * content between segments, for example if a log file logs segments 
188             * individually with timestamps between them.  
189             * 
190             * @param theSource a string containing HL7 messages 
191             * @return the HL7 messages contained in theSource
192             */
193            public static String[] getHL7Messages(String theSource) {
194                    ArrayList messages = new ArrayList(20); 
195                    Pattern startPattern = Pattern.compile("^MSH", Pattern.MULTILINE);
196                    Matcher startMatcher = startPattern.matcher(theSource);
197    
198                    while (startMatcher.find()) {
199                            String messageExtent = 
200                                    getMessageExtent(theSource.substring(startMatcher.start()), startPattern);
201                            
202                            char fieldDelim = messageExtent.charAt(3);
203                            Pattern segmentPattern = Pattern.compile("^[A-Z\\d]{3}\\" + fieldDelim + ".*$", Pattern.MULTILINE);
204                            Matcher segmentMatcher = segmentPattern.matcher(messageExtent);
205                            StringBuffer msg = new StringBuffer();
206                            while (segmentMatcher.find()) {
207                                    msg.append(segmentMatcher.group().trim());
208                                    msg.append('\r');
209                            }
210                            messages.add(msg.toString());
211                    }
212                    return (String[]) messages.toArray(new String[0]);
213            }
214        
215            /** 
216             * Given a string that contains at least one HL7 message, returns the 
217             * smallest string that contains the first of these messages.  
218             */
219            private static String getMessageExtent(String theSource, Pattern theStartPattern) {
220                    Matcher startMatcher = theStartPattern.matcher(theSource);
221                    if (!startMatcher.find()) {
222                            throw new IllegalArgumentException(theSource + "does not contain message start pattern" 
223                                    + theStartPattern.toString());
224                    }
225            
226                    int start = startMatcher.start();
227                    int end = theSource.length();
228                    if (startMatcher.find()) {
229                            end = startMatcher.start();
230                    }
231            
232                    return theSource.substring(start, end).trim();
233            }
234        
235        
236        private void sendMessage(String theMessage) throws IOException
237        {
238            os.write( HL7_START_OF_MESSAGE.getBytes() );
239            os.write( theMessage.getBytes() );
240            os.write( HL7_END_OF_MESSGAE.getBytes() );
241            os.write(13);
242            os.flush();
243            ourLog.info("Sent: " + theMessage);
244        }
245         
246        
247        
248        /**
249         * Main method for running the application
250         * 
251         * example command lines args:
252         * 
253         * -f UHN_PRO_DEV_PATIENTS.dat -h 142.224.178.152 -p 3999
254         * 
255         */
256        public static void main( String[] theArgs ) {
257    
258            //parse command line arguments        
259    
260            //create the command line parser
261            CommandLineParser parser = new PosixParser();
262    
263            //create the Options
264            Options options = new Options();
265    
266            options.addOption("h", "host", true, "IP of host to send to");
267            options.addOption("p", "port", true, "port to send to");
268            options.addOption("f", "file", true, "file to read HL7 messages from");
269            
270            CommandLine cmdLine = null;
271            try
272            {
273                // parse the command line arguments
274                cmdLine = parser.parse(options, theArgs);
275            }
276            catch (ParseException e)
277            {
278                ourLog.error(e);
279                return;
280            }
281    
282            String portString = cmdLine.getOptionValue("p");
283            int port = 0;
284            String host = cmdLine.getOptionValue("h");        
285            String file = cmdLine.getOptionValue("f");
286            
287            if (portString == null || host == null || file == null)
288            {
289                //automatically generate the help statement
290                HelpFormatter formatter = new HelpFormatter();
291                //assuming that a shell script named serverTest will be created
292                formatter.printHelp( "serverTest", options );
293                return;
294            }
295            else {
296                //parse portAsString
297                port = Integer.parseInt(portString);
298            }
299            
300            HL7ServerTestHelper serverTest = new HL7ServerTestHelper( host, port );
301            
302            //InputStream msgInputStream = HL7ServerTestHelper.class.getResourceAsStream( file );
303                    InputStream msgInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);          
304            try{            
305                serverTest.openSocket();
306                serverTest.process( msgInputStream );
307            }
308            catch(Exception e){
309                    e.printStackTrace();
310                HelpFormatter formatter = new HelpFormatter();
311                //assuming that a shell script named hl7mom will be created
312                formatter.printHelp( "serverTest", options );
313                System.exit(-1);
314            }
315            
316            serverTest.closeSocket();
317        }
318        
319            /**
320             * TODO: this code is copied from HAPI ... should make it part of HAPI public API instead
321             * Removes C and C++ style comments from a reader stream.  C style comments are
322             * distinguished from URL protocol delimiters by the preceding colon in the
323             * latter.
324             */
325            public static class CommentFilterReader extends PushbackReader {
326            
327                    private final char[] startCPPComment = {'/', '*'};
328                    private final char[] endCPPComment = {'*', '/'};
329                    private final char[] startCComment = {'/', '/'};
330                    private final char[] endCComment = {'\n'};
331                    private final char[] protocolDelim = {':', '/', '/'};
332            
333                    public CommentFilterReader(Reader in) {
334                            super(in, 5);
335                    }
336            
337                    /**
338                     * Returns the next character, not including comments.
339                     */
340                    public int read() throws IOException {
341                            if (atSequence(protocolDelim)) {
342                                    //proceed normally
343                            } else if (atSequence(startCPPComment)) {
344                                    //skip() doesn't seem to work for some reason
345                                    while (!atSequence(endCPPComment)) super.read();
346                                    for (int i = 0; i < endCPPComment.length; i++) super.read();
347                            } else if (atSequence(startCComment)) {
348                                    while (!atSequence(endCComment)) super.read();
349                                    for (int i = 0; i < endCComment.length; i++) super.read();
350                            }
351                            int ret = super.read();
352                            if (ret == 65535) ret = -1;
353                            return ret;            
354                    }
355                    
356                    public int read(char[] cbuf, int off, int len) throws IOException {
357                            int i = -1;
358                            boolean done = false;
359                            while (++i < len) {
360                                    int next = read();
361                                    if (next == 65535 || next == -1) { //Pushback causes -1 to convert to 65535
362                                            done = true;
363                                            break;  
364                                    }
365                                    cbuf[off + i] = (char) next;
366                            }
367                            if (i == 0 && done) i = -1; 
368                            return i; 
369                    }            
370            
371                    /**
372                     * Tests incoming data for match with char sequence, resets reader when done.
373                     */
374                    private boolean atSequence(char[] sequence) throws IOException {
375                            boolean result = true;
376                            int i = -1;
377                            int[] data = new int[sequence.length];
378                            while (++i < sequence.length && result == true) {
379                                    data[i] = super.read();
380                                    if ((char) data[i] != sequence[i]) result = false; //includes case where end of stream reached
381                            }
382                            for (int j = i-1; j >= 0; j--) {
383                                    this.unread(data[j]);
384                            }
385                            return result;
386                    }        
387            }
388        
389    
390    }