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 		Hl7V2MessageCollection col = new Hl7V2MessageCollection();
588 		col.setValidationContext(new DefaultValidation());
589 
590 		String message = "MSH|^~\\&|NES|NINTENDO|TESTSYSTEM|TESTFACILITY|20010101000000||ADT^A04|Q123456789T123456789X123456|P|2.3\r" + "EVN|A04|20010101000000|||^KOOPA^BOWSER^^^^^^^CURRENT\r"
591 				+ "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"
592 				+ "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"
593 				+ "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"
594 				+ "IN1|1|PAR^PARENT||||LUIGI\r" + "IN1|2|FRI^FRIEND||||PRINCESS";
595 		col.setEncoding(Hl7V2EncodingTypeEnum.ER_7);
596 		col.setSourceMessage(message);
597 		myMessagesList.addMessage(col);
598 
599 		int port = PortUtil.findFreePort();
600 
601 		InboundConnection iCon = myInboundConnectionList.createDefaultConnection(port);
602 		iCon.setPersistent(true);
603 		myInboundConnectionList.addConnection(iCon);
604 
605 		OutboundConnection oCon = myOutboundConnectionList.createDefaultConnection(port);
606 		oCon.setPersistent(true);
607 		myOutboundConnectionList.addConnection(oCon);
608 
609 		setLeftSelectedItem(col);
610 	}
611 
612 	/**
613 	 * Provide a random, currently unused port
614 	 */
615 	private int provideRandomPort() {
616 		ServerSocket server;
617 		try {
618 			server = new ServerSocket(0);
619 			int port = server.getLocalPort();
620 			server.close();
621 			return port;
622 		} catch (IOException e) {
623 			throw new Error(e);
624 		}
625 	}
626 
627 	private Component provideViewFrameIfItExists() {
628 		return myView != null ? myView.getFrame() : null;
629 	}
630 
631 	public void removeInboundConnection(InboundConnection theConnection) {
632 		myInboundConnectionList.removeConnecion(theConnection);
633 		if (myInboundConnectionList.getConnections().size() > 0) {
634 			setLeftSelectedItem(myInboundConnectionList.getConnections().get(0));
635 		} else {
636 			tryToSelectSomething();
637 		}
638 	}
639 
640 	public void removeOutboundConnection(OutboundConnection theConnection) {
641 		myOutboundConnectionList.removeConnecion(theConnection);
642 		if (myOutboundConnectionList.getConnections().size() > 0) {
643 			setLeftSelectedItem(myOutboundConnectionList.getConnections().get(0));
644 		} else {
645 			tryToSelectSomething();
646 		}
647 	}
648 
649 	public void revertMessage(Hl7V2MessageCollection theMsg) {
650 		if (StringUtils.isBlank(theMsg.getSaveFileName())) {
651 			showDialogError("Message has not yet been saved");
652 			return;
653 		}
654 
655 		File file = new File(theMsg.getSaveFileName());
656 		if (file.exists() == false || file.isDirectory() || !file.canRead()) {
657 			showDialogError("File \"" + theMsg.getSaveFileName() + "\" can not be read");
658 			return;
659 		}
660 
661 		int revert = showDialogYesNo("Revert file to saved contents? You will lose all changes.");
662 		if (revert == JOptionPane.NO_OPTION) {
663 			return;
664 		}
665 
666 		Charset charSet = theMsg.getSaveCharset();
667 		String contents;
668 		try {
669 			contents = FileUtils.readFile(file, charSet);
670 		} catch (IOException e) {
671 			ourLog.error("Failed to read from file " + file.getAbsolutePath(), e);
672 			showDialogError("Failed to read from file: " + e.getMessage());
673 			return;
674 		}
675 
676 		theMsg.setSourceMessage(contents);
677 
678 	}
679 
680 	public boolean saveAllMessagesAndReturnFalseIfCancelIsPressed() {
681 
682 		for (Hl7V2MessageCollection next : myMessagesList.getMessages()) {
683 			if (next.isSaved() == false) {
684 				int save = showPromptToSaveMessageBeforeClosingIt(next, true);
685 				switch (save) {
686 				case JOptionPane.YES_OPTION:
687 					if (!saveMessages(next)) {
688 						return false;
689 					}
690 					break;
691 				case JOptionPane.NO_OPTION:
692 					break;
693 				case JOptionPane.CANCEL_OPTION:
694 					return false;
695 				default:
696 					// shouldn't happen
697 					throw new Error("invalid option:" + save);
698 				}
699 			}
700 		}
701 
702 		return true;
703 	}
704 
705 	public boolean saveMessages(Hl7V2MessageCollection theSelectedValue) {
706 		Validate.notNull(theSelectedValue);
707 
708 		if (theSelectedValue.getSaveFileName() == null) {
709 			return saveMessagesAs(theSelectedValue);
710 		} else {
711 			return doSave(theSelectedValue);
712 		}
713 
714 	}
715 
716 	/**
717 	 * Prompt for a filename and save the currently selected messages
718 	 * 
719 	 * @return Returns true if the file is saved
720 	 */
721 	public boolean saveMessagesAs(Hl7V2MessageCollection theSelectedValue) {
722 		Validate.notNull(theSelectedValue);
723 
724 		if (mySaveMessagesFileChooser == null) {
725 			mySaveMessagesFileChooser = new JFileChooser(Prefs.getInstance().getSavePathMessages());
726 			mySaveMessagesFileChooser.setDialogTitle("Choose a file to save the current message(s) to");
727 			mySaveMessagesFileChooserAccessory = new FileChooserSaveAccessory();
728 			mySaveMessagesFileChooser.setAccessory(mySaveMessagesFileChooserAccessory);
729 
730 			FileFilter type = new ExtensionFilter("HL7 Files", new String[] { ".hl7" });
731 			mySaveMessagesFileChooser.addChoosableFileFilter(type);
732 
733 			type = new ExtensionFilter("XML Files", new String[] { ".xml" });
734 			mySaveMessagesFileChooser.addChoosableFileFilter(type);
735 
736 			type = new AllFileFilter();
737 			mySaveMessagesFileChooser.addChoosableFileFilter(type);
738 
739 			mySaveMessagesFileChooser.setPreferredSize(new Dimension(700, 500));
740 		}
741 
742 		int value = mySaveMessagesFileChooser.showSaveDialog(myView.getFrame());
743 		if (value == JFileChooser.APPROVE_OPTION) {
744 
745 			File file = mySaveMessagesFileChooser.getSelectedFile();
746 			Prefs.getInstance().setSavePathMessages(file.getPath());
747 
748 			if (!file.getName().contains(".")) {
749 				switch (theSelectedValue.getEncoding()) {
750 				case ER_7:
751 					file = new File(file.getAbsolutePath() + ".hl7");
752 					break;
753 				case XML:
754 					file = new File(file.getAbsolutePath() + ".xml");
755 					break;
756 				}
757 			}
758 
759 			if (file.exists()) {
760 				String message = "The file \"" + file.getName() + "\" already exists. Do you wish to overwrite it?";
761 				int confirmed = showDialogYesNo(message);
762 				if (confirmed == JOptionPane.NO_OPTION) {
763 					return false;
764 				}
765 
766 				ourLog.info("Deleting file: {}", file.getAbsolutePath());
767 				file.delete();
768 			}
769 
770 			theSelectedValue.setSaveCharset(mySaveMessagesFileChooserAccessory.getSelectedCharset());
771 			theSelectedValue.setSaveFileName(file.getAbsolutePath());
772 
773 			theSelectedValue.setSaveStripComments(mySaveMessagesFileChooserAccessory.isSelectedSaveStripComments());
774 			theSelectedValue.setSaveLineEndings(mySaveMessagesFileChooserAccessory.getSelectedLineEndings());
775 
776 			doSave(theSelectedValue);
777 
778 			return true;
779 
780 		} else {
781 
782 			return false;
783 
784 		}
785 	}
786 
787 	/**
788 	 * Send one or more messages out over an interface
789 	 * 
790 	 * @param theITransmissionCallback
791 	 */
792 	public void sendMessages(OutboundConnection theConnection, Hl7V2MessageCollection theMessage, ISendProgressCallback theTransmissionCallback) {
793 		theConnection.sendMessages(theMessage, theTransmissionCallback);
794 	}
795 
796 	public void setLeftSelectedItem(Object theSelectedValue) {
797 		if (myLeftSelectedItem == theSelectedValue) {
798 			return;
799 		}
800 
801 		String id = null;
802 
803 		myLeftSelectedItem = theSelectedValue;
804 		if (myLeftSelectedItem instanceof Hl7V2MessageCollection) {
805 			Hl7V2MessageEditorPanel hl7v2MessageEditorPanel = new Hl7V2MessageEditorPanel(this);
806 			Hl7V2MessageCollection collection = (Hl7V2MessageCollection) myLeftSelectedItem;
807 			hl7v2MessageEditorPanel.setMessage(collection);
808 			myView.setMainPanel(hl7v2MessageEditorPanel);
809 			id = collection.getId();
810 		} else if (myLeftSelectedItem instanceof OutboundConnection) {
811 			OutboundConnectionPanel panel = new OutboundConnectionPanel(this);
812 			panel.setController(this);
813 			OutboundConnection connection = (OutboundConnection) myLeftSelectedItem;
814 			panel.setConnection(connection);
815 			id = connection.getId();
816 			myView.setMainPanel(panel);
817 		} else if (myLeftSelectedItem instanceof InboundConnection) {
818 			InboundConnectionPanel panel = new InboundConnectionPanel(this);
819 			InboundConnection connection = (InboundConnection) myLeftSelectedItem;
820 			panel.setConnection(connection);
821 			myView.setMainPanel(panel);
822 			id = connection.getId();
823 		} else if (myLeftSelectedItem == myNothingSelectedMarker) {
824 			myView.setMainPanel(new NothingSelectedPanel(this));
825 		}
826 
827 		if (id != null) {
828 			Prefs.getInstance().setMostRecentlySelectedItemId(id);
829 		}
830 	}
831 
832 	/**
833 	 * @param theMessageEditorInFollowMode
834 	 *            the messageEditorInFollowMode to set
835 	 */
836 	public void setMessageEditorInFollowMode(boolean theMessageEditorInFollowMode) {
837 		myMessageEditorInFollowMode = theMessageEditorInFollowMode;
838 	}
839 
840 	public void showAboutDialog() {
841 		myView.showAboutDialog();
842 	}
843 
844 	public void showDialogError(String message) {
845 		JOptionPane.showMessageDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.ERROR_MESSAGE);
846 	}
847 
848 	public void showDialogInfo(String message) {
849 		JOptionPane.showMessageDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.INFORMATION_MESSAGE);
850 	}
851 
852 	public void showDialogWarning(String message) {
853 		JOptionPane.showMessageDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.WARNING_MESSAGE);
854 	}
855 
856 	public int showDialogYesNo(String message) {
857 		return JOptionPane.showConfirmDialog(provideViewFrameIfItExists(), message, DIALOG_TITLE, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
858 	}
859 
860 	public void showProfilesAndTablesEditor() {
861 		if (myProfilesAndTablesController == null) {
862 			myProfilesAndTablesController = new ConformanceEditorController(this);
863 		}
864 		myProfilesAndTablesController.show();
865 	}
866 
867 	private int showPromptToSaveMessageBeforeClosingIt(Hl7V2MessageCollection theMsg, boolean theShowCancelButton) {
868 		Component parentComponent = myView.getFrame();
869 		Object message = "<html>The following file is unsaved, do you want to save before closing?<br>" + theMsg.getBestDescription() + "</html>";
870 		String title = DIALOG_TITLE;
871 		int optionType = theShowCancelButton ? JOptionPane.YES_NO_CANCEL_OPTION : JOptionPane.YES_NO_OPTION;
872 		int messageType = JOptionPane.QUESTION_MESSAGE;
873 		return JOptionPane.showConfirmDialog(parentComponent, message, title, optionType, messageType);
874 	}
875 
876 	public void start() {
877 		ourLog.info("Starting TestPanel Controller...");
878 
879 		myExecutor = Executors.newSingleThreadExecutor();
880 		for (Runnable next : myQueuedTasks) {
881 			myExecutor.execute(next);
882 		}
883 		myQueuedTasks = null;
884 
885 		myView = new TestPanelWindow(this);
886 		myView.getFrame().setVisible(true);
887 
888 		String leftItemId = Prefs.getInstance().getMostRecentlySelectedItemId();
889 		if (isNotBlank(leftItemId)) {
890 			Object leftItem = myMessagesList.getWithId(leftItemId);
891 			leftItem = (leftItem != null) ? leftItem : myOutboundConnectionList.getWithId(leftItemId);
892 			leftItem = (leftItem != null) ? leftItem : myInboundConnectionList.getWithId(leftItemId);
893 			if (leftItem != null) {
894 				setLeftSelectedItem(leftItem);
895 			}
896 		}
897 
898 		if (getLeftSelectedItem() == null) {
899 			if (myMessagesList.getMessages().size() > 0) {
900 				setLeftSelectedItem(myMessagesList.getMessages().get(0));
901 			} else {
902 				setLeftSelectedItem(myNothingSelectedMarker);
903 			}
904 		}
905 
906 		new VersionChecker().start();
907 		
908 		Prefs.getInstance().setController(this);
909 		
910 	}
911 
912 	public void startAllInboundConnections() {
913 		ourLog.info("Starting all inbound connections");
914 		for (InboundConnection next : myInboundConnectionList.getConnections()) {
915 			next.start();
916 		}
917 	}
918 
919 	public void startAllOutboundConnections() {
920 		ourLog.info("Starting all outbound connections");
921 		for (OutboundConnection next : myOutboundConnectionList.getConnections()) {
922 			next.start();
923 		}
924 	}
925 
926 	public void startInboundConnection(InboundConnection theLeftSelectedItem) {
927 		theLeftSelectedItem.start();
928 	}
929 
930 	public void startOutboundConnection(OutboundConnection theLeftSelectedItem) {
931 		theLeftSelectedItem.start();
932 	}
933 
934 	public void stopAllInboundConnections() {
935 		ourLog.info("Stopping all inbound connections");
936 		for (InboundConnection next : myInboundConnectionList.getConnections()) {
937 			next.stop();
938 		}
939 	}
940 
941 	public void stopAllOutboundConnections() {
942 		ourLog.info("Stopping all outbound connections");
943 		for (OutboundConnection next : myOutboundConnectionList.getConnections()) {
944 			next.stop();
945 		}
946 	}
947 
948 	private void tryToSelectSomething() {
949 		if (myMessagesList.getMessages().size() > 0) {
950 			setLeftSelectedItem(myMessagesList.getMessages().get(0));
951 		} else if (myOutboundConnectionList.getConnections().size() > 0) {
952 			setLeftSelectedItem(myOutboundConnectionList.getConnections().get(0));
953 		} else if (myInboundConnectionList.getConnections().size() > 0) {
954 			setLeftSelectedItem(myInboundConnectionList.getConnections().get(0));
955 		} else {
956 			setLeftSelectedItem(myNothingSelectedMarker);
957 		}
958 	}
959 
960 	private void updateRecentMessageFiles(Hl7V2MessageCollection theMessage) {
961 		Prefs.getInstance().addMessagesFileXmlToRecents(myProfileFileList, Arrays.asList(theMessage));
962 		if (myView != null) {
963 			myView.setRecentMessageFiles(Prefs.getInstance().getRecentMessageXmlFiles(myProfileFileList));
964 		}
965 	}
966 
967 	public boolean validateNewValue(String theTerserPath, String theNewValue) {
968 		String errorMsg = null;
969 		if (theTerserPath.endsWith("MSH-1")) {
970 			if (theNewValue.length() != 1) {
971 				errorMsg = "MSH-1 must be exactly 1 character";
972 			}
973 		}
974 
975 		if (theTerserPath.endsWith("MSH-2")) {
976 			if (theNewValue.length() != 4) {
977 				errorMsg = "MSH-2 must be exactly 4 characters";
978 			}
979 		}
980 
981 		if (errorMsg != null) {
982 			showDialogError(errorMsg);
983 			return false;
984 		}
985 
986 		return true;
987 	}
988 
989 	/**
990 	 * Thread which checks if we are running the latest version of the TestPanel
991 	 */
992 	private class VersionChecker extends Thread {
993 
994 		@Override
995 		public void run() {
996 			String version = getAppVersionString();
997 			if (version.contains("$")) {
998 				version = "1.0";
999 			}
1000 
1001 			boolean isWebstart = true;
1002 			try {
1003 				Class.forName("javax.jnlp.ServiceManager");
1004 			} catch (Throwable t) {
1005 				isWebstart = false;
1006 			}
1007 
1008 			try {
1009 				String javaVersion = System.getProperty("java.version");
1010 				String os = System.getProperty("os.name").replace(" ", "+");
1011 
1012 				URL url = new URL("http://hl7api.sourceforge.net/cgi-bin/testpanelversion.cgi?version=" + version + "&java=" + javaVersion + "&os=" + os + "&webstart=" + isWebstart + "&end");
1013 				InputStream is = (InputStream) url.getContent();
1014 				Reader reader = new InputStreamReader(is, "US-ASCII");
1015 				String content = FileUtils.readFromReaderIntoString(reader);
1016 				if (content.contains("OK")) {
1017 					ourLog.info("HAPI TestPanel is up to date. Great!");
1018 				} else if (content.contains("ERRORNOE ")) {
1019 					final String message = content.replace("ERRORNOE ", "");
1020 					ourLog.warn(message);
1021 					EventQueue.invokeLater(new Runnable() {
1022 						public void run() {
1023 							showDialogWarning(message);
1024 						}
1025 					});
1026 				} else {
1027 					ourLog.warn(content);
1028 				}
1029 			} catch (MalformedURLException e) {
1030 				ourLog.warn("Couldn't parse version checker URL", e);
1031 			} catch (IOException e) {
1032 				ourLog.info("Failed to check if we are running the latest version");
1033 			}
1034 		}
1035 
1036 	}
1037 
1038 }