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