001    /**
002    The contents of this file are subject to the Mozilla Public License Version 1.1 
003    (the "License"); you may not use this file except in compliance with the License. 
004    You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005    Software distributed under the License is distributed on an "AS IS" basis, 
006    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007    specific language governing rights and limitations under the License. 
008    
009    The Original Code is "TestPanel.java".  Description: 
010    "A user interface for testing communications with an HL7 server." 
011    
012    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
013    2001.  All Rights Reserved. 
014    
015    Contributor(s): ______________________________________. 
016    
017    Alternatively, the contents of this file may be used under the terms of the 
018    GNU General Public License (the  ?GPL?), in which case the provisions of the GPL are 
019    applicable instead of those above.  If you wish to allow use of your version of this 
020    file only under the terms of the GPL and not to allow others to use your version 
021    of this file under the MPL, indicate your decision by deleting  the provisions above 
022    and replace  them with the notice and other provisions required by the GPL License.  
023    If you do not delete the provisions above, a recipient may use your version of 
024    this file under either the MPL or the GPL. 
025    
026    */
027    
028    package ca.uhn.hl7v2.app;
029    
030    import java.awt.BorderLayout;
031    import java.awt.Component;
032    import java.awt.FlowLayout;
033    import java.awt.event.ActionEvent;
034    import java.awt.event.ActionListener;
035    import java.awt.event.WindowAdapter;
036    import java.awt.event.WindowEvent;
037    import java.io.IOException;
038    import java.net.Socket;
039    import java.util.Vector;
040    
041    import javax.swing.JButton;
042    import javax.swing.JCheckBox;
043    import javax.swing.JFrame;
044    import javax.swing.JLabel;
045    import javax.swing.JList;
046    import javax.swing.JOptionPane;
047    import javax.swing.JPanel;
048    import javax.swing.JScrollPane;
049    import javax.swing.JSplitPane;
050    import javax.swing.JTextArea;
051    
052    import ca.uhn.hl7v2.HL7Exception;
053    import ca.uhn.hl7v2.llp.LLPException;
054    import ca.uhn.hl7v2.llp.LowerLayerProtocol;
055    import ca.uhn.hl7v2.model.Message;
056    import ca.uhn.hl7v2.parser.EncodingNotSupportedException;
057    import ca.uhn.hl7v2.parser.GenericParser;
058    import ca.uhn.hl7v2.parser.Parser;
059    import ca.uhn.hl7v2.parser.PipeParser;
060    import ca.uhn.hl7v2.view.TreePanel;
061    import ca.uhn.log.HapiLog;
062    import ca.uhn.log.HapiLogFactory;
063    
064    /**
065     * <p>A user interface for testing communications with an HL7 server.  The UI contains 
066     * a window with 4 main panels.  One can enter message text into the first panel, press
067     * a "parse" button so that the message is displayed in a tree in the second panel, 
068     * press "send" to send the message to a remote server and display the response in 
069     * a tree in the third panel, and press "encode" to write the inbound message as text 
070     * in the fourth panel.  To use, run from the command line with no arguments, like this:</p>
071     * <p><code>java -classpath . ca.uhn.hl7v2.app.TestPanel</code></p>
072     * <p>Exceptions generated during parsing or server communication are logged in the working 
073     * directory and displayed in a dialog box.</p>
074     * @author Bryan Tripp
075     */
076    public class TestPanel extends JPanel implements ConnectionListener {
077            
078            private static final HapiLog log = HapiLogFactory.getHapiLog(TestPanel.class);
079    
080        private PipeParser pparser; //used only to display encoded fields in tree 
081        private GenericParser parser;
082        private JTextArea outboundText;
083        private TreePanel outboundTree;
084        private JTextArea inboundText;
085        private TreePanel inboundTree;
086        private JTextArea hostTA;
087        private JTextArea portTA;
088        private Initiator conn = null;
089        private Vector connections;
090        private JSplitPane messages;
091        private JList connList = null;
092        private MessageTypeRouter router;
093        private JCheckBox xmlCheckBox;
094        
095        /** Creates a new instance of TestPanel */
096        public TestPanel() throws HL7Exception {
097            this.pparser = new PipeParser();
098            this.parser = new GenericParser();
099            connections = new Vector();
100            router = new MessageTypeRouter();
101            initUI();
102        }
103    
104        /** 
105         * Creates and lays out UI components 
106         */
107        private void initUI() {
108            //main part of panel shows grid of 4 message areas: outbound on top; inbound on bottom
109            //   and plain text on left, tree on right
110            this.setLayout(new BorderLayout());
111            outboundText = new JTextArea(10, 10); JScrollPane outTextScroll = new JScrollPane(outboundText);
112            outboundTree = new TreePanel(this.pparser); JScrollPane outTreeScroll = new JScrollPane(outboundTree);
113            inboundText = new JTextArea(10, 10); JScrollPane inTextScroll = new JScrollPane(inboundText);
114            inboundTree = new TreePanel(this.pparser); JScrollPane inTreeScroll = new JScrollPane(inboundTree);
115            
116            JSplitPane outbound = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, addTitle(outTextScroll, " Outbound Message Text "), addTitle(outTreeScroll, " Outbound Message Tree "));
117            JSplitPane inbound = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, addTitle(inTextScroll, " Inbound Message Text "), addTitle(inTreeScroll, " Inbound Message Tree "));
118            messages = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, outbound, inbound);
119            
120            this.add(messages, BorderLayout.CENTER);
121            
122            //controls arranged along top for now
123            JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
124            JButton parseButton = new JButton(" Parse ");
125            JButton sendButton = new JButton(" Send ");
126            JButton encodeButton = new JButton(" Encode Inbound ");
127            JButton encodeOriginalButton = new JButton(" Encode Outbound ");
128            //JButton xmlEncodeButton = new JButton(" Encode XML ");
129            xmlCheckBox = new JCheckBox("Use XML", false);
130            JButton connectButton = new JButton(" Connect ");
131            JButton disconnectButton = new JButton(" Disconnect ");
132            hostTA = new JTextArea(1, 30);
133            portTA = new JTextArea(1, 6);
134            controlPanel.add(parseButton);
135            controlPanel.add(sendButton);
136            controlPanel.add(encodeButton);
137            controlPanel.add(encodeOriginalButton);
138            controlPanel.add(xmlCheckBox);
139            //controlPanel.add(xmlEncodeButton);
140            //controlPanel.add(new JLabel("   Host: "));
141            //controlPanel.add(hostTA);
142            //controlPanel.add(new JLabel("   Port: "));
143            //controlPanel.add(portTA);
144            controlPanel.add(connectButton);
145            controlPanel.add(disconnectButton);
146            this.add(controlPanel, BorderLayout.NORTH);
147            
148            //connection selecter on right side
149            connList = new JList();
150            connList.setPrototypeCellValue("xxxxxxxxxxxxxxxxxxxxxxxxxxx");
151            connList.setSelectionMode(0);  //found through trial & error - don't know where constants are defined 
152            JPanel connPanel = new JPanel(new BorderLayout());
153            connPanel.add(new JScrollPane(connList), BorderLayout.CENTER);
154            connPanel.add(new JLabel(" Connections "), BorderLayout.NORTH);
155            this.add(connPanel, BorderLayout.EAST);
156            
157            //set up event handlers for buttons
158            parseButton.addActionListener(new ActionListener() {
159                public void actionPerformed(ActionEvent ae) {
160                    try {
161                        parseOutbound();
162                    } catch (Exception e) { showException(e); }
163                } 
164            });
165            
166            connectButton.addActionListener(new ActionListener() {
167                public void actionPerformed(ActionEvent ae) {
168                    try {
169                        new ConnectionDialog(getThis());
170                    } catch (Exception e) { showException(e); }
171                } 
172            });
173            
174            sendButton.addActionListener(new ActionListener() {
175                public void actionPerformed(ActionEvent ae) {
176                    try {
177                        sendAndRecieve();
178                    } catch (Exception e) { showException(e); }
179                } 
180            });
181            
182            encodeButton.addActionListener(new ActionListener() {
183                public void actionPerformed(ActionEvent ae) {
184                    try {
185                        encodeInbound();
186                    } catch (Exception e) { showException(e); }
187                } 
188            });
189            
190            encodeOriginalButton.addActionListener(new ActionListener() {
191                public void actionPerformed(ActionEvent ae) {
192                    try {
193                        encodeOutbound();
194                    } catch (Exception e) { showException(e); }
195                } 
196            });
197            
198            xmlCheckBox.addActionListener(new ActionListener() {
199                public void actionPerformed(ActionEvent ae) {
200                    if (xmlCheckBox.isSelected()) {
201                        parser.setXMLParserAsPrimary();
202                    } else {
203                        parser.setPipeParserAsPrimary();
204                    }
205                }
206            });
207            
208            disconnectButton.addActionListener(new ActionListener() {
209                public void actionPerformed(ActionEvent ae) {
210                    try {
211                        disconnect(getCurrentConnection());
212                    } catch (Exception e) { 
213                        showException(e); 
214                    }
215                }
216            });
217            
218        }
219        
220        /** Used in connect button handler ... got a better idea? */
221        private TestPanel getThis() {
222            return this;
223        }
224        
225        /** 
226         * Returns this TestPanel's underlying GenericParser.  This method is needed 
227         * by the HL7Service that the panel uses, so that it can share the parser and 
228         * use whatever encoding it is using. 
229         */
230        protected Parser getParser() {
231            return this.parser;
232        }
233        
234        /**
235         * Adds a title to a component by putting it in another panel, adding the '
236         * title, and returning the new panel. 
237         */
238        private static JPanel addTitle(Component toBeTitled, String title) {
239            JPanel newPanel = new JPanel(new BorderLayout());
240            newPanel.add(toBeTitled, BorderLayout.CENTER);
241            newPanel.add(new JLabel(title), BorderLayout.NORTH);
242            return newPanel;
243        }
244        
245        /**
246         * Displays an exception in a standard way.  All exceptions for TestPanel are 
247         * routed here.  Currently this prints to std err, and displays an error dialog, 
248         * but it could be changed. 
249         */
250        public void showException(Exception e) {
251            JOptionPane.showMessageDialog(this, e.getMessage(), e.getClass().getName(), JOptionPane.ERROR_MESSAGE);
252            
253            log.error("showException: ",e);
254        }
255        
256        /**
257         * Attempts to parse the contents of the outbound message text box into a 
258         * message object and display in the outbound message tree.
259         */
260        public void parseOutbound() throws HL7Exception, EncodingNotSupportedException {
261            //replace all the line feeds in the text area with carriage returns ... 
262            String messageString = this.outboundText.getText().replace('\n', '\r');
263            Message out = parser.parse(messageString);
264            this.outboundTree.setMessage(out);       
265    
266            if (messages.getDividerLocation() < 0) messages.setDividerLocation(0.5);
267            this.validate();
268        }
269        
270        /** Returns the Connection that is currently selected */
271        private Connection getCurrentConnection() { 
272            Connection conn = null;
273            Object o = connList.getSelectedValue();
274            if (o != null) conn = (Connection) o;
275            return conn;
276        }
277        
278        /** 
279         * Sets up a connection to a remote server that uses the minimal lower layer 
280         * protocol, and this TestPanel's GenericParser. 
281         */
282        public void connect(String host, int port) throws IOException, LLPException {
283            Socket s = new Socket(host, port);
284            addConnection(new Connection(this.parser, LowerLayerProtocol.makeLLP(), s));      
285        }
286        
287        /** 
288         * Sets up a connection to a remote server that uses the minimal lower layer 
289         * protocol, and this TestPanel's GenericParser. 
290         */
291        public void connect(String host, int inboundPort, int outboundPort) throws IOException, LLPException {
292            Socket in = new Socket(host, inboundPort);
293            Socket out = new Socket(host, outboundPort);
294            addConnection(new Connection(this.parser, LowerLayerProtocol.makeLLP(), in, out));      
295        }
296    
297        /** Notification that a new Connection has arrived at an HL7Service. */ 
298        public void connectionReceived(ca.uhn.hl7v2.app.Connection connection) {
299            addConnection(connection);
300        }
301        
302        public void connectionDiscarded(Connection connection) {
303            connections.remove(connection);
304            connList.setListData(connections);
305        }
306        
307        /** Adds the given connection to the connection list */ 
308        private void addConnection(Connection c) {
309            c.getResponder().registerApplication(this.router);
310            connections.add(c);
311            connList.setListData(connections);
312        }
313        
314        private void disconnect(Connection c) {
315            if (c != null) { 
316                c.close();
317                connections.remove(c);
318                connList.setListData(connections);
319            }
320        }
321        
322        /** 
323         * Returns the MessageTypeRouter associated with this TestPanel.  Every Connection 
324         * that a TestPanel uses routes unsolicited messages through this MessageTypeRouter.  
325         * Applications can be registered with the router using registerApplication().
326         */
327        public MessageTypeRouter getRouter() {
328            return this.router;
329        }
330    
331        /** 
332         * Sends the message that is currently displayed in the outbound tree to the
333         * remote system that is currently connected.  
334         */
335        public void sendAndRecieve() throws HL7Exception, LLPException, IOException {
336            Message outbound = this.outboundTree.getMessage();
337            Message inbound;
338            try {
339                inbound = getCurrentConnection().getInitiator().sendAndReceive(outbound);
340            } catch (NullPointerException e) { throw new IOException("Please select a Connection."); }
341            this.inboundTree.setMessage(inbound);
342            this.validate();
343        }
344        
345        /**
346         * Encodes the message that is currently displayed in the tree into a 
347         * traditionally encoded message string and displays in the inbound message text box.
348         */
349        public void encodeInbound() throws HL7Exception {
350            String inbound = this.parser.encode(this.inboundTree.getMessage());
351            inbound = inbound.replace('\r', '\n');
352            this.inboundText.setText(inbound);
353        }
354        
355        /**
356         * Encodes the message that is currently displayed in the outbound tree into a 
357         * traditionally encoded message string and displays in a new window.
358         */
359        public void encodeOutbound() throws HL7Exception {
360            String outbound = this.parser.encode(this.outboundTree.getMessage());
361            outbound = outbound.replace('\r', '\n');
362            this.openTextWindow("Outbound Message", outbound);
363        }
364        
365        
366        
367        /**
368         * Encodes the message that is currently displayed in the tree into an 
369         * XML encoded message string and displays in the inbound message text box.
370         */
371        /*public void xmlEncodeInbound() throws HL7Exception {
372            String inbound = this.xparser.encode(this.inboundTree.getMessage());
373            this.inboundText.setText(inbound);        
374        }*/
375        
376        /** A wrapper that lets us put a Connection in a JList */
377        private class ConnectionLabel extends JLabel {      
378            private Connection conn;
379            
380            public ConnectionLabel(Connection c) {
381                super("Connection: " + c.toString());
382                conn = c;
383            } 
384            
385            public Connection getConnection() {
386                return conn;
387            }
388        }
389        
390        /**
391         * Opens a new window for displaying text (intended for displaying 
392         * encoded messages. 
393         */
394        public static void openTextWindow(String title, String text) {
395            JFrame frame = new JFrame(title);
396            
397            try {
398                frame.getContentPane().setLayout(new BorderLayout());
399                JTextArea textArea = new JTextArea(text); 
400                JScrollPane scroll = new JScrollPane(textArea);            
401                frame.getContentPane().add(scroll, BorderLayout.CENTER);
402                
403                frame.pack();
404                frame.setVisible(true);
405            } catch (Exception e) {
406                System.err.println("Can't display text in new window: " + e.getMessage());
407            }
408        }
409            
410        public static void main(String args[]) {
411    
412            if (args.length > 2) {
413                System.out.println("Usage: ca.uhn.hl7v2.app.TestPanel [inbound_port [outbound_port] ]");
414                System.out.println("    If port numbers are provided, an HL7Server will be started, to listen for incoming connections.");
415                System.out.println("    If outbound port is not provided, inbound and outbound messages will use the same port.");            
416                System.exit(1);
417            }
418            
419            
420            //show a TestPanel in a window
421            JFrame frame = new JFrame("Message Tester");
422    
423            try {
424                TestPanel panel = new TestPanel();
425                
426                try { 
427                    if (args.length > 0) {
428                        HL7Service service = null;
429                        GenericParser parser = new GenericParser();
430                        LowerLayerProtocol llp = LowerLayerProtocol.makeLLP();
431                        int inPort = Integer.parseInt(args[0]);
432                        if (args.length > 1) {
433                            int outPort = Integer.parseInt(args[1]);
434                            service = new TwoPortService(panel.getParser(), llp, inPort, outPort);
435                        } else {
436                            service = new SimpleServer(inPort, llp, panel.getParser());
437                        }
438                        service.registerConnectionListener(panel);
439                        service.start();
440                    }
441                    
442                } catch (NumberFormatException nfe) {
443                    System.out.println("The given port number(s) are not valid integers");
444                    System.exit(1);
445                } catch (Exception e) {
446                    e.printStackTrace();
447                }
448                
449                frame.getContentPane().add(panel, BorderLayout.CENTER);
450    
451                //Finish setting up the frame
452                frame.addWindowListener(new WindowAdapter() {
453                        public void windowClosing(WindowEvent e) {
454                            System.exit(0);
455                    }
456                });
457    
458                frame.pack();
459                frame.setVisible(true);
460            } catch (Exception e) {
461                e.printStackTrace();
462            }
463        }
464    
465        
466    }