1
2
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
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
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
128 long endTime = new GregorianCalendar().getTimeInMillis();
129
130
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
260
261
262 CommandLineParser parser = new PosixParser();
263
264
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
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
291 HelpFormatter formatter = new HelpFormatter();
292
293 formatter.printHelp( "serverTest", options );
294 return;
295 }
296 else {
297
298 port = Integer.parseInt(portString);
299 }
300
301 HL7ServerTestHelper serverTest = new HL7ServerTestHelper( host, port );
302
303
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
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
344 } else if (atSequence(startCPPComment)) {
345
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) {
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;
382 }
383 for (int j = i-1; j >= 0; j--) {
384 this.unread(data[j]);
385 }
386 return result;
387 }
388 }
389
390
391 }