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.model.conn;
27  
28  import static org.apache.commons.lang.StringUtils.*;
29  
30  import java.awt.EventQueue;
31  import java.beans.PropertyVetoException;
32  import java.io.ByteArrayOutputStream;
33  import java.io.File;
34  import java.io.IOException;
35  import java.net.ServerSocket;
36  import java.net.Socket;
37  import java.nio.charset.Charset;
38  import java.security.KeyStore;
39  import java.security.KeyStoreException;
40  import java.security.NoSuchAlgorithmException;
41  import java.security.UnrecoverableKeyException;
42  import java.security.cert.Certificate;
43  import java.security.cert.CertificateException;
44  import java.security.cert.X509Certificate;
45  import java.util.ArrayList;
46  import java.util.Collections;
47  import java.util.Date;
48  import java.util.List;
49  import java.util.UUID;
50  
51  import javax.net.ServerSocketFactory;
52  import javax.net.ssl.SSLServerSocketFactory;
53  import javax.net.ssl.SSLSocketFactory;
54  import javax.swing.SwingUtilities;
55  import javax.xml.bind.annotation.XmlAccessType;
56  import javax.xml.bind.annotation.XmlAccessorType;
57  import javax.xml.bind.annotation.XmlAttribute;
58  import javax.xml.bind.annotation.XmlType;
59  
60  import org.apache.commons.lang.StringUtils;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  import ca.uhn.hl7v2.DefaultHapiContext;
65  import ca.uhn.hl7v2.HapiContext;
66  import ca.uhn.hl7v2.hoh.auth.SingleCredentialClientCallback;
67  import ca.uhn.hl7v2.hoh.auth.SingleCredentialServerCallback;
68  import ca.uhn.hl7v2.hoh.llp.Hl7OverHttpLowerLayerProtocol;
69  import ca.uhn.hl7v2.hoh.sign.BouncyCastleCmsMessageSigner;
70  import ca.uhn.hl7v2.hoh.sockets.CustomCertificateTlsSocketFactory;
71  import ca.uhn.hl7v2.hoh.sockets.TlsSocketFactory;
72  import ca.uhn.hl7v2.hoh.util.HapiSocketTlsFactoryWrapper;
73  import ca.uhn.hl7v2.hoh.util.KeystoreUtils;
74  import ca.uhn.hl7v2.hoh.util.ServerRoleEnum;
75  import ca.uhn.hl7v2.llp.ExtendedMinLowerLayerProtocol;
76  import ca.uhn.hl7v2.llp.LLPException;
77  import ca.uhn.hl7v2.llp.LowerLayerProtocol;
78  import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
79  import ca.uhn.hl7v2.parser.DefaultXMLParser;
80  import ca.uhn.hl7v2.parser.Parser;
81  import ca.uhn.hl7v2.parser.PipeParser;
82  import ca.uhn.hl7v2.testpanel.api.WorkingStatusBean;
83  import ca.uhn.hl7v2.testpanel.controller.Controller;
84  import ca.uhn.hl7v2.testpanel.model.AbstractModelClass;
85  import ca.uhn.hl7v2.testpanel.model.ActivityBase;
86  import ca.uhn.hl7v2.testpanel.model.ActivityIncomingBytes;
87  import ca.uhn.hl7v2.testpanel.model.ActivityInfo;
88  import ca.uhn.hl7v2.testpanel.model.ActivityOutgoingBytes;
89  import ca.uhn.hl7v2.testpanel.ui.IDestroyable;
90  import ca.uhn.hl7v2.testpanel.util.CollectionUtils;
91  import ca.uhn.hl7v2.testpanel.util.llp.ByteCapturingMinLowerLayerProtocolWrapper;
92  import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
93  import ca.uhn.hl7v2.util.SocketFactory;
94  import ca.uhn.hl7v2.validation.builder.support.NoValidationBuilder;
95  import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;
96  
97  @XmlAccessorType(XmlAccessType.FIELD)
98  @XmlType(name = "AbstractConnection")
99  public abstract class AbstractConnection extends AbstractModelClass implements IDestroyable {
100 	public static final String HOH_SIGNATURE_KEYSTORE_STATUS = AbstractConnection.class.getName() + "_HOH_SIGNATURE_KEYSTORE_STATUS";
101 	public static final String HOH_SIGNER_AVAILABLE_ALIASES_PROPERTY = AbstractConnection.class.getName() + "_HOH_SIGNER_AVAILABLE_ALIASES";
102 	public static final String NAME_PROPERTY = AbstractConnection.class.getName() + "_NAME";
103 	public static final String NEW_MESSAGES_PROPERTY = InboundConnection.class.getName() + "_NEW_MESSAGES_PROP";
104 	private static final Logger ourLog = LoggerFactory.getLogger(AbstractConnection.class);
105 	public static final String PERSISTENT_PROPERTY = AbstractConnection.class.getName() + "_PERSISTENT";
106 	public static final String RECENT_ACTIVITY_PROPERTY = AbstractConnection.class.getName() + "_RECENT_ACTIVITY";
107 	public static final String STATUS_LINE_PROPERTY = AbstractConnection.class.getName() + "_STATUS_LINE";
108 	public static final String STATUS_PROPERTY = AbstractConnection.class.getName() + "_STATUS";
109 	public static final String TLS_KEYSTORE_STATUS = AbstractConnection.class.getName() + "_TLS_KEYSTORE_STATUS";
110 	public static final String TRANSPORT_PROPERTY = AbstractConnection.class.getName() + "_TRANSPORT";
111 
112 	@XmlAttribute(required = true)
113 	private boolean myCaptureBytes;
114 
115 	@XmlAttribute(required = true)
116 	private String myCharSet;
117 
118 	private transient Controller myController;
119 
120 	@XmlAttribute(required = true)
121 	private boolean myDetectCharSetInMessage;
122 
123 	@XmlAttribute(required = true)
124 	private boolean myDualPort;
125 
126 	@XmlAttribute(required = true)
127 	private Hl7V2EncodingTypeEnum myEncoding;
128 
129 	@XmlAttribute(name = "hoh_authentication")
130 	private boolean myHohAuthenticationEnabled;
131 
132 	@XmlAttribute(name = "hoh_auth_pass")
133 	private String myHohAuthenticationPassword;
134 
135 	@XmlAttribute(name = "hoh_auth_user")
136 	private String myHohAuthenticationUsername;
137 
138 	private boolean myHohSecurityKeystoreCheckIsScheduled;
139 
140 	private transient List<String> myHohSignatureAvailableAliases;
141 
142 	@XmlAttribute(name = "hoh_signature_enabled")
143 	private boolean myHohSignatureEnabled;
144 	@XmlAttribute(name = "hoh_signature_key")
145 	private String myHohSignatureKey;
146 
147 	@XmlAttribute(name = "hoh_signature_key_password")
148 	private String myHohSignatureKeyPassword;
149 
150 	@XmlAttribute(name = "hoh_signature_keystore")
151 	private String myHohSignatureKeystore;
152 	private transient KeyStore myHohSignatureKeystore_;
153 	private boolean myHohSignatureKeystoreCheckIsScheduled;
154 	@XmlAttribute(name = "hoh_signature_keystore_password")
155 	private String myHohSignatureKeystorePassword;
156 	private WorkingStatusBean myHohSignatureKeystoreStatus;
157 	@XmlAttribute(required = true)
158 	private String myHost;
159 	@XmlAttribute(name = "httpUriPath", required = false)
160 	private String myHttpUriPath;
161 	@XmlAttribute(required = true)
162 	private String myId;
163 	@XmlAttribute(required = true)
164 	private int myIncomingOrSinglePort;
165 	@XmlAttribute(required = true)
166 	private String myName;
167 
168 	@XmlAttribute(required = true)
169 	private boolean myNameIsExplicitlySet;
170 
171 	private transient int myNewMessages;
172 
173 	@XmlAttribute(required = true)
174 	private int myOutgoingPort;
175 
176 	@XmlAttribute(required = true)
177 	private boolean myPersistent;
178 
179 	private transient ByteArrayOutputStream myReaderCapture = new ByteArrayOutputStream();
180 
181 	private transient List<ActivityBase> myRecentActivity = new ArrayList<ActivityBase>();
182 
183 	private transient StatusEnum myStatus = StatusEnum.STOPPED;
184 
185 	private transient String myStatusLine;
186 
187 	private transient StreamWatcherThread myStreamWatcherThread;
188 
189 	@XmlAttribute(required = true)
190 	private boolean myTls;
191 
192 	private transient KeyStore myTlsKeystore;
193 
194 	@XmlAttribute(required = false)
195 	private String myTlsKeystoreLocation;
196 
197 	@XmlAttribute(required = false)
198 	private String myTlsKeystorePassword;
199 	private transient WorkingStatusBean myTlsKeystoreStatus;
200 	@XmlAttribute(name = "transport", required = true)
201 	private TransportStyleEnum myTransport;
202 	private transient ByteArrayOutputStream myWriterCapture = new ByteArrayOutputStream();
203 
204 	public AbstractConnection() {
205 		myId = UUID.randomUUID().toString();
206 	}
207 
208 	protected void addActivity(ActivityBase theActivity) {
209 		myRecentActivity.add(theActivity);
210 		if (myRecentActivity.size() > 100) {
211 			myRecentActivity.remove(0);
212 		}
213 		firePropertyChange(RECENT_ACTIVITY_PROPERTY, null, null);
214 	}
215 
216 	SocketFactory getSocketFactory() {
217 		return new SocketFactory() {
218 
219 			@Override
220 			public Socket createTlsSocket() throws IOException {
221 				try {
222 					if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP && getTlsKeystore() != null) {
223 						return createHohSocketFactory().createClientSocket();
224 					}
225 				} catch (KeyStoreException e) {
226 					throw new IOException(e.getMessage(), e);
227 				}
228 				return SSLSocketFactory.getDefault().createSocket();
229 			}
230 
231 			@Override
232 			public ServerSocket createTlsServerSocket() throws IOException {
233 				try {
234 					if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP && getHohSignatureKeystore_() != null) {
235 						return createHohSocketFactory().createServerSocket();
236 					}
237 				} catch (KeyStoreException e) {
238 					throw new IOException(e.getMessage(), e);
239 				}
240 				return SSLServerSocketFactory.getDefault().createServerSocket();
241 			}
242 
243 			private CustomCertificateTlsSocketFactory createHohSocketFactory() throws KeyStoreException {
244 				KeyStore keystore = getTlsKeystore();
245 				String keystorePassword = getTlsKeystorePassword();
246 				CustomCertificateTlsSocketFactory sf = new CustomCertificateTlsSocketFactory(keystore, keystorePassword);
247 				return sf;
248 			}
249 
250 			@Override
251 			public Socket createSocket() throws IOException {
252 				return javax.net.SocketFactory.getDefault().createSocket();
253 			}
254 
255 			@Override
256 			public ServerSocket createServerSocket() throws IOException {
257 				return ServerSocketFactory.getDefault().createServerSocket();
258 			}
259 		};
260 	}
261 
262 	public void addNewMessage() {
263 		int oldValue = myNewMessages;
264 		int newValue = myNewMessages + 1;
265 
266 		try {
267 			fireVetoableChange(NEW_MESSAGES_PROPERTY, oldValue, newValue);
268 		} catch (PropertyVetoException e) {
269 			ourLog.debug("Property {} vetoed", NEW_MESSAGES_PROPERTY);
270 			return;
271 		}
272 
273 		myNewMessages = newValue;
274 		firePropertyChange(NEW_MESSAGES_PROPERTY, oldValue, myNewMessages);
275 	}
276 
277 	protected void beforeProcessingNewMessageIn() {
278 		if (isCaptureBytes()) {
279 			checkInboundCapture();
280 		}
281 	}
282 
283 	protected void beforeProcessingNewMessageOut() {
284 		if (isCaptureBytes()) {
285 			checkOutboundCapture();
286 		}
287 	}
288 
289 	private void checkInboundCapture() {
290 		synchronized (myReaderCapture) {
291 			byte[] inboundBytes = myReaderCapture.toByteArray();
292 			if (inboundBytes.length > 0) {
293 				addActivity(new ActivityIncomingBytes(new Date(), inboundBytes));
294 				myReaderCapture.reset();
295 			}
296 		}
297 	}
298 
299 	protected void addActivityInfoInSwingThread(final String msg) {
300 		final ActivityBase activity = new ActivityInfo(new Date(), msg);
301 		addActivityInSwingThread(activity);
302 	}
303 
304 	protected void addActivityInSwingThread(final ActivityBase theActivity) {
305 		SwingUtilities.invokeLater(new Runnable() {
306 			@Override
307 			public void run() {
308 				addActivity(theActivity);
309 			}
310 		});
311 	}
312 
313 	private void checkOutboundCapture() {
314 		synchronized (myWriterCapture) {
315 			byte[] outboundBytes = myWriterCapture.toByteArray();
316 			if (outboundBytes.length > 0) {
317 				addActivity(new ActivityOutgoingBytes(new Date(), outboundBytes));
318 				myWriterCapture.reset();
319 			}
320 		}
321 	}
322 
323 	public void clearNewMessages() {
324 		int oldValue = myNewMessages;
325 
326 		try {
327 			fireVetoableChange(NEW_MESSAGES_PROPERTY, oldValue, 0);
328 		} catch (PropertyVetoException e) {
329 			ourLog.debug("Property {} vetoed", NEW_MESSAGES_PROPERTY);
330 			return;
331 		}
332 
333 		myNewMessages = 0;
334 		firePropertyChange(NEW_MESSAGES_PROPERTY, oldValue, myNewMessages);
335 	}
336 
337 	/**
338 	 * Remove all entries from the recent activity list
339 	 */
340 	public void clearRecentActivity() {
341 		myRecentActivity.clear();
342 		firePropertyChange(RECENT_ACTIVITY_PROPERTY, null, null);
343 	}
344 
345 	protected String createDescription() {
346 		StringBuilder retVal = new StringBuilder();
347 		if (StringUtils.isNotBlank(myHost)) {
348 			retVal.append(myHost);
349 		} else {
350 			retVal.append("Unknown");
351 		}
352 
353 		retVal.append(":");
354 		if (myIncomingOrSinglePort > 0) {
355 			retVal.append(myIncomingOrSinglePort);
356 		} else {
357 			retVal.append("Unknown");
358 		}
359 
360 		if (myOutgoingPort > 0) {
361 			retVal.append(":");
362 			retVal.append(myOutgoingPort);
363 		}
364 		String name = retVal.toString();
365 		return name;
366 	}
367 
368 	protected LowerLayerProtocol createLlp() throws LLPException {
369 		LowerLayerProtocol llpClass;
370 		if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP) {
371 
372 			ServerRoleEnum role = isInbound() ? ServerRoleEnum.SERVER : ServerRoleEnum.CLIENT;
373 			Hl7OverHttpLowerLayerProtocol hohLlp = new Hl7OverHttpLowerLayerProtocol(role);
374 			if (isHohAuthenticationEnabled()) {
375 				if (isInbound()) {
376 					hohLlp.setAuthorizationCallback(new SingleCredentialServerCallback(getHohAuthenticationUsername(), getHohAuthenticationPassword()));
377 				} else {
378 					hohLlp.setAuthorizationCallback(new SingleCredentialClientCallback(getHohAuthenticationUsername(), getHohAuthenticationPassword()));
379 				}
380 			}
381 			if (isHohSignatureEnabled()) {
382 				BouncyCastleCmsMessageSigner signer = new BouncyCastleCmsMessageSigner();
383 				try {
384 					signer.setKeyStore(getHohSignatureKeystore_());
385 				} catch (KeyStoreException e) {
386 					throw new LLPException(e.getMessage(), e);
387 				}
388 				signer.setKeyAlias(getHohSignatureKey());
389 				signer.setAliasPassword(getHohSignatureKeyPassword());
390 				hohLlp.setSigner(signer);
391 			}
392 			hohLlp.setUriPath(getHttpUriPath());
393 
394 			llpClass = hohLlp;
395 		} else if (isDetectCharSetInMessage()) {
396 			llpClass = new ExtendedMinLowerLayerProtocol();
397 		} else {
398 			MinLowerLayerProtocol llp = new MinLowerLayerProtocol();
399 			llp.setCharset(Charset.forName(getCharSet()));
400 			llpClass = llp;
401 		}
402 
403 		if (isCaptureBytes()) {
404 			llpClass = new ByteCapturingMinLowerLayerProtocolWrapper(llpClass, myReaderCapture, myWriterCapture);
405 		}
406 
407 		return llpClass;
408 	}
409 
410 	protected HapiContext createHapiContext() throws IOException {
411 		try {
412 
413 			SocketFactory serverSocket;
414 			if (!isTls()) {
415 				serverSocket = new ca.uhn.hl7v2.util.StandardSocketFactory();
416 			} else if (getTlsKeystore() == null) {
417 				serverSocket = new HapiSocketTlsFactoryWrapper(new TlsSocketFactory());
418 			} else {
419 				serverSocket = new HapiSocketTlsFactoryWrapper(new CustomCertificateTlsSocketFactory(getTlsKeystore(), getTlsKeystorePassword()));
420 			}
421 
422 			HapiContext ctx = new DefaultHapiContext(new NoValidationBuilder());
423 			ctx.setLowerLayerProtocol(createLlp());
424 			ctx.setSocketFactory(serverSocket);
425 			
426 			if (getEncoding() == Hl7V2EncodingTypeEnum.ER_7) {
427 				ctx.getGenericParser().setPipeParserAsPrimary();
428 			} else {
429 				ctx.getGenericParser().setXMLParserAsPrimary();
430 			}
431 
432 			return ctx;
433 
434 		} catch (Exception e) {
435 			throw new IOException(e.getMessage(), e);
436 		}
437 	}
438 
439 	protected Parser createParser() {
440 		Parser parser;
441 		if (getEncoding() == Hl7V2EncodingTypeEnum.ER_7) {
442 			parser = new PipeParser();
443 		} else {
444 			parser = new DefaultXMLParser();
445 		}
446 		parser.setValidationContext(new ValidationContextImpl());
447 		return parser;
448 	}
449 
450 	public void destroy() {
451 		stop();
452 	}
453 
454 	/*
455 	 * (non-Javadoc)
456 	 * 
457 	 * @see java.lang.Object#equals(java.lang.Object)
458 	 */
459 	@Override
460 	public boolean equals(Object theObj) {
461 		return (theObj instanceof AbstractConnection) && ((AbstractConnection) theObj).myId.equals(myId);
462 	}
463 
464 	/**
465 	 * @return the charSet
466 	 */
467 	public String getCharSet() {
468 		return myCharSet;
469 	}
470 
471 	/**
472 	 * @return the controller
473 	 */
474 	public Controller getController() {
475 		return myController;
476 	}
477 
478 	/**
479 	 * @return the encoding
480 	 */
481 	public Hl7V2EncodingTypeEnum getEncoding() {
482 		return myEncoding;
483 	}
484 
485 	/**
486 	 * @return the hohAuthenticationPassword
487 	 */
488 	public String getHohAuthenticationPassword() {
489 		return myHohAuthenticationPassword;
490 	}
491 
492 	/**
493 	 * @return the hohAuthenticationUsername
494 	 */
495 	public String getHohAuthenticationUsername() {
496 		return myHohAuthenticationUsername;
497 	}
498 
499 	/**
500 	 * @return the hohSignatureAvailableAliases
501 	 */
502 	public List<String> getHohSignatureAvailableAliases() {
503 		List<String> retVal;
504 		if (myHohSignatureAvailableAliases != null) {
505 			retVal = myHohSignatureAvailableAliases;
506 		} else {
507 			retVal = Collections.emptyList();
508 		}
509 		return retVal;
510 	}
511 
512 	/**
513 	 * @return the hohSignatureKey
514 	 */
515 	public String getHohSignatureKey() {
516 		return myHohSignatureKey;
517 	}
518 
519 	/**
520 	 * @return the hohSignatureKeyPassword
521 	 */
522 	public String getHohSignatureKeyPassword() {
523 		return myHohSignatureKeyPassword;
524 	}
525 
526 	/**
527 	 * @return the hohSignatureKeystore
528 	 */
529 	public String getHohSignatureKeystore() {
530 		return myHohSignatureKeystore;
531 	}
532 
533 	/**
534 	 * TODO: rename
535 	 */
536 	public KeyStore getHohSignatureKeystore_() throws KeyStoreException {
537 		if (isBlank(getHohSignatureKeystore())) {
538 			return null;
539 		}
540 		if (myHohSignatureKeystore_ != null) {
541 			return myHohSignatureKeystore_;
542 		}
543 
544 		File jksFile = new File(getHohSignatureKeystore());
545 		if (!jksFile.exists() || !jksFile.canRead()) {
546 			throw new KeyStoreException("File does not exist or can not be read: " + jksFile.getAbsolutePath());
547 		}
548 
549 		char[] password = null;
550 		if (isNotBlank(myHohSignatureKeystorePassword)) {
551 			password = myHohSignatureKeystorePassword.toCharArray();
552 		}
553 
554 		KeyStore keystore;
555 		try {
556 			keystore = KeystoreUtils.loadKeystore(jksFile, password);
557 		} catch (NoSuchAlgorithmException e) {
558 			ourLog.error("Failed to load keystore!", e);
559 			throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
560 		} catch (CertificateException e) {
561 			ourLog.error("Failed to load keystore!", e);
562 			throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
563 		} catch (IOException e) {
564 			ourLog.error("Failed to load keystore!", e);
565 			if (e.getCause() instanceof UnrecoverableKeyException) {
566 				throw new KeyStoreException("Keystore password appears to be incorrect");
567 			}
568 			throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
569 		}
570 
571 		if (this instanceof InboundConnection) {
572 			if (!KeystoreUtils.validateKeystoreForSignatureVerifying(keystore)) {
573 				throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
574 			}
575 		} else if (this instanceof OutboundConnection) {
576 			if (!KeystoreUtils.validateKeystoreForSignatureSigning(keystore)) {
577 				throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
578 			}
579 		}
580 
581 		myHohSignatureKeystore_ = keystore;
582 		return myHohSignatureKeystore_;
583 	}
584 
585 	/**
586 	 * @return the hohSignatureKeystorePassword
587 	 */
588 	public String getHohSignatureKeystorePassword() {
589 		return myHohSignatureKeystorePassword;
590 	}
591 
592 	/**
593 	 * @return the host
594 	 */
595 	public String getHost() {
596 		return myHost;
597 	}
598 
599 	/**
600 	 * @return the httpUriPath
601 	 */
602 	public String getHttpUriPath() {
603 		return myHttpUriPath;
604 	}
605 
606 	public String getId() {
607 		return myId;
608 	}
609 
610 	/**
611 	 * @return the incomingOrSinglePort
612 	 */
613 	public int getIncomingOrSinglePort() {
614 		return myIncomingOrSinglePort;
615 	}
616 
617 	/**
618 	 * @return the name
619 	 */
620 	public String getName() {
621 		updateName();
622 		return myName;
623 	}
624 
625 	/**
626 	 * @return the newMessages
627 	 */
628 	public int getNewMessages() {
629 		return myNewMessages;
630 	}
631 
632 	/**
633 	 * @return the outgoingPort
634 	 */
635 	public int getOutgoingPort() {
636 		return myOutgoingPort;
637 	}
638 
639 	/**
640 	 * @return the recentActivity
641 	 */
642 	public List<ActivityBase> getRecentActivity() {
643 		return myRecentActivity;
644 	}
645 
646 	@SuppressWarnings("unchecked")
647 	public <T extends ActivityBase> List<T> getRecentActivityEntriesOfType(Class<T> theClass) {
648 		ArrayList<T> retVal = new ArrayList<T>();
649 		for (Object next : getRecentActivity()) {
650 			if (theClass.isAssignableFrom(next.getClass())) {
651 				retVal.add((T) next);
652 			}
653 		}
654 		return retVal;
655 	}
656 
657 	/**
658 	 * @return the status
659 	 */
660 	public StatusEnum getStatus() {
661 		return myStatus;
662 	}
663 
664 	/**
665 	 * @return the statusLine
666 	 */
667 	public String getStatusLine() {
668 		return myStatusLine;
669 	}
670 
671 	public KeyStore getTlsKeystore() throws KeyStoreException {
672 		if (isBlank(myTlsKeystoreLocation) || isTls() == false) {
673 			return null;
674 		}
675 		if (myTlsKeystore != null) {
676 			return myTlsKeystore;
677 		}
678 
679 		File jksFile = new File(myTlsKeystoreLocation);
680 		if (!jksFile.exists() || !jksFile.canRead()) {
681 			throw new KeyStoreException("File does not exist or can not be read: " + jksFile.getAbsolutePath());
682 		}
683 
684 		char[] password = null;
685 		if (isNotBlank(myTlsKeystorePassword)) {
686 			password = myTlsKeystorePassword.toCharArray();
687 		}
688 
689 		KeyStore keystore;
690 		try {
691 			keystore = KeystoreUtils.loadKeystore(jksFile, password);
692 		} catch (NoSuchAlgorithmException e) {
693 			ourLog.error("Failed to load keystore!", e);
694 			throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
695 		} catch (CertificateException e) {
696 			ourLog.error("Failed to load keystore!", e);
697 			throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
698 		} catch (IOException e) {
699 			ourLog.error("Failed to load keystore!", e);
700 			if (e.getCause() instanceof UnrecoverableKeyException) {
701 				throw new KeyStoreException("Keystore password appears to be incorrect");
702 			}
703 			throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
704 		}
705 
706 		if (this instanceof InboundConnection) {
707 			if (!KeystoreUtils.validateKeystoreForTlsReceiving(keystore)) {
708 				throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
709 			}
710 		} else if (this instanceof OutboundConnection) {
711 			if (!KeystoreUtils.validateKeystoreForTlsSending(keystore)) {
712 				throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
713 			}
714 		}
715 
716 		myTlsKeystore = keystore;
717 		return myTlsKeystore;
718 	}
719 
720 	/**
721 	 * @return the tlsKeystoreLocation
722 	 */
723 	public String getTlsKeystoreLocation() {
724 		return myTlsKeystoreLocation;
725 	}
726 
727 	/**
728 	 * @return the tlsKeystorePassword
729 	 */
730 	public String getTlsKeystorePassword() {
731 		return myTlsKeystorePassword;
732 	}
733 
734 	/**
735 	 * @return the transport
736 	 */
737 	public TransportStyleEnum getTransport() {
738 		if (myTransport == null) {
739 			if (myDualPort) {
740 				myTransport = TransportStyleEnum.DUAL_PORT_MLLP;
741 			} else {
742 				myTransport = TransportStyleEnum.SINGLE_PORT_MLLP;
743 			}
744 		}
745 		return myTransport;
746 	}
747 
748 	/*
749 	 * (non-Javadoc)
750 	 * 
751 	 * @see java.lang.Object#hashCode()
752 	 */
753 	@Override
754 	public int hashCode() {
755 		return myId.hashCode();
756 	}
757 
758 	/**
759 	 * @return the captureBytes
760 	 */
761 	public boolean isCaptureBytes() {
762 		return myCaptureBytes;
763 	}
764 
765 	/**
766 	 * @return the detectCharSetInMessage
767 	 */
768 	public boolean isDetectCharSetInMessage() {
769 		return myDetectCharSetInMessage;
770 	}
771 
772 	/**
773 	 * @return the dualPort
774 	 */
775 	public boolean isDualPort() {
776 		return myDualPort;
777 	}
778 
779 	/**
780 	 * @return the hohAuthenticationEnabled
781 	 */
782 	public boolean isHohAuthenticationEnabled() {
783 		return myHohAuthenticationEnabled;
784 	}
785 
786 	/**
787 	 * @return the hohSignatureEnabled
788 	 */
789 	public boolean isHohSignatureEnabled() {
790 		return myHohSignatureEnabled;
791 	}
792 
793 	private boolean isInbound() {
794 		return this instanceof InboundConnection;
795 	}
796 
797 	/**
798 	 * @return the nameIsExplicitlySet
799 	 */
800 	public boolean isNameIsExplicitlySet() {
801 		return myNameIsExplicitlySet;
802 	}
803 
804 	/**
805 	 * @return the persistent
806 	 */
807 	public boolean isPersistent() {
808 		return myPersistent;
809 	}
810 
811 	/**
812 	 * @return the tls
813 	 */
814 	public boolean isTls() {
815 		return myTls;
816 	}
817 
818 	private void scheduleHohSecurityKeystoreCheck() {
819 		synchronized (this) {
820 			if (myHohSecurityKeystoreCheckIsScheduled == false) {
821 				setTlsKeystoreStatus(new WorkingStatusBean("Working...", WorkingStatusBean.StatusEnum.WORKING));
822 			}
823 			myHohSecurityKeystoreCheckIsScheduled = true;
824 			if (myController != null) {
825 				myController.invokeInBackground(new CheckHohSecurityKeystoreRunnable());
826 			}
827 		}
828 	}
829 
830 	private void scheduleHohSignatureKeystoreCheck() {
831 		synchronized (this) {
832 			if (myHohSignatureKeystoreCheckIsScheduled == false) {
833 				setHohSignatureKeystoreStatus(new WorkingStatusBean("Working...", WorkingStatusBean.StatusEnum.WORKING));
834 			}
835 			myHohSignatureKeystoreCheckIsScheduled = true;
836 			if (myController != null) {
837 				myController.invokeInBackground(new CheckHohSignatureKeystoreRunnable());
838 			}
839 		}
840 	}
841 
842 	/**
843 	 * @param theCaptureBytes
844 	 *            the captureBytes to set
845 	 */
846 	public void setCaptureBytes(boolean theCaptureBytes) {
847 		myCaptureBytes = theCaptureBytes;
848 	}
849 
850 	/**
851 	 * @param theCharSet
852 	 *            the charSet to set
853 	 */
854 	public void setCharSet(String theCharSet) {
855 		myCharSet = theCharSet;
856 	}
857 
858 	/**
859 	 * @param theController
860 	 *            the controller to set
861 	 */
862 	public void setController(Controller theController) {
863 		myController = theController;
864 		scheduleHohSecurityKeystoreCheck();
865 		scheduleHohSignatureKeystoreCheck();
866 	}
867 
868 	/**
869 	 * @param theDetectCharSetInMessage
870 	 *            the detectCharSetInMessage to set
871 	 */
872 	public void setDetectCharSetInMessage(boolean theDetectCharSetInMessage) {
873 		myDetectCharSetInMessage = theDetectCharSetInMessage;
874 	}
875 
876 	/**
877 	 * @param theDualPort
878 	 *            the dualPort to set
879 	 */
880 	public void setDualPort(boolean theDualPort) {
881 		myDualPort = theDualPort;
882 		updateName();
883 	}
884 
885 	/**
886 	 * @param theEncoding
887 	 *            the encoding to set
888 	 */
889 	public void setEncoding(Hl7V2EncodingTypeEnum theEncoding) {
890 		myEncoding = theEncoding;
891 	}
892 
893 	/**
894 	 * @param theHohAuthenticationEnabled
895 	 *            the hohAuthenticationEnabled to set
896 	 */
897 	public void setHohAuthenticationEnabled(boolean theHohAuthenticationEnabled) {
898 		myHohAuthenticationEnabled = theHohAuthenticationEnabled;
899 	}
900 
901 	/**
902 	 * @param theHohAuthenticationPassword
903 	 *            the hohAuthenticationPassword to set
904 	 */
905 	public void setHohAuthenticationPassword(String theHohAuthenticationPassword) {
906 		myHohAuthenticationPassword = theHohAuthenticationPassword;
907 	}
908 
909 	/**
910 	 * @param theHohAuthenticationUsername
911 	 *            the hohAuthenticationUsername to set
912 	 */
913 	public void setHohAuthenticationUsername(String theHohAuthenticationUsername) {
914 		myHohAuthenticationUsername = theHohAuthenticationUsername;
915 	}
916 
917 	/**
918 	 * @param theList
919 	 *            the hohSignatureAvailableAliases to set
920 	 */
921 	public void setHohSignatureAvailableAliases(List<String> theList) {
922 		List<String> oldValue = myHohSignatureAvailableAliases;
923 		myHohSignatureAvailableAliases = theList;
924 		firePropertyChange(HOH_SIGNER_AVAILABLE_ALIASES_PROPERTY, oldValue, theList);
925 	}
926 
927 	/**
928 	 * @param theHohSignatureEnabled
929 	 *            the hohSignatureEnabled to set
930 	 */
931 	public void setHohSignatureEnabled(boolean theHohSignatureEnabled) {
932 		myHohSignatureEnabled = theHohSignatureEnabled;
933 		scheduleHohSignatureKeystoreCheck();
934 	}
935 
936 	/**
937 	 * @param theHohSignatureKey
938 	 *            the hohSignatureKey to set
939 	 */
940 	public void setHohSignatureKey(String theHohSignatureKey) {
941 		myHohSignatureKey = theHohSignatureKey;
942 		scheduleHohSignatureKeystoreCheck();
943 	}
944 
945 	/**
946 	 * @param theHohSignatureKeyPassword
947 	 *            the hohSignatureKeyPassword to set
948 	 */
949 	public void setHohSignatureKeyPassword(String theHohSignatureKeyPassword) {
950 		myHohSignatureKeyPassword = theHohSignatureKeyPassword;
951 		scheduleHohSignatureKeystoreCheck();
952 	}
953 
954 	/**
955 	 * @param theHohSignatureKeystore
956 	 *            the hohSignatureKeystore to set
957 	 */
958 	public void setHohSignatureKeystore(String theHohSignatureKeystore) {
959 		myHohSignatureKeystore = theHohSignatureKeystore;
960 		scheduleHohSignatureKeystoreCheck();
961 	}
962 
963 	/**
964 	 * @param theHohSignatureKeystorePassword
965 	 *            the hohSignatureKeystorePassword to set
966 	 */
967 	public void setHohSignatureKeystorePassword(String theHohSignatureKeystorePassword) {
968 		myHohSignatureKeystorePassword = theHohSignatureKeystorePassword;
969 		scheduleHohSignatureKeystoreCheck();
970 	}
971 
972 	private void setHohSignatureKeystoreStatus(final WorkingStatusBean theStatusBean) {
973 		final WorkingStatusBean oldValue = myHohSignatureKeystoreStatus;
974 		myHohSignatureKeystoreStatus = theStatusBean;
975 		EventQueue.invokeLater(new Runnable() {
976 			@Override
977 			public void run() {
978 				firePropertyChange(HOH_SIGNATURE_KEYSTORE_STATUS, oldValue, theStatusBean);
979 			}
980 		});
981 	}
982 
983 	/**
984 	 * @param theHost
985 	 *            the host to set
986 	 */
987 	public void setHost(String theHost) {
988 		myHost = theHost;
989 		updateName();
990 	}
991 
992 	/**
993 	 * @param theHttpUriPath
994 	 *            the httpUriPath to set
995 	 */
996 	public void setHttpUriPath(String theHttpUriPath) {
997 		myHttpUriPath = theHttpUriPath;
998 	}
999 
1000 	/**
1001 	 * @param theIncomingOrSinglePort
1002 	 *            the incomingOrSinglePort to set
1003 	 */
1004 	public void setIncomingOrSinglePort(int theIncomingOrSinglePort) {
1005 		myIncomingOrSinglePort = theIncomingOrSinglePort;
1006 		updateName();
1007 	}
1008 
1009 	/**
1010 	 * @param theName
1011 	 *            the name to set
1012 	 */
1013 	public void setName(String theName) {
1014 		String oldValue = myName;
1015 		myName = theName;
1016 		firePropertyChange(NAME_PROPERTY, oldValue, myName);
1017 	}
1018 
1019 	/**
1020 	 * @param theName
1021 	 *            the name to set
1022 	 */
1023 	public void setNameExplicitly(String theName) {
1024 		if (theName == null) {
1025 			return;
1026 		}
1027 		String oldValue = myName;
1028 		myName = theName;
1029 		if (StringUtils.equals(oldValue, theName) == false) {
1030 			myNameIsExplicitlySet = true;
1031 		}
1032 		firePropertyChange(NAME_PROPERTY, oldValue, myName);
1033 	}
1034 
1035 	/**
1036 	 * @param theNameIsExplicitlySet
1037 	 *            the nameIsExplicitlySet to set
1038 	 */
1039 	public void setNameIsExplicitlySet(boolean theNameIsExplicitlySet) {
1040 		myNameIsExplicitlySet = theNameIsExplicitlySet;
1041 	}
1042 
1043 	/**
1044 	 * @param theOutgoingPort
1045 	 *            the outgoingPort to set
1046 	 */
1047 	public void setOutgoingPort(int theOutgoingPort) {
1048 		myOutgoingPort = theOutgoingPort;
1049 	}
1050 
1051 	/**
1052 	 * @param thePersistent
1053 	 *            the persistent to set
1054 	 */
1055 	public void setPersistent(boolean thePersistent) {
1056 		boolean oldValue = myPersistent;
1057 		myPersistent = thePersistent;
1058 		firePropertyChange(PERSISTENT_PROPERTY, oldValue, myPersistent);
1059 	}
1060 
1061 	protected void setStatus(StatusEnum theTryingToStart) {
1062 		StatusEnum oldValue = myStatus;
1063 		myStatus = theTryingToStart;
1064 		firePropertyChange(STATUS_PROPERTY, oldValue, myStatus);
1065 	}
1066 
1067 	/**
1068 	 * @param theStatusLine
1069 	 *            the statusLine to set
1070 	 */
1071 	public void setStatusLine(String theStatusLine) {
1072 		String oldValue = myStatusLine;
1073 		myStatusLine = theStatusLine;
1074 		firePropertyChange(STATUS_LINE_PROPERTY, oldValue, theStatusLine);
1075 	}
1076 
1077 	public void setTls(boolean theSelected) {
1078 		boolean oldValue = myTls;
1079 		myTls = theSelected;
1080 		if (oldValue != myTls) {
1081 			myTlsKeystore = null;
1082 		}
1083 	}
1084 
1085 	public void setTlsKeystoreLocation(String theTlsKeystoreLocation) {
1086 		String oldValue = myTlsKeystoreLocation;
1087 		myTlsKeystoreLocation = theTlsKeystoreLocation;
1088 		if (StringUtils.equals(oldValue, theTlsKeystoreLocation) == false) {
1089 			myTlsKeystore = null;
1090 		}
1091 		scheduleHohSecurityKeystoreCheck();
1092 	}
1093 
1094 	public void setTlsKeystorePassword(String theTlsKeystorePassword) {
1095 		String oldValue = myTlsKeystorePassword;
1096 		myTlsKeystorePassword = theTlsKeystorePassword;
1097 		if (StringUtils.equals(oldValue, theTlsKeystorePassword) == false) {
1098 			myTlsKeystore = null;
1099 		}
1100 		scheduleHohSecurityKeystoreCheck();
1101 	}
1102 
1103 	private void setTlsKeystoreStatus(final WorkingStatusBean theStatusBean) {
1104 		final WorkingStatusBean oldValue = myTlsKeystoreStatus;
1105 		myTlsKeystoreStatus = theStatusBean;
1106 		EventQueue.invokeLater(new Runnable() {
1107 			@Override
1108 			public void run() {
1109 				firePropertyChange(TLS_KEYSTORE_STATUS, oldValue, theStatusBean);
1110 			}
1111 		});
1112 	}
1113 
1114 	/**
1115 	 * @param theTransport
1116 	 *            the transport to set
1117 	 */
1118 	public void setTransport(TransportStyleEnum theTransport) {
1119 		TransportStyleEnum oldValue = myTransport;
1120 		myTransport = theTransport;
1121 		firePropertyChange(TRANSPORT_PROPERTY, oldValue, myTransport);
1122 	}
1123 
1124 	public void start() {
1125 		if (isCaptureBytes()) {
1126 			myStreamWatcherThread = new StreamWatcherThread();
1127 			myStreamWatcherThread.start();
1128 		}
1129 	}
1130 
1131 	public void stop() {
1132 		if (myStreamWatcherThread != null) {
1133 			StreamWatcherThread streamWatcherThread = myStreamWatcherThread;
1134 			myStreamWatcherThread = null;
1135 			streamWatcherThread.interrupt();
1136 		}
1137 	}
1138 
1139 	private void updateName() {
1140 		if (myName != null && isNameIsExplicitlySet()) {
1141 			return;
1142 		}
1143 
1144 		String name = createDescription();
1145 		setName(name);
1146 	}
1147 
1148 	private final class CheckHohSecurityKeystoreRunnable implements Runnable {
1149 		@Override
1150 		public void run() {
1151 			try {
1152 				Thread.sleep(500);
1153 			} catch (InterruptedException e) {
1154 				// wait a bit
1155 			}
1156 
1157 			synchronized (AbstractConnection.this) {
1158 				if (!myHohSecurityKeystoreCheckIsScheduled) {
1159 					return;
1160 				}
1161 				myHohSecurityKeystoreCheckIsScheduled = false;
1162 			}
1163 
1164 			KeyStore keystore;
1165 			try {
1166 				keystore = getTlsKeystore();
1167 				if (keystore == null) {
1168 					if (isTls()) {
1169 						setTlsKeystoreStatus(new WorkingStatusBean("Using system keystore", WorkingStatusBean.StatusEnum.OK));
1170 					} else {
1171 						setTlsKeystoreStatus(new WorkingStatusBean("", WorkingStatusBean.StatusEnum.OK));
1172 					}
1173 				} else {
1174 					setTlsKeystoreStatus(new WorkingStatusBean("Keystore appears good", WorkingStatusBean.StatusEnum.OK));
1175 				}
1176 			} catch (KeyStoreException e) {
1177 				ourLog.error("Keystore problem", e);
1178 				setTlsKeystoreStatus(new WorkingStatusBean(e.getMessage(), WorkingStatusBean.StatusEnum.ERROR));
1179 			}
1180 		}
1181 
1182 	}
1183 
1184 	private final class CheckHohSignatureKeystoreRunnable implements Runnable {
1185 		@Override
1186 		public void run() {
1187 
1188 			if (!isHohSignatureEnabled()) {
1189 				setHohSignatureKeystoreStatus(new WorkingStatusBean("", WorkingStatusBean.StatusEnum.OK));
1190 				return;
1191 			}
1192 
1193 			if (isBlank(getHohSignatureKeystore())) {
1194 				setHohSignatureKeystoreStatus(new WorkingStatusBean("Select a KeyStore", WorkingStatusBean.StatusEnum.ERROR));
1195 				return;
1196 			}
1197 
1198 			try {
1199 				Thread.sleep(500);
1200 			} catch (InterruptedException e) {
1201 				// wait a bit
1202 			}
1203 
1204 			synchronized (AbstractConnection.this) {
1205 				if (!myHohSignatureKeystoreCheckIsScheduled) {
1206 					return;
1207 				}
1208 				myHohSignatureKeystoreCheckIsScheduled = false;
1209 			}
1210 
1211 			KeyStore keystore;
1212 			try {
1213 				keystore = getHohSignatureKeystore_();
1214 				if (keystore == null) {
1215 					setHohSignatureKeystoreStatus(new WorkingStatusBean("No keystore selected", WorkingStatusBean.StatusEnum.ERROR));
1216 					setHohSignatureAvailableAliases(new ArrayList<String>());
1217 				} else {
1218 
1219 					List<String> aliases = CollectionUtils.enumerationToList(keystore.aliases());
1220 					if (aliases.size() > 0) {
1221 						if (isBlank(myHohSignatureKey)) {
1222 							myHohSignatureKey = aliases.get(0);
1223 						}
1224 					} else {
1225 						setHohSignatureKeystoreStatus(new WorkingStatusBean("Keystore contains no suitable keys/aliases", WorkingStatusBean.StatusEnum.ERROR));
1226 						return;
1227 					}
1228 
1229 					setHohSignatureAvailableAliases(aliases);
1230 
1231 					if (!isInbound()) {
1232 						if (isBlank(myHohSignatureKeyPassword)) {
1233 							setHohSignatureKeystoreStatus(new WorkingStatusBean("Key password not specified", WorkingStatusBean.StatusEnum.ERROR));
1234 							return;
1235 						}
1236 						if (!KeystoreUtils.canRecoverKey(getHohSignatureKeystore_(), getHohSignatureKey(), getHohSignatureKeyPassword())) {
1237 							setHohSignatureKeystoreStatus(new WorkingStatusBean("Key password is incorrect", WorkingStatusBean.StatusEnum.ERROR));
1238 							return;
1239 						}
1240 						if (!KeystoreUtils.validateKeyForSignatureSigning(getHohSignatureKeystore_(), getHohSignatureKey(), getHohSignatureKeyPassword())) {
1241 							setHohSignatureKeystoreStatus(new WorkingStatusBean("Key is not appropriate for signing", WorkingStatusBean.StatusEnum.ERROR));
1242 							return;
1243 						}
1244 					}
1245 
1246 					Certificate cert = keystore.getCertificate(myHohSignatureKey);
1247 					if (cert instanceof X509Certificate) {
1248 						String signer = ((X509Certificate) cert).getSubjectX500Principal().getName();
1249 						setHohSignatureKeystoreStatus(new WorkingStatusBean("Key belongs to: " + signer, WorkingStatusBean.StatusEnum.OK));
1250 					} else {
1251 						setHohSignatureKeystoreStatus(new WorkingStatusBean("Keystore appears good", WorkingStatusBean.StatusEnum.OK));
1252 					}
1253 
1254 					// TODO: verify that key alias is usable
1255 					// TODO: verify whether separate key password is needed
1256 
1257 				}
1258 			} catch (KeyStoreException e) {
1259 				ourLog.error("Keystore problem", e);
1260 				setHohSignatureKeystoreStatus(new WorkingStatusBean(e.getMessage(), WorkingStatusBean.StatusEnum.ERROR));
1261 				setHohSignatureAvailableAliases(new ArrayList<String>());
1262 			}
1263 		}
1264 
1265 	}
1266 
1267 	public enum StatusEnum {
1268 		FAILED(false), STARTED(true), STOPPED(false), TRYING_TO_START(true);
1269 
1270 		private boolean myRunning;
1271 
1272 		private StatusEnum(boolean theRunning) {
1273 			myRunning = theRunning;
1274 		}
1275 
1276 		public boolean isRunning() {
1277 			return myRunning;
1278 		}
1279 	}
1280 
1281 	private class StreamWatcherThread extends Thread {
1282 
1283 		@Override
1284 		public void run() {
1285 			while (myStreamWatcherThread == this) {
1286 				checkInboundCapture();
1287 				checkOutboundCapture();
1288 
1289 				try {
1290 					Thread.sleep(500);
1291 				} catch (InterruptedException e) {
1292 					// ignore
1293 				}
1294 			}
1295 		}
1296 
1297 	}
1298 
1299 }