View Javadoc

1   /*
2    * HL7ServerTest.java
3    */
4   
5   package ca.uhn.hl7v2.app;
6   
7   import java.io.BufferedReader;
8   import java.io.FileNotFoundException;
9   import java.io.IOException;
10  import java.io.InputStream;
11  import java.io.InputStreamReader;
12  import java.io.OutputStream;
13  import java.io.PushbackReader;
14  import java.io.Reader;
15  import java.net.Socket;
16  import java.util.ArrayList;
17  import java.util.GregorianCalendar;
18  import java.util.regex.Matcher;
19  import java.util.regex.Pattern;
20  
21  import org.apache.commons.cli.CommandLine;
22  import org.apache.commons.cli.CommandLineParser;
23  import org.apache.commons.cli.HelpFormatter;
24  import org.apache.commons.cli.Options;
25  import org.apache.commons.cli.ParseException;
26  import org.apache.commons.cli.PosixParser;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import ca.uhn.hl7v2.parser.Parser;
31  import ca.uhn.hl7v2.parser.PipeParser;
32  
33  /**
34   * Helper class used to send messages from a flat file to 
35   * an ip and port. 
36   * 
37   * Messasges are sent using MLLP and ACK protocal
38   * 
39   * <mailto:laura.bright@uhn.on.ca>Laura Bright</mailto>
40   * <mailto:neal.acharya@uhn.on.ca>Neal Acharya</mailto>
41   * <mailto:jamesagnew@sourceforge.net>James Agnew</mailto>
42   * 
43   * @version $Revision: 1.5 $ updated on $Date: 2007/02/20 03:17:54 $ by $Author: jamesagnew $
44   */
45  public class HL7ServerTestHelper {
46      
47      private static final Log ourLog = LogFactory.getLog(HL7ServerTestHelper.class);
48      
49      private static final String HL7_START_OF_MESSAGE = "\u000b";
50      private static final String HL7_SEGMENT_SEPARATOR = "\r";
51      private static final String HL7_END_OF_MESSGAE = "\u001c";
52      
53      private int receivedAckCount;
54  
55      private String host = null;
56      
57      private int port = 0;
58  
59      private Socket socket = null;
60  
61      private OutputStream os = null;
62      private InputStream is = null;
63      
64      public HL7ServerTestHelper(String host, int port) {
65          this.host = host;
66          this.port = port;
67      }
68  
69      /*
70       * 
71       */
72      public void openSocket() throws IOException{
73          socket = new Socket(host, port);
74          socket.setSoLinger(true, 1000);
75          
76          os = socket.getOutputStream();
77          is = socket.getInputStream();
78      }
79      
80      /**
81       * 
82       *
83       */
84      public void closeSocket() {
85          try {
86              Socket sckt = socket;
87              socket = null;
88              if (sckt != null)
89                  sckt.close();
90          }
91          catch (Exception e) {
92              ourLog.error(e);
93          }
94      }
95   
96      
97      public int process( InputStream theMsgInputStream ) throws FileNotFoundException, IOException
98      {
99          Parser hapiParser = new PipeParser(); 
100         
101         BufferedReader in =
102             new BufferedReader( 
103                 new CommentFilterReader( new InputStreamReader( theMsgInputStream ) )
104             );
105         	
106         StringBuffer rawMsgBuffer = new StringBuffer();
107         
108         //String line = in.readLine();
109         int c = 0;
110 		while( (c = in.read()) >= 0) {
111 			rawMsgBuffer.append( (char) c);
112 		}
113 		
114 		String[] messages = getHL7Messages(rawMsgBuffer.toString());
115         int retVal = 0;
116         
117         //start time
118         long startTime = new GregorianCalendar().getTimeInMillis(); 
119             
120             
121 		for (int i = 0; i < messages.length; i++) {
122 			sendMessage(messages[i]);	
123 			readAck();	
124             retVal++;
125 		}
126         
127         //end time
128         long endTime =  new GregorianCalendar().getTimeInMillis();
129         
130         //elapsed time
131         long elapsedTime = (endTime - startTime) / 1000;
132         
133         ourLog.info(retVal + " messages sent.");
134         ourLog.info("Elapsed Time in seconds:  " + elapsedTime);
135         return retVal;
136 			
137             /*line = line.trim();
138             
139             if ( line.length()!=0 ) {
140                 rawMsgBuffer.append( line );
141                 rawMsgBuffer.append( HL7_SEGMENT_SEPARATOR );
142             }
143             else {
144                 if (rawMsgBuffer.length()!=0) {
145                     String rawMsg = rawMsgBuffer.toString();
146                     sendMessage( rawMsg );
147                     //clear buffer 
148                     rawMsgBuffer = new StringBuffer(); 
149                     //do not wait for ACK, we just want to feed the Hl7Server
150                     
151                     //TODO look into this, the HL7Server should perform better. JMS integration should fix this.
152                     
153                     try {
154                         //wait a sec, give some time to the HL7Server
155                         Thread.sleep(1000); //1 seconds
156                     }
157                     catch (InterruptedException e) {
158                     }
159                 }
160             }
161 			            
162             line = in.readLine();    
163         }*/
164         
165     }
166     
167 	private String readAck() throws IOException
168 	{
169 		StringBuffer stringbuffer = new StringBuffer();
170 		int i = 0;
171 		do {
172 			i = is.read();
173 			if (i == -1)
174 				return null;
175             
176 			stringbuffer.append((char) i);
177 		}
178 		while (i != 28);        
179 		return stringbuffer.toString();
180 	}
181     
182     
183     
184 	/** 
185 	 * Given a string that contains HL7 messages, and possibly other junk, 
186 	 * returns an array of the HL7 messages.  
187 	 * An attempt is made to recognize segments even if there is other 
188 	 * content between segments, for example if a log file logs segments 
189 	 * individually with timestamps between them.  
190 	 * 
191 	 * @param theSource a string containing HL7 messages 
192 	 * @return the HL7 messages contained in theSource
193 	 */
194 	public static String[] getHL7Messages(String theSource) {
195 		ArrayList messages = new ArrayList(20); 
196 		Pattern startPattern = Pattern.compile("^MSH", Pattern.MULTILINE);
197 		Matcher startMatcher = startPattern.matcher(theSource);
198 
199 		while (startMatcher.find()) {
200 			String messageExtent = 
201 				getMessageExtent(theSource.substring(startMatcher.start()), startPattern);
202 			
203 			char fieldDelim = messageExtent.charAt(3);
204 			Pattern segmentPattern = Pattern.compile("^[A-Z\\d]{3}\\" + fieldDelim + ".*$", Pattern.MULTILINE);
205 			Matcher segmentMatcher = segmentPattern.matcher(messageExtent);
206 			StringBuffer msg = new StringBuffer();
207 			while (segmentMatcher.find()) {
208 				msg.append(segmentMatcher.group().trim());
209 				msg.append('\r');
210 			}
211 			messages.add(msg.toString());
212 		}
213 		return (String[]) messages.toArray(new String[0]);
214 	}
215     
216 	/** 
217 	 * Given a string that contains at least one HL7 message, returns the 
218 	 * smallest string that contains the first of these messages.  
219 	 */
220 	private static String getMessageExtent(String theSource, Pattern theStartPattern) {
221 		Matcher startMatcher = theStartPattern.matcher(theSource);
222 		if (!startMatcher.find()) {
223 			throw new IllegalArgumentException(theSource + "does not contain message start pattern" 
224 				+ theStartPattern.toString());
225 		}
226     	
227 		int start = startMatcher.start();
228 		int end = theSource.length();
229 		if (startMatcher.find()) {
230 			end = startMatcher.start();
231 		}
232  	
233 		return theSource.substring(start, end).trim();
234 	}
235     
236     
237     private void sendMessage(String theMessage) throws IOException
238     {
239         os.write( HL7_START_OF_MESSAGE.getBytes() );
240         os.write( theMessage.getBytes() );
241         os.write( HL7_END_OF_MESSGAE.getBytes() );
242         os.write(13);
243         os.flush();
244         ourLog.info("Sent: " + theMessage);
245     }
246      
247     
248     
249     /**
250      * Main method for running the application
251      * 
252      * example command lines args:
253      * 
254      * -f UHN_PRO_DEV_PATIENTS.dat -h 142.224.178.152 -p 3999
255      * 
256      */
257     public static void main( String[] theArgs ) {
258 
259         //parse command line arguments        
260 
261         //create the command line parser
262         CommandLineParser parser = new PosixParser();
263 
264         //create the Options
265         Options options = new Options();
266 
267         options.addOption("h", "host", true, "IP of host to send to");
268         options.addOption("p", "port", true, "port to send to");
269         options.addOption("f", "file", true, "file to read HL7 messages from");
270         
271         CommandLine cmdLine = null;
272         try
273         {
274             // parse the command line arguments
275             cmdLine = parser.parse(options, theArgs);
276         }
277         catch (ParseException e)
278         {
279             ourLog.error(e);
280             return;
281         }
282 
283         String portString = cmdLine.getOptionValue("p");
284         int port = 0;
285         String host = cmdLine.getOptionValue("h");        
286         String file = cmdLine.getOptionValue("f");
287         
288         if (portString == null || host == null || file == null)
289         {
290             //automatically generate the help statement
291             HelpFormatter formatter = new HelpFormatter();
292             //assuming that a shell script named serverTest will be created
293             formatter.printHelp( "serverTest", options );
294             return;
295         }
296         else {
297             //parse portAsString
298             port = Integer.parseInt(portString);
299         }
300         
301         HL7ServerTestHelper serverTest = new HL7ServerTestHelper( host, port );
302         
303         //InputStream msgInputStream = HL7ServerTestHelper.class.getResourceAsStream( file );
304 		InputStream msgInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);		
305         try{            
306             serverTest.openSocket();
307             serverTest.process( msgInputStream );
308         }
309         catch(Exception e){
310         	e.printStackTrace();
311             HelpFormatter formatter = new HelpFormatter();
312             //assuming that a shell script named hl7mom will be created
313             formatter.printHelp( "serverTest", options );
314             System.exit(-1);
315         }
316         
317         serverTest.closeSocket();
318     }
319     
320 	/**
321 	 * TODO: this code is copied from HAPI ... should make it part of HAPI public API instead
322 	 * Removes C and C++ style comments from a reader stream.  C style comments are
323 	 * distinguished from URL protocol delimiters by the preceding colon in the
324 	 * latter.
325 	 */
326 	public static class CommentFilterReader extends PushbackReader {
327         
328 		private final char[] startCPPComment = {'/', '*'};
329 		private final char[] endCPPComment = {'*', '/'};
330 		private final char[] startCComment = {'/', '/'};
331 		private final char[] endCComment = {'\n'};
332 		private final char[] protocolDelim = {':', '/', '/'};
333         
334 		public CommentFilterReader(Reader in) {
335 			super(in, 5);
336 		}
337         
338 		/**
339 		 * Returns the next character, not including comments.
340 		 */
341 		public int read() throws IOException {
342 			if (atSequence(protocolDelim)) {
343 				//proceed normally
344 			} else if (atSequence(startCPPComment)) {
345 				//skip() doesn't seem to work for some reason
346 				while (!atSequence(endCPPComment)) super.read();
347 				for (int i = 0; i < endCPPComment.length; i++) super.read();
348 			} else if (atSequence(startCComment)) {
349 				while (!atSequence(endCComment)) super.read();
350 				for (int i = 0; i < endCComment.length; i++) super.read();
351 			}
352 			int ret = super.read();
353 			if (ret == 65535) ret = -1;
354 			return ret;            
355 		}
356                 
357 		public int read(char[] cbuf, int off, int len) throws IOException {
358 			int i = -1;
359 			boolean done = false;
360 			while (++i < len) {
361 				int next = read();
362 				if (next == 65535 || next == -1) { //Pushback causes -1 to convert to 65535
363 					done = true;
364 					break;  
365 				}
366 				cbuf[off + i] = (char) next;
367 			}
368 			if (i == 0 && done) i = -1; 
369 			return i; 
370 		}            
371         
372 		/**
373 		 * Tests incoming data for match with char sequence, resets reader when done.
374 		 */
375 		private boolean atSequence(char[] sequence) throws IOException {
376 			boolean result = true;
377 			int i = -1;
378 			int[] data = new int[sequence.length];
379 			while (++i < sequence.length && result == true) {
380 				data[i] = super.read();
381 				if ((char) data[i] != sequence[i]) result = false; //includes case where end of stream reached
382 			}
383 			for (int j = i-1; j >= 0; j--) {
384 				this.unread(data[j]);
385 			}
386 			return result;
387 		}        
388 	}
389     
390 
391 }