View Javadoc
1   /**
2    * The contents of this file are subject to the Mozilla Public License Version 1.1
3    * (the "License"); you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
5    * Software distributed under the License is distributed on an "AS IS" basis,
6    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
7    * specific language governing rights and limitations under the License.
8    *
9    * The Original Code is ""  Description:
10   * ""
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2001.  All Rights Reserved.
14   *
15   * Contributor(s): ______________________________________.
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the  "GPL"), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   */
26  package ca.uhn.hl7v2.testpanel.controller;
27  
28  import static org.apache.commons.lang.StringUtils.*;
29  
30  import java.awt.Component;
31  import java.awt.Dimension;
32  import java.awt.EventQueue;
33  import java.awt.Frame;
34  import java.beans.PropertyVetoException;
35  import java.io.BufferedWriter;
36  import java.io.File;
37  import java.io.FileOutputStream;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.io.InputStreamReader;
41  import java.io.OutputStreamWriter;
42  import java.io.Reader;
43  import java.net.MalformedURLException;
44  import java.net.ServerSocket;
45  import java.net.URL;
46  import java.nio.charset.Charset;
47  import java.text.SimpleDateFormat;
48  import java.util.Arrays;
49  import java.util.LinkedList;
50  import java.util.List;
51  import java.util.Properties;
52  import java.util.concurrent.ExecutorService;
53  import java.util.concurrent.Executors;
54  
55  import javax.swing.JFileChooser;
56  import javax.swing.JOptionPane;
57  import javax.swing.filechooser.FileFilter;
58  
59  import org.apache.commons.lang.StringUtils;
60  import org.apache.commons.lang.Validate;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  import ca.uhn.hl7v2.conf.ProfileException;
65  import ca.uhn.hl7v2.model.Message;
66  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
67  import ca.uhn.hl7v2.parser.GenericParser;
68  import ca.uhn.hl7v2.testpanel.model.ActivityIncomingMessage;
69  import ca.uhn.hl7v2.testpanel.model.ActivityMessage;
70  import ca.uhn.hl7v2.testpanel.model.MessagesList;
71  import ca.uhn.hl7v2.testpanel.model.conf.ProfileFileList;
72  import ca.uhn.hl7v2.testpanel.model.conf.ProfileGroup;
73  import ca.uhn.hl7v2.testpanel.model.conf.TableFileList;
74  import ca.uhn.hl7v2.testpanel.model.conn.InboundConnection;
75  import ca.uhn.hl7v2.testpanel.model.conn.InboundConnectionList;
76  import ca.uhn.hl7v2.testpanel.model.conn.OutboundConnection;
77  import ca.uhn.hl7v2.testpanel.model.conn.OutboundConnectionList;
78  import ca.uhn.hl7v2.testpanel.model.msg.Comment;
79  import ca.uhn.hl7v2.testpanel.model.msg.Hl7V2MessageBase;
80  import ca.uhn.hl7v2.testpanel.model.msg.Hl7V2MessageCollection;
81  import ca.uhn.hl7v2.testpanel.model.msg.Hl7V2MessageEr7;
82  import ca.uhn.hl7v2.testpanel.model.msg.Hl7V2MessageXml;
83  import ca.uhn.hl7v2.testpanel.ui.AddMessageDialog;
84  import ca.uhn.hl7v2.testpanel.ui.FileChooserOpenAccessory;
85  import ca.uhn.hl7v2.testpanel.ui.FileChooserSaveAccessory;
86  import ca.uhn.hl7v2.testpanel.ui.NothingSelectedPanel;
87  import ca.uhn.hl7v2.testpanel.ui.TestPanelWindow;
88  import ca.uhn.hl7v2.testpanel.ui.conn.CreateOutboundConnectionDialog;
89  import ca.uhn.hl7v2.testpanel.ui.conn.InboundConnectionPanel;
90  import ca.uhn.hl7v2.testpanel.ui.conn.OutboundConnectionPanel;
91  import ca.uhn.hl7v2.testpanel.ui.editor.Hl7V2MessageEditorPanel;
92  import ca.uhn.hl7v2.testpanel.util.AllFileFilter;
93  import ca.uhn.hl7v2.testpanel.util.ExtensionFilter;
94  import ca.uhn.hl7v2.testpanel.util.FileUtils;
95  import ca.uhn.hl7v2.testpanel.util.IOkCancelCallback;
96  import ca.uhn.hl7v2.testpanel.util.ISendProgressCallback;
97  import ca.uhn.hl7v2.testpanel.util.LineEndingsEnum;
98  import ca.uhn.hl7v2.testpanel.util.PortUtil;
99  import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
100 import ca.uhn.hl7v2.validation.impl.DefaultValidation;
101 
102 public class Controller {
103 
104 	static final String DIALOG_TITLE = "TestPanel";
105 	private static final Logger ourLog = LoggerFactory.getLogger(Controller.class);
106 	private String myAppVersionString;
107 	private JFileChooser myConformanceProfileFileChooser;
108 	private ExecutorService myExecutor;
109 	private InboundConnectionList myInboundConnectionList;
110 	private Object myLeftSelectedItem;
111 	private boolean myMessageEditorInFollowMode = true;
112 	private MessagesList myMessagesList;
113 	private Object myNothingSelectedMarker = new Object();
114 	private JFileChooser myOpenMessagesFileChooser;
115 	private FileChooserOpenAccessory myOpenMessagesFileChooserAccessory;
116 	private OutboundConnectionList myOutboundConnectionList;
117 	private ProfileFileList myProfileFileList;
118 	private ConformanceEditorController myProfilesAndTablesController;
119 	private LinkedList<Runnable> myQueuedTasks = new LinkedList<Runnable>();
120 	private JFileChooser mySaveMessagesFileChooser;
121 	private FileChooserSaveAccessory mySaveMessagesFileChooserAccessory;
122 	private TableFileList myTableFileList;
123 	private TestPanelWindow myView;
124 
125 	public Controller() {
126 		myTableFileList = new TableFileList();
127 		myProfileFileList = new ProfileFileList(myTableFileList);
128 
129 		myMessagesList = new MessagesList(this);
130 		try {
131 			File workfilesDir = Prefs.getTempWorkfilesDirectory();
132 			if (workfilesDir.exists() && workfilesDir.listFiles().length > 0) {
133 				ourLog.info("Restoring work files from directory: {}", workfilesDir.getAbsolutePath());
134 				myMessagesList.restoreFromWorkDirectory(workfilesDir);
135 			}
136 		} catch (IOException e1) {
137 			ourLog.error("Failed to restore from work direrctory", e1);
138 		}
139 
140 		String savedOutboundList = Prefs.getInstance().getOutboundConnectionList();
141 		if (StringUtils.isNotBlank(savedOutboundList)) {
142 			try {
143 				myOutboundConnectionList = OutboundConnectionList.fromXml(this, savedOutboundList);
144 			} catch (Exception e) {
145 				ourLog.error("Failed to load outbound connections from storage, going to create default value", e);
146 				createDefaultOutboundConnectionList();
147 			}
148 		}
149 
150 		if (myOutboundConnectionList == null || myOutboundConnectionList.getConnections().isEmpty()) {
151 			ourLog.info("No saved outbound connection list found");
152 			createDefaultOutboundConnectionList();
153 		}
154 
155 		String savedInboundList = Prefs.getInstance().getInboundConnectionList();
156 		if (StringUtils.isNotBlank(savedInboundList)) {
157 			try {
158 				myInboundConnectionList = InboundConnectionList.fromXml(this, savedInboundList);
159 			} catch (Exception e) {
160 				ourLog.error("Failed to load inbound connections from storage, going to create default value", e);
161 				createDefaultInboundConnectionList();
162 			}
163 		}
164 
165 		if (myInboundConnectionList == null || myInboundConnectionList.getConnections().isEmpty()) {
166 			ourLog.info("No saved inbound connection list found");
167 			createDefaultInboundConnectionList();
168 		}
169 
170 	}
171 
172 	public void addInboundConnection() {
173 		InboundConnection con = myInboundConnectionList.createDefaultConnection(provideRandomPort());
174 
175 		setLeftSelectedItem(con);
176 		myInboundConnectionList.addConnection(con);
177 	}
178 
179 	public void addMessage() {
180 		AddMessageDialog dialog = new AddMessageDialog(this);
181 		dialog.setModal(true);
182 		dialog.setVisible(true);
183 	}
184 
185 	/**
186 	 * Create a new message collection with a single instantiated message and
187 	 * add it to the list of available messages, then select it for editing.
188 	 */
189 	public void addMessage(String theVersion, String theType, String theTrigger, String theStructure, Hl7V2EncodingTypeEnum theEncoding) {
190 		DefaultModelClassFactory mcf = new DefaultModelClassFactory();
191 		try {
192 			Hl7V2MessageCollection col = new Hl7V2MessageCollection();
193 			col.setValidationContext(new DefaultValidation());
194 
195 			Class<? extends Message> messageClass = mcf.getMessageClass(theStructure, theVersion, true);
196 			ca.uhn.hl7v2.model.AbstractMessage message = (ca.uhn.hl7v2.model.AbstractMessage) messageClass.newInstance();
197 			message.initQuickstart(theType, theTrigger, "T");
198 
199 			GenericParser p = new GenericParser();
200 			Hl7V2MessageBase msg;
201 			if (theEncoding == Hl7V2EncodingTypeEnum.ER_7) {
202 				p.setPipeParserAsPrimary();
203 				col.setEncoding(Hl7V2EncodingTypeEnum.ER_7);
204 				msg = new Hl7V2MessageEr7();
205 				msg.setSourceMessage(p.encode(message));
206 			} else {
207 				p.setXMLParserAsPrimary();
208 				col.setEncoding(Hl7V2EncodingTypeEnum.XML);
209 				msg = new Hl7V2MessageXml();
210 				msg.setSourceMessage(p.encode(message));
211 			}
212 
213 			col.addMessage(new Comment(""));
214 
215 			msg.setIndexWithinCollection(1);
216 			col.addMessage(msg);
217 
218 			setLeftSelectedItem(col);
219 			myMessagesList.addMessage(col);
220 
221 		} catch (Exception e) {
222 			handleUnexpectedError(e);
223 		}
224 	}
225 
226 	public void addOutboundConnection() {
227 		OutboundConnection con = myOutboundConnectionList.createDefaultConnection(provideRandomPort());
228 
229 		setLeftSelectedItem(con);
230 		myOutboundConnectionList.addConnection(con);
231 	}
232 
233 	public void addOutboundConnectionToSendTo(final IOkCancelCallback<OutboundConnection> theHandler) {
234 		OutboundConnection connection = myOutboundConnectionList.createDefaultConnection(provideRandomPort());
235 
236 		final IOkCancelCallback<OutboundConnection> handler = new IOkCancelCallback<OutboundConnection>() {
237 			public void cancel(OutboundConnection theArg) {
238 				theHandler.cancel(theArg);
239 			}
240 
241 			public void ok(OutboundConnection theArg) {
242 				myOutboundConnectionList.addConnection(theArg);
243 				theHandler.ok(theArg);
244 			}
245 		};
246 
247 		CreateOutboundConnectionDialog dialog = new CreateOutboundConnectionDialog(this, connection, handler);
248 		dialog.setVisible(true);
249 	}
250 
251 	public void chooseAndLoadConformanceProfileForMessage(Hl7V2MessageCollection theMessage, IOkCancelCallback<Void> theCallback) {
252 		if (myConformanceProfileFileChooser == null) {
253 			myConformanceProfileFileChooser = new JFileChooser(Prefs.getInstance().getOpenPathConformanceProfile());
254 			myConformanceProfileFileChooser.setDialogTitle("Choose an HL7 Conformance Profile");
255 
256 			ExtensionFilter type = new ExtensionFilter("XML Files", new String[] { ".xml" });
257 			myConformanceProfileFileChooser.addChoosableFileFilter(type);
258 		}
259 
260 		int value = myConformanceProfileFileChooser.showOpenDialog(myView.getFrame());
261 		if (value == JFileChooser.APPROVE_OPTION) {
262 
263 			File file = myConformanceProfileFileChooser.getSelectedFile();
264 			Prefs.getInstance().setOpenPathConformanceProfile(file.getPath());
265 
266 			try {
267 				String profileString = FileUtils.readFile(file);
268 				theMessage.setRuntimeProfile(ProfileGroup.createFromRuntimeProfile(profileString));
269 
270 				theCallback.ok(null);
271 
272 			} catch (IOException e) {
273 				ourLog.error("Failed to load profile", e);
274 				theCallback.cancel(null);
275 			} catch (ProfileException e) {
276 				ourLog.error("Failed to load profile", e);
277 				theCallback.cancel(null);
278 			}
279 		} else {
280 			theCallback.cancel(null);
281 		}
282 
283 	}
284 
285 	public void close() {
286 
287 		if (!saveAllMessagesAndReturnFalseIfCancelIsPressed()) {
288 			return;
289 		}
290 
291 		myOutboundConnectionList.removeNonPersistantConnections();
292 		ourLog.info("Saving {} outbound connection descriptors", myOutboundConnectionList.getConnections().size());
293 		Prefs.getInstance().setOutboundConnectionList(myOutboundConnectionList.exportConfigToXml());
294 
295 		myInboundConnectionList.removeNonPersistantConnections();
296 		ourLog.info("Saving {} inbound connection descriptors", myInboundConnectionList.getConnections().size());
297 		Prefs.getInstance().setInboundConnectionList(myInboundConnectionList.exportConfigToXml());
298 
299 		try {
300 			
301 			ourLog.info("Flusing open messages to work directory");
302 			File workfilesDir = Prefs.getTempWorkfilesDirectory();
303 			myMessagesList.dumpToWorkDirectory(workfilesDir);
304 			
305 //			ourLog.info("Flushing imported profile group files");
306 //			myProfileFileList.dumpImportedToWorkDirectory(Prefs.getTempImportedProfileGroupFiles());
307 			
308 		} catch (IOException e) {
309 			ourLog.error("Failed to flush work directory!", e);
310 		}
311 
312 		myView.destroy();
313 
314 		
315 		// Do this last, since it destroys the executor service
316 		myExecutor.shutdown();
317 
318 		ourLog.info("TestPanel is exiting with status 0");
319 		System.exit(0);
320 	}
321 
322 	public void closeMessage(Hl7V2MessageCollection theMsg) {
323 		if (theMsg.isSaved() == false) {
324 			int save = showPromptToSaveMessageBeforeClosingIt(theMsg, true);
325 			switch (save) {
326 			case JOptionPane.YES_OPTION:
327 				if (!saveMessages(theMsg)) {
328 					return;
329 				}
330 				break;
331 			case JOptionPane.NO_OPTION:
332 				break;
333 			case JOptionPane.CANCEL_OPTION:
334 				return;
335 			default:
336 				// shouldn't happen
337 				throw new Error("invalid option:" + save);
338 			}
339 		}
340 
341 		updateRecentMessageFiles(theMsg);
342 
343 		myMessagesList.removeMessage(theMsg);
344 		if (myMessagesList.getMessages().size() > 0) {
345 			setLeftSelectedItem(myMessagesList.getMessages().get(0));
346 		} else {
347 			tryToSelectSomething();
348 		}
349 	}
350 
351 	private void createDefaultInboundConnectionList() {
352 		myInboundConnectionList = new InboundConnectionList();
353 		// myInboundConnectionList.addConnection(myInboundConnectionList.createDefaultConnection(9999));
354 	}
355 
356 	private void createDefaultOutboundConnectionList() {
357 		myOutboundConnectionList = new OutboundConnectionList();
358 		// myOutboundConnectionList.addConnection(myOutboundConnectionList.createDefaultConnection(9999));
359 	}
360 
361 	/**
362 	 * @return Returns true if the file is saved
363 	 */
364 	private boolean doSave(Hl7V2MessageCollection theSelectedValue) {
365 		Validate.notNull(theSelectedValue);
366 
367 		try {
368 			// BufferedWriter w = new BufferedWriter(new
369 			// FileWriterWithEncoding(theSelectedValue.getSaveFileName(),
370 			// theSelectedValue.getSaveCharset()));
371 
372 			File saveFile = new File(theSelectedValue.getSaveFileName());
373 			FileOutputStream fos = new FileOutputStream(saveFile);
374 			
375 			Charset saveCharset = theSelectedValue.getSaveCharset();
376 			BufferedWriter w;
377 			if (saveCharset != null) {
378 				w = new BufferedWriter(new OutputStreamWriter(fos, saveCharset));
379 			} else {
380 				w = new BufferedWriter(new OutputStreamWriter(fos));
381 			}
382 
383 			boolean saveStripComments = theSelectedValue.isSaveStripComments();
384 			LineEndingsEnum lineEndings = theSelectedValue.getSaveLineEndings();
385 
386 			theSelectedValue.writeToFile(w, saveStripComments, lineEndings);
387 
388 			w.close();
389 			fos.close();
390 
391 			theSelectedValue.setSaveFileTimestamp(saveFile.lastModified());
392 
393 			ourLog.info("Saved " + theSelectedValue.getMessages().size() + " messages to " + theSelectedValue.getSaveFileName());
394 			theSelectedValue.setSaved(true);
395 			return true;
396 
397 		} catch (IOException e) {
398 			ourLog.error("Failed to save file", e);
399 			showDialogError("Failed to save file: " + e.getMessage());
400 			return false;
401 		}
402 
403 	}
404 
405 	public void editMessages(List<ActivityMessage> theList) {
406 		Validate.notEmpty(theList);
407 
408 		Hl7V2MessageCollection messageCollection = new Hl7V2MessageCollection();
409 
410 		int index = 0;
411 		for (ActivityMessage next : theList) {
412 			Hl7V2MessageBase nextModel;
413 			if (next.getEncoding() == Hl7V2EncodingTypeEnum.ER_7) {
414 				nextModel = new Hl7V2MessageEr7();
415 			} else {
416 				nextModel = new Hl7V2MessageXml();
417 			}
418 
419 			nextModel.setEncoding(next.getEncoding());
420 			try {
421 				nextModel.setSourceMessage(next.getRawMessage());
422 			} catch (PropertyVetoException e) {
423 				ourLog.error("Failed to create message object", e);
424 				continue;
425 			}
426 
427 			nextModel.setIndexWithinCollection(index++);
428 
429 			StringBuilder b = new StringBuilder();
430 			if (index > 1) {
431 				b.append("\n");
432 			}
433 
434 			if (next instanceof ActivityIncomingMessage) {
435 				b.append("Received ");
436 			} else {
437 				b.append("Sent ");
438 			}
439 
440 			String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSS").format(next.getTimestamp());
441 			b.append(timestamp);
442 
443 			messageCollection.addComment(b.toString());
444 
445 			messageCollection.addMessage(nextModel);
446 
447 		}
448 
449 		setLeftSelectedItem(messageCollection);
450 		myMessagesList.addMessage(messageCollection);
451 
452 	}
453 
454 	public String getAppVersionString() {
455 		if (myAppVersionString == null) {
456 			// FileUtils.loadResourceFromClasspath(thePath)
457 			Properties prop = new Properties();
458 			try {
459 				prop.load(Controller.class.getClassLoader().getResourceAsStream("testpanelversion.properties"));
460 				myAppVersionString = prop.getProperty("app.version");
461 			} catch (IOException e) {
462 				ourLog.error("Couldn't load version property", e);
463 				myAppVersionString = "v.UNK";
464 			}
465 		}
466 		return myAppVersionString;
467 	}
468 
469 	/**
470 	 * @return the inboundConnectionList
471 	 */
472 	public InboundConnectionList getInboundConnectionList() {
473 		return myInboundConnectionList;
474 	}
475 
476 	public Object getLeftSelectedItem() {
477 		return myLeftSelectedItem;
478 	}
479 
480 	public MessagesList getMessagesList() {
481 		return myMessagesList;
482 	}
483 
484 	public OutboundConnectionList getOutboundConnectionList() {
485 		return myOutboundConnectionList;
486 	}
487 
488 	public ProfileFileList getProfileFileList() {
489 		return myProfileFileList;
490 	}
491 
492 	/**
493 	 * @return the tableFileList
494 	 */
495 	public TableFileList getTableFileList() {
496 		return myTableFileList;
497 	}
498 
499 	public Frame getWindow() {
500 		return myView.getFrame();
501 	}
502 
503 	private void handleUnexpectedError(Exception theE) {
504 		ourLog.error(theE.getMessage(), theE);
505 		showDialogError(theE.getMessage());
506 	}
507 
508 	public void invokeInBackground(Runnable theRunnable) {
509 		if (myExecutor != null) {
510 			myExecutor.execute(theRunnable);
511 		} else {
512 			myQueuedTasks.add(theRunnable);
513 		}
514 	}
515 
516 	public boolean isMessageEditorInFollowMode() {
517 		return myMessageEditorInFollowMode;
518 	}
519 
520 	private void openMessageFile(File file, Charset theCharset) {
521 		try {
522 			String profileString = FileUtils.readFile(file, theCharset);
523 			Hl7V2MessageCollection col = new Hl7V2MessageCollection();
524 
525 			col.setSourceMessage(profileString);
526 			col.setSaveFileName(file.getAbsolutePath());
527 			col.setSaved(true);
528 
529 			if (col.getMessages().isEmpty()) {
530 				showDialogError("No messages were found in the file");
531 			} else {
532 
533 				setLeftSelectedItem(col);
534 				myMessagesList.addMessage(col);
535 
536 			}
537 		} catch (IOException e) {
538 			ourLog.error("Failed to load profile", e);
539 		}
540 	}
541 
542 	public void openMessages() {
543 		if (myOpenMessagesFileChooser == null) {
544 			myOpenMessagesFileChooser = new JFileChooser(Prefs.getInstance().getOpenPathMessages());
545 			myOpenMessagesFileChooserAccessory = new FileChooserOpenAccessory();
546 			myOpenMessagesFileChooser.setAccessory(myOpenMessagesFileChooserAccessory);
547 			myOpenMessagesFileChooser.setDialogTitle("Choose a file containing HL7 messages");
548 
549 			FileFilter type = new ExtensionFilter("HL7 Files", new String[] { ".hl7" });
550 			myOpenMessagesFileChooser.addChoosableFileFilter(type);
551 
552 			type = new ExtensionFilter("XML Files", new String[] { ".xml" });
553 			myOpenMessagesFileChooser.addChoosableFileFilter(type);
554 
555 			type = new AllFileFilter();
556 			myOpenMessagesFileChooser.addChoosableFileFilter(type);
557 		}
558 
559 		int value = myOpenMessagesFileChooser.showOpenDialog(myView.getFrame());
560 		if (value == JFileChooser.APPROVE_OPTION) {
561 
562 			File file = myOpenMessagesFileChooser.getSelectedFile();
563 			Prefs.getInstance().setOpenPathMessages(file.getPath());
564 
565 			openMessageFile(file, myOpenMessagesFileChooserAccessory.getSelectedCharset());
566 		}
567 
568 	}
569 
570 	public void openOrSwitchToMessage(Hl7V2MessageCollection theFile) {
571 		for (Hl7V2MessageCollection next : myMessagesList.getMessages()) {
572 			if (theFile.equals(next.getSaveFileName())) {
573 				setLeftSelectedItem(next);
574 				return;
575 			}
576 		}
577 
578 		File file = new File(theFile.getSaveFileName());
579 		if (file.exists() == false) {
580 			ourLog.error("Can't find file: {}", theFile);
581 		}
582 
583 		openMessageFile(file, theFile.getSaveCharset());
584 	}
585 
586 	public void populateWithSampleMessageAndConnections() {
587 
588 		// Create a new messsage and add it to the list of messages
589 		
590 		Hl7V2MessageCollection col = new Hl7V2MessageCollection();
591 		col.setValidationContext(new DefaultValidation());
592 
593 		String message = "MSH|^~\\&|NES|NINTENDO|TESTSYSTEM|TESTFACILITY|20010101000000||ADT^A04|Q123456789T123456789X123456|P|2.3\r" + "EVN|A04|20010101000000|||^KOOPA^BOWSER^^^^^^^CURRENT\r"
594 				+ "PID|1||123456789|0123456789^AA^^JP|BROS^MARIO^^^^||19850101000000|M|||123 FAKE STREET^MARIO \\T\\ LUIGI BROS PLACE^TOADSTOOL KINGDOM^NES^A1B2C3^JP^HOME^^1234|1234|(555)555-0123^HOME^JP:1234567|||S|MSH|12345678|||||||0|||||N\r"
595 				+ "NK1|1|PEACH^PRINCESS^^^^|SO|ANOTHER CASTLE^^TOADSTOOL KINGDOM^NES^^JP|(123)555-1234|(123)555-2345|NOK|||||||||||||\r" + "NK1|2|TOADSTOOL^PRINCESS^^^^|SO|YET ANOTHER CASTLE^^TOADSTOOL KINGDOM^NES^^JP|(123)555-3456|(123)555-4567|EMC|||||||||||||\r"
596 				+ "PV1|1|O|ABCD^EFGH^|||^^|123456^DINO^YOSHI^^^^^^MSRM^CURRENT^^^NEIGHBOURHOOD DR NBR^|^DOG^DUCKHUNT^^^^^^^CURRENT||CRD|||||||123456^DINO^YOSHI^^^^^^MSRM^CURRENT^^^NEIGHBOURHOOD DR NBR^|AO|0123456789|1|||||||||||||||||||MSH||A|||20010101000000\r"
597 				+ "IN1|1|PAR^PARENT||||LUIGI\r" + "IN1|2|FRI^FRIEND||||PRINCESS";
598 		col.setEncoding(Hl7V2EncodingTypeEnum.ER_7);
599 		col.setSourceMessage(message);
600 		myMessagesList.addMessage(col);
601 
602 		// FInd a free port
603 		int port = PortUtil.findFreePort();
604 
605 		// Create two connections
606 		
607 		InboundConnection iCon = myInboundConnectionList.createDefaultConnection(port);
608 		iCon.setPersistent(true);
609 		myInboundConnectionList.addConnection(iCon);
610 
611 		OutboundConnection oCon = myOutboundConnectionList.createDefaultConnection(port);
612 		oCon.setPersistent(true);
613 		myOutboundConnectionList.addConnection(oCon);
614 
615 		// Select the new message 
616 		
617 		setLeftSelectedItem(col);
618 	}
619 
620 	/**
621 	 * Provide a random, currently unused port
622 	 */
623 	private int provideRandomPort() {
624 		ServerSocket server;
625 		try {
626 			server = new ServerSocket(0);
627 			int port = server.getLocalPort();
628 			server.close();
629 			return port;
630 		} catch (IOException e) {
631 			throw new Error(e);
632 		}
633 	}
634 
635 	private Component provideViewFrameIfItExists() {
636 		return myView != null ? myView.getFrame() : null;
637 	}
638 
639 	public void removeInboundConnection(InboundConnection theConnection) {
640 		myInboundConnectionList.removeConnecion(theConnection);
641 		if (myInboundConnectionList.getConnections().size() > 0) {
642 			setLeftSelectedItem(myInboundConnectionList.getConnections().get(0));
643 		} else {
644 			tryToSelectSomething();
645 		}
646 	}
647 
648 	public void removeOutboundConnection(OutboundConnection theConnection) {
649 		myOutboundConnectionList.removeConnecion(theConnection);
650 		if (myOutboundConnectionList.getConnections().size() > 0) {
651 			setLeftSelectedItem(myOutboundConnectionList.getConnections().get(0));
652 		} else {
653 			tryToSelectSomething();
654 		}
655 	}
656 
657 	public void revertMessage(Hl7V2MessageCollection theMsg) {
658 		if (StringUtils.isBlank(theMsg.getSaveFileName())) {
659 			showDialogError("Message has not yet been saved");
660 			return;
661 		}
662 
663 		File file = new File(theMsg.getSaveFileName());
664 		if (file.exists() == false || file.isDirectory() || !file.canRead()) {
665 			showDialogError("File \"" + theMsg.getSaveFileName() + "\" can not be read");
666 			return;
667 		}
668 
669 		int revert = showDialogYesNo("Revert file to saved contents? You will lose all changes.");
670 		if (revert == JOptionPane.NO_OPTION) {
671 			return;
672 		}
673 
674 		Charset charSet = theMsg.getSaveCharset();
675 		String contents;
676 		try {
677 			contents = FileUtils.readFile(file, charSet);
678 		} catch (IOException e) {
679 			ourLog.error("Failed to read from file " + file.getAbsolutePath(), e);
680 			showDialogError("Failed to read from file: " + e.getMessage());
681 			return;
682 		}
683 
684 		theMsg.setSourceMessage(contents);
685 
686 	}
687 
688 	public boolean saveAllMessagesAndReturnFalseIfCancelIsPressed() {
689 
690 		for (Hl7V2MessageCollection next : myMessagesList.getMessages()) {
691 			if (next.isSaved() == false) {
692 				int save = showPromptToSaveMessageBeforeClosingIt(next, true);
693 				switch (save) {
694 				case JOptionPane.YES_OPTION:
695 					if (!saveMessages(next)) {
696 						return false;
697 					}
698 					break;
699 				case JOptionPane.NO_OPTION:
700 					break;
701 				case JOptionPane.CANCEL_OPTION:
702 					return false;
703 				default:
704 					// shouldn't happen
705 					throw new Error("invalid option:" + save);
706 				}
707 			}
708 		}
709 
710 		return true;
711 	}
712 
713 	public boolean saveMessages(Hl7V2MessageCollection theSelectedValue) {
714 		Validate.notNull(theSelectedValue);
715 
716 		if (theSelectedValue.getSaveFileName() == null) {
717 			return saveMessagesAs(theSelectedValue);
718 		} else {
719 			return doSave(theSelectedValue);
720 		}
721 
722 	}
723 
724 	/**
725 	 * Prompt for a filename and save the currently selected messages
726 	 * 
727 	 * @return Returns true if the file is saved
728 	 */
729 	public boolean saveMessagesAs(Hl7V2MessageCollection theSelectedValue) {
730 		Validate.notNull(theSelectedValue);
731 
732 		if (mySaveMessagesFileChooser == null) {
733 			mySaveMessagesFileChooser = new JFileChooser(Prefs.getInstance().getSavePathMessages());
734 			mySaveMessagesFileChooser.setDialogTitle("Choose a file to save the current message(s) to");
735 			mySaveMessagesFileChooserAccessory = new FileChooserSaveAccessory();
736 			mySaveMessagesFileChooser.setAccessory(mySaveMessagesFileChooserAccessory);
737 
738 			FileFilter type = new ExtensionFilter("HL7 Files", new String[] { ".hl7" });
739 			mySaveMessagesFileChooser.addChoosableFileFilter(type);
740 
741 			type = new ExtensionFilter("XML Files", new String[] { ".xml" });
742 			mySaveMessagesFileChooser.addChoosableFileFilter(type);
743 
744 			type = new AllFileFilter();
745 			mySaveMessagesFileChooser.addChoosableFileFilter(type);
746 
747 			mySaveMessagesFileChooser.setPreferredSize(new Dimension(700, 500));
748 		}
749 
750 		int value = mySaveMessagesFileChooser.showSaveDialog(myView.getFrame());
751 		if (value == JFileChooser.APPROVE_OPTION) {
752 
753 			File file = mySaveMessagesFileChooser.getSelectedFile();
754 			Prefs.getInstance().setSavePathMessages(file.getPath());
755 
756 			if (!file.getName().contains(".")) {
757 				switch (theSelectedValue.getEncoding()) {
758 				case ER_7:
759 					file = new File(file.getAbsolutePath() + ".hl7");
760 					break;
761 				case XML:
762 					file = new File(file.getAbsolutePath() + ".xml");
763 					break;
764 				}
765 			}
766 
767 			if (file.exists()) {
768 				String message = "The file \"" + file.getName() + "\" already exists. Do you wish to overwrite it?";
769 				int confirmed = showDialogYesNo(message);
770 				if (confirmed == JOptionPane.NO_OPTION) {
771 					return false;
772 				}
773 
774 				ourLog.info("Deleting file: {}", file.getAbsolutePath());
775 				file.delete();
776 			}
777 
778 			theSelectedValue.setSaveCharset(mySaveMessagesFileChooserAccessory.getSelectedCharset());
779 			theSelectedValue.setSaveFileName(file.getAbsolutePath());
780 
781 			theSelectedValue.setSaveStripComments(mySaveMessagesFileChooserAccessory.isSelectedSaveStripComments());
782 			theSelectedValue.setSaveLineEndings(mySaveMessagesFileChooserAccessory.getSelectedLineEndings());
783 
784 			doSave(theSelectedValue);
785 
786 			return true;
787 
788 		} else {
789 
790 			return false;
791 
792 		}
793 	}
794 
795 	/**
796 	 * Send one or more messages out over an interface
797 	 * 
798 	 * @param theITransmissionCallback
799 	 */
800 	public void sendMessages(OutboundConnection theConnection, Hl7V2MessageCollection theMessage, ISendProgressCallback theTransmissionCallback) {
801 		theConnection.sendMessages(theMessage, theTransmissionCallback);
802 	}
803 
804 	public void setLeftSelectedItem(Object theSelectedValue) {
805 		if (myLeftSelectedItem == theSelectedValue) {
806 			return;
807 		}
808 
809 		String id = null;
810 
811 		myLeftSelectedItem = theSelectedValue;
812 		if (myLeftSelectedItem instanceof Hl7V2MessageCollection) {
813 			Hl7V2MessageEditorPanel hl7v2MessageEditorPanel = new Hl7V2MessageEditorPanel(this);
814 			Hl7V2MessageCollection collection = (Hl7V2MessageCollection) myLeftSelectedItem;
815 			hl7v2MessageEditorPanel.setMessage(collection);
816 			myView.setMainPanel(hl7v2MessageEditorPanel);
817 			id = collection.getId();
818 		} else if (myLeftSelectedItem instanceof OutboundConnection) {
819 			OutboundConnectionPanel panel = new OutboundConnectionPanel(this);
820 			panel.setController(this);
821 			OutboundConnection connection = (OutboundConnection) myLeftSelectedItem;
822 			panel.setConnection(connection);
823 			id = connection.getId();
824 			myView.setMainPanel(panel);
825 		} else if (myLeftSelectedItem instanceof InboundConnection) {
826 			InboundConnectionPanel panel = new InboundConnectionPanel(this);
827 			InboundConnection connection = (InboundConnection) myLeftSelectedItem;
828 			panel.setConnection(connection);
829 			myView.setMainPanel(panel);
830 			id = connection.getId();
831 		} else if (myLeftSelectedItem == myNothingSelectedMarker) {
832 			myView.setMainPanel(new NothingSelectedPanel(this));
833 		}
834 
835 		if (id != null) {
836 			Prefs.getInstance().setMostRecentlySelectedItemId(id);
837 		}
838 	}
839 
840 	/**
841 	 * @param theMessageEditorInFollowMode
842 	 *            the messageEditorInFollowMode to set
843 	 */
844 	public void setMessageEditorInFollowMode(boolean theMessageEditorInFollowMode) {
845 		myMessageEditorInFollowMode = theMessageEditorInFollowMode;
846 	}
847 
848 	public void showAboutDialog() {
849 		myView.showAboutDialog();
850 	}
851 
852 	public void showDialogError(String message) {
853 		JOptionPane.showMessageDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.ERROR_MESSAGE);
854 	}
855 
856 	public void showDialogInfo(String message) {
857 		JOptionPane.showMessageDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.INFORMATION_MESSAGE);
858 	}
859 
860 	public void showDialogWarning(String message) {
861 		JOptionPane.showMessageDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.WARNING_MESSAGE);
862 	}
863 
864 	public int showDialogYesNo(String message) {
865 		return JOptionPane.showConfirmDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
866 	}
867 
868 	public void showProfilesAndTablesEditor() {
869 		if (myProfilesAndTablesController == null) {
870 			myProfilesAndTablesController = new ConformanceEditorController(this);
871 		}
872 		myProfilesAndTablesController.show();
873 	}
874 
875 	private int showPromptToSaveMessageBeforeClosingIt(Hl7V2MessageCollection theMsg, boolean theShowCancelButton) {
876 		Component parentComponent = myView.getFrame();
877 		Object message = "<html>The following file is unsaved, do you want to save before closing?<br>" + theMsg.getBestDescription() + "</html>";
878 		String title = DIALOG_TITLE;
879 		int optionType = theShowCancelButton ? JOptionPane.YES_NO_CANCEL_OPTION : JOptionPane.YES_NO_OPTION;
880 		int messageType = JOptionPane.QUESTION_MESSAGE;
881 		return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType);
882 	}
883 
884 	public void start() {
885 		ourLog.info("Starting TestPanel Controller...");
886 
887 		myExecutor = Executors.newSingleThreadExecutor();
888 		for (Runnable next : myQueuedTasks) {
889 			myExecutor.execute(next);
890 		}
891 		myQueuedTasks = null;
892 
893 		myView = new TestPanelWindow(this);
894 		myView.getFrame().setVisible(true);
895 
896 		String leftItemId = Prefs.getInstance().getMostRecentlySelectedItemId();
897 		if (isNotBlank(leftItemId)) {
898 			Object leftItem = myMessagesList.getWithId(leftItemId);
899 			leftItem = (leftItem != null) ? leftItem : myOutboundConnectionList.getWithId(leftItemId);
900 			leftItem = (leftItem != null) ? leftItem : myInboundConnectionList.getWithId(leftItemId);
901 			if (leftItem != null) {
902 				setLeftSelectedItem(leftItem);
903 			}
904 		}
905 
906 		if (getLeftSelectedItem() == null) {
907 			if (myMessagesList.getMessages().size() > 0) {
908 				setLeftSelectedItem(myMessagesList.getMessages().get(0));
909 			} else {
910 				setLeftSelectedItem(myNothingSelectedMarker);
911 			}
912 		}
913 
914 		new VersionChecker().start();
915 		
916 		Prefs.getInstance().setController(this);
917 		
918 	}
919 
920 	public void startAllInboundConnections() {
921 		ourLog.info("Starting all inbound connections");
922 		for (InboundConnection next : myInboundConnectionList.getConnections()) {
923 			next.start();
924 		}
925 	}
926 
927 	public void startAllOutboundConnections() {
928 		ourLog.info("Starting all outbound connections");
929 		for (OutboundConnection next : myOutboundConnectionList.getConnections()) {
930 			next.start();
931 		}
932 	}
933 
934 	public void startInboundConnection(InboundConnection theLeftSelectedItem) {
935 		theLeftSelectedItem.start();
936 	}
937 
938 	public void startOutboundConnection(OutboundConnection theLeftSelectedItem) {
939 		theLeftSelectedItem.start();
940 	}
941 
942 	public void stopAllInboundConnections() {
943 		ourLog.info("Stopping all inbound connections");
944 		for (InboundConnection next : myInboundConnectionList.getConnections()) {
945 			next.stop();
946 		}
947 	}
948 
949 	public void stopAllOutboundConnections() {
950 		ourLog.info("Stopping all outbound connections");
951 		for (OutboundConnection next : myOutboundConnectionList.getConnections()) {
952 			next.stop();
953 		}
954 	}
955 
956 	private void tryToSelectSomething() {
957 		if (myMessagesList.getMessages().size() > 0) {
958 			setLeftSelectedItem(myMessagesList.getMessages().get(0));
959 		} else if (myOutboundConnectionList.getConnections().size() > 0) {
960 			setLeftSelectedItem(myOutboundConnectionList.getConnections().get(0));
961 		} else if (myInboundConnectionList.getConnections().size() > 0) {
962 			setLeftSelectedItem(myInboundConnectionList.getConnections().get(0));
963 		} else {
964 			setLeftSelectedItem(myNothingSelectedMarker);
965 		}
966 	}
967 
968 	private void updateRecentMessageFiles(Hl7V2MessageCollection theMessage) {
969 		Prefs.getInstance().addMessagesFileXmlToRecents(myProfileFileList, Arrays.asList(theMessage));
970 		if (myView != null) {
971 			myView.setRecentMessageFiles(Prefs.getInstance().getRecentMessageXmlFiles(myProfileFileList));
972 		}
973 	}
974 
975 	public boolean validateNewValue(String theTerserPath, String theNewValue) {
976 		String errorMsg = null;
977 		if (theTerserPath.endsWith("MSH-1")) {
978 			if (theNewValue.length() != 1) {
979 				errorMsg = "MSH-1 must be exactly 1 character";
980 			}
981 		}
982 
983 		if (theTerserPath.endsWith("MSH-2")) {
984 			if (theNewValue.length() != 4) {
985 				errorMsg = "MSH-2 must be exactly 4 characters";
986 			}
987 		}
988 
989 		if (errorMsg != null) {
990 			showDialogError(errorMsg);
991 			return false;
992 		}
993 
994 		return true;
995 	}
996 
997 	/**
998 	 * Thread which checks if we are running the latest version of the TestPanel
999 	 */
1000 	private class VersionChecker extends Thread {
1001 
1002 		@Override
1003 		public void run() {
1004 			String version = getAppVersionString();
1005 			if (version.contains("$")) {
1006 				version = "1.0";
1007 			}
1008 
1009 			boolean isWebstart = true;
1010 			try {
1011 				Class.forName("javax.jnlp.ServiceManager");
1012 			} catch (Throwable t) {
1013 				isWebstart = false;
1014 			}
1015 
1016 			try {
1017 				String javaVersion = System.getProperty("java.version");
1018 				String os = System.getProperty("os.name").replace(" ", "+");
1019 
1020 				URL url = new URL("http://hl7api.sourceforge.net/cgi-bin/testpanelversion.cgi?version=" + version + "&java=" + javaVersion + "&os=" + os + "&webstart=" + isWebstart + "&end");
1021 				InputStream is = (InputStream) url.getContent();
1022 				Reader reader = new InputStreamReader(is, "US-ASCII");
1023 				String content = FileUtils.readFromReaderIntoString(reader);
1024 				if (content.contains("OK")) {
1025 					ourLog.info("HAPI TestPanel is up to date. Great!");
1026 				} else if (content.contains("ERRORNOE ")) {
1027 					final String message = content.replace("ERRORNOE ", "");
1028 					ourLog.warn(message);
1029 					EventQueue.invokeLater(new Runnable() {
1030 						public void run() {
1031 							showDialogWarning(message);
1032 						}
1033 					});
1034 				} else {
1035 					ourLog.warn(content);
1036 				}
1037 			} catch (MalformedURLException e) {
1038 				ourLog.warn("Couldn't parse version checker URL", e);
1039 			} catch (IOException e) {
1040 				ourLog.info("Failed to check if we are running the latest version");
1041 			}
1042 		}
1043 
1044 	}
1045 
1046 }