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 }