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 = null;
096        try {
097                in = new BufferedReader( 
098                        new CommentFilterReader( new InputStreamReader( theMsgInputStream ) )
099                    );
100                        
101                StringBuffer rawMsgBuffer = new StringBuffer();
102                
103                //String line = in.readLine();
104                int c = 0;
105                        while( (c = in.read()) >= 0) {
106                                rawMsgBuffer.append( (char) c);
107                        }
108                        
109                        String[] messages = getHL7Messages(rawMsgBuffer.toString());
110                int retVal = 0;
111                
112                //start time
113                long startTime = new GregorianCalendar().getTimeInMillis(); 
114                    
115                    
116                        for (int i = 0; i < messages.length; i++) {
117                                sendMessage(messages[i]);       
118                                readAck();      
119                    retVal++;
120                        }
121                
122                //end time
123                long endTime =  new GregorianCalendar().getTimeInMillis();
124                
125                //elapsed time
126                long elapsedTime = (endTime - startTime) / 1000;
127                
128                ourLog.info("{} messages sent.", retVal);
129                ourLog.info("Elapsed Time in seconds: {} ", elapsedTime);
130                return retVal;
131        } finally {
132                if (in != null) {
133                        try {
134                                        in.close();
135                                } catch (IOException e) {
136                                }
137                }
138        }
139                        
140        
141    }
142    
143        private String readAck() throws IOException
144        {
145                StringBuffer stringbuffer = new StringBuffer();
146                int i = 0;
147                do {
148                        i = is.read();
149                        if (i == -1)
150                                return null;
151            
152                        stringbuffer.append((char) i);
153                }
154                while (i != 28);        
155                return stringbuffer.toString();
156        }
157    
158    
159    
160        /** 
161         * Given a string that contains HL7 messages, and possibly other junk, 
162         * returns an array of the HL7 messages.  
163         * An attempt is made to recognize segments even if there is other 
164         * content between segments, for example if a log file logs segments 
165         * individually with timestamps between them.  
166         * 
167         * @param theSource a string containing HL7 messages 
168         * @return the HL7 messages contained in theSource
169         */
170        public static String[] getHL7Messages(String theSource) {
171                List<String> messages = new ArrayList<String>(20); 
172                Pattern startPattern = Pattern.compile("^MSH", Pattern.MULTILINE);
173                Matcher startMatcher = startPattern.matcher(theSource);
174
175                while (startMatcher.find()) {
176                        String messageExtent = 
177                                getMessageExtent(theSource.substring(startMatcher.start()), startPattern);
178                        
179                        char fieldDelim = messageExtent.charAt(3);
180                        Pattern segmentPattern = Pattern.compile("^[A-Z\\d]{3}\\" + fieldDelim + ".*$", Pattern.MULTILINE);
181                        Matcher segmentMatcher = segmentPattern.matcher(messageExtent);
182                        StringBuffer msg = new StringBuffer();
183                        while (segmentMatcher.find()) {
184                                msg.append(segmentMatcher.group().trim());
185                                msg.append('\r');
186                        }
187                        messages.add(msg.toString());
188                }
189                return messages.toArray(new String[0]);
190        }
191    
192        /** 
193         * Given a string that contains at least one HL7 message, returns the 
194         * smallest string that contains the first of these messages.  
195         */
196        private static String getMessageExtent(String theSource, Pattern theStartPattern) {
197                Matcher startMatcher = theStartPattern.matcher(theSource);
198                if (!startMatcher.find()) {
199                        throw new IllegalArgumentException(theSource + "does not contain message start pattern" 
200                                + theStartPattern.toString());
201                }
202        
203                int start = startMatcher.start();
204                int end = theSource.length();
205                if (startMatcher.find()) {
206                        end = startMatcher.start();
207                }
208        
209                return theSource.substring(start, end).trim();
210        }
211    
212    
213    private void sendMessage(String theMessage) throws IOException
214    {
215        os.write( HL7_START_OF_MESSAGE.getBytes() );
216        os.write( theMessage.getBytes() );
217        os.write( HL7_END_OF_MESSGAE.getBytes() );
218        os.write(13);
219        os.flush();
220        ourLog.info("Sent: " + theMessage);
221    }
222     
223    
224    
225    /**
226     * Main method for running the application
227     * 
228     * example command lines args:
229     * 
230     * -f UHN_PRO_DEV_PATIENTS.dat -h 142.224.178.152 -p 3999
231     * 
232     */
233    public static void main( String[] theArgs ) {
234
235        //parse command line arguments        
236
237        //create the command line parser
238        CommandLineParser parser = new PosixParser();
239
240        //create the Options
241        Options options = new Options();
242
243        options.addOption("h", "host", true, "IP of host to send to");
244        options.addOption("p", "port", true, "port to send to");
245        options.addOption("f", "file", true, "file to read HL7 messages from");
246        
247        CommandLine cmdLine = null;
248        try
249        {
250            // parse the command line arguments
251            cmdLine = parser.parse(options, theArgs);
252        }
253        catch (ParseException e)
254        {
255            ourLog.error(e.getMessage(), e);
256            return;
257        }
258
259        String portString = cmdLine.getOptionValue("p");
260        int port = 0;
261        String host = cmdLine.getOptionValue("h");        
262        String file = cmdLine.getOptionValue("f");
263        
264        if (portString == null || host == null || file == null)
265        {
266            //automatically generate the help statement
267            HelpFormatter formatter = new HelpFormatter();
268            //assuming that a shell script named serverTest will be created
269            formatter.printHelp( "serverTest", options );
270            return;
271        }
272        else {
273            //parse portAsString
274            port = Integer.parseInt(portString);
275        }
276        
277        HL7ServerTestHelper serverTest = new HL7ServerTestHelper( host, port );
278        
279        //InputStream msgInputStream = HL7ServerTestHelper.class.getResourceAsStream( file );
280                InputStream msgInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);          
281        try{            
282            serverTest.openSocket();
283            serverTest.process( msgInputStream );
284        }
285        catch(Exception e){
286                e.printStackTrace();
287            HelpFormatter formatter = new HelpFormatter();
288            //assuming that a shell script named hl7mom will be created
289            formatter.printHelp( "serverTest", options );
290            System.exit(-1);
291        }
292        
293        serverTest.closeSocket();
294    }
295    
296        /**
297         * TODO: this code is copied from HAPI ... should make it part of HAPI public API instead
298         * Removes C and C++ style comments from a reader stream.  C style comments are
299         * distinguished from URL protocol delimiters by the preceding colon in the
300         * latter.
301         */
302        public static class CommentFilterReader extends PushbackReader {
303        
304                private final char[] startCPPComment = {'/', '*'};
305                private final char[] endCPPComment = {'*', '/'};
306                private final char[] startCComment = {'/', '/'};
307                private final char[] endCComment = {'\n'};
308                private final char[] protocolDelim = {':', '/', '/'};
309        
310                public CommentFilterReader(Reader in) {
311                        super(in, 5);
312                }
313        
314                /**
315                 * Returns the next character, not including comments.
316                 */
317                public int read() throws IOException {
318                        if (atSequence(protocolDelim)) {
319                                //proceed normally
320                        } else if (atSequence(startCPPComment)) {
321                                //skip() doesn't seem to work for some reason
322                                while (!atSequence(endCPPComment)) super.read();
323                                for (int i = 0; i < endCPPComment.length; i++) super.read();
324                        } else if (atSequence(startCComment)) {
325                                while (!atSequence(endCComment)) super.read();
326                                for (int i = 0; i < endCComment.length; i++) super.read();
327                        }
328                        int ret = super.read();
329                        if (ret == 65535) ret = -1;
330                        return ret;            
331                }
332                
333                public int read(char[] cbuf, int off, int len) throws IOException {
334                        int i = -1;
335                        boolean done = false;
336                        while (++i < len) {
337                                int next = read();
338                                if (next == 65535 || next == -1) { //Pushback causes -1 to convert to 65535
339                                        done = true;
340                                        break;  
341                                }
342                                cbuf[off + i] = (char) next;
343                        }
344                        if (i == 0 && done) i = -1; 
345                        return i; 
346                }            
347        
348                /**
349                 * Tests incoming data for match with char sequence, resets reader when done.
350                 */
351                private boolean atSequence(char[] sequence) throws IOException {
352                        boolean result = true;
353                        int i = -1;
354                        int[] data = new int[sequence.length];
355                        while (++i < sequence.length && result == true) {
356                                data[i] = super.read();
357                                if ((char) data[i] != sequence[i]) result = false; //includes case where end of stream reached
358                        }
359                        for (int j = i-1; j >= 0; j--) {
360                                this.unread(data[j]);
361                        }
362                        return result;
363                }        
364        }
365    
366
367}