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.net.SocketException;
38  import java.nio.charset.Charset;
39  import java.security.KeyStore;
40  import java.security.KeyStoreException;
41  import java.security.NoSuchAlgorithmException;
42  import java.security.UnrecoverableKeyException;
43  import java.security.cert.Certificate;
44  import java.security.cert.CertificateException;
45  import java.security.cert.X509Certificate;
46  import java.util.ArrayList;
47  import java.util.Collections;
48  import java.util.Date;
49  import java.util.List;
50  import java.util.UUID;
51  
52  import javax.net.ServerSocketFactory;
53  import javax.net.ssl.SSLServerSocketFactory;
54  import javax.net.ssl.SSLSocketFactory;
55  import javax.swing.SwingUtilities;
56  import jakarta.xml.bind.annotation.XmlAccessType;
57  import jakarta.xml.bind.annotation.XmlAccessorType;
58  import jakarta.xml.bind.annotation.XmlAttribute;
59  import jakarta.xml.bind.annotation.XmlType;
60  
61  import org.apache.commons.lang.StringUtils;
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  import ca.uhn.hl7v2.DefaultHapiContext;
66  import ca.uhn.hl7v2.HapiContext;
67  import ca.uhn.hl7v2.hoh.auth.SingleCredentialClientCallback;
68  import ca.uhn.hl7v2.hoh.auth.SingleCredentialServerCallback;
69  import ca.uhn.hl7v2.hoh.llp.Hl7OverHttpLowerLayerProtocol;
70  import ca.uhn.hl7v2.hoh.sign.BouncyCastleCmsMessageSigner;
71  import ca.uhn.hl7v2.hoh.sockets.CustomCertificateTlsSocketFactory;
72  import ca.uhn.hl7v2.hoh.sockets.TlsSocketFactory;
73  import ca.uhn.hl7v2.hoh.util.HapiSocketTlsFactoryWrapper;
74  import ca.uhn.hl7v2.hoh.util.KeystoreUtils;
75  import ca.uhn.hl7v2.hoh.util.ServerRoleEnum;
76  import ca.uhn.hl7v2.llp.ExtendedMinLowerLayerProtocol;
77  import ca.uhn.hl7v2.llp.LLPException;
78  import ca.uhn.hl7v2.llp.LowerLayerProtocol;
79  import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
80  import ca.uhn.hl7v2.parser.DefaultXMLParser;
81  import ca.uhn.hl7v2.parser.Parser;
82  import ca.uhn.hl7v2.parser.PipeParser;
83  import ca.uhn.hl7v2.testpanel.api.WorkingStatusBean;
84  import ca.uhn.hl7v2.testpanel.controller.Controller;
85  import ca.uhn.hl7v2.testpanel.model.AbstractModelClass;
86  import ca.uhn.hl7v2.testpanel.model.ActivityBase;
87  import ca.uhn.hl7v2.testpanel.model.ActivityIncomingBytes;
88  import ca.uhn.hl7v2.testpanel.model.ActivityInfo;
89  import ca.uhn.hl7v2.testpanel.model.ActivityOutgoingBytes;
90  import ca.uhn.hl7v2.testpanel.ui.IDestroyable;
91  import ca.uhn.hl7v2.testpanel.util.CollectionUtils;
92  import ca.uhn.hl7v2.testpanel.util.llp.ByteCapturingMinLowerLayerProtocolWrapper;
93  import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
94  import ca.uhn.hl7v2.util.SocketFactory;
95  import ca.uhn.hl7v2.validation.builder.support.NoValidationBuilder;
96  import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;
97  
98  @XmlAccessorType(XmlAccessType.FIELD)
99  @XmlType(name = "AbstractConnection")
100 public abstract class AbstractConnection extends AbstractModelClass implements IDestroyable {
101 	public static final String HOH_SIGNATURE_KEYSTORE_STATUS = AbstractConnection.class.getName() + "_HOH_SIGNATURE_KEYSTORE_STATUS";
102 	public static final String HOH_SIGNER_AVAILABLE_ALIASES_PROPERTY = AbstractConnection.class.getName() + "_HOH_SIGNER_AVAILABLE_ALIASES";
103 	public static final String NAME_PROPERTY = AbstractConnection.class.getName() + "_NAME";
104 	public static final String NEW_MESSAGES_PROPERTY = InboundConnection.class.getName() + "_NEW_MESSAGES_PROP";
105 	private static final Logger ourLog = LoggerFactory.getLogger(AbstractConnection.class);
106 	public static final String PERSISTENT_PROPERTY = AbstractConnection.class.getName() + "_PERSISTENT";
107 	public static final String RECENT_ACTIVITY_PROPERTY = AbstractConnection.class.getName() + "_RECENT_ACTIVITY";
108 	public static final String STATUS_LINE_PROPERTY = AbstractConnection.class.getName() + "_STATUS_LINE";
109 	public static final String STATUS_PROPERTY = AbstractConnection.class.getName() + "_STATUS";
110 	public static final String TLS_KEYSTORE_STATUS = AbstractConnection.class.getName() + "_TLS_KEYSTORE_STATUS";
111 	public static final String TRANSPORT_PROPERTY = AbstractConnection.class.getName() + "_TRANSPORT";
112 
113 	@XmlAttribute(required = true)
114 	private boolean myCaptureBytes;
115 
116 	@XmlAttribute(required = true)
117 	private String myCharSet;
118 
119 	private transient Controller myController;
120 
121 	@XmlAttribute(required = true)
122 	private boolean myDetectCharSetInMessage;
123 
124 	@XmlAttribute(required = true)
125 	private boolean myDualPort;
126 
127 	@XmlAttribute(required = true)
128 	private Hl7V2EncodingTypeEnum myEncoding;
129 
130 	@XmlAttribute(name = "hoh_authentication")
131 	private boolean myHohAuthenticationEnabled;
132 
133 	@XmlAttribute(name = "hoh_auth_pass")
134 	private String myHohAuthenticationPassword;
135 
136 	@XmlAttribute(name = "hoh_auth_user")
137 	private String myHohAuthenticationUsername;
138 
139 	private boolean myHohSecurityKeystoreCheckIsScheduled;
140 
141 	private transient List<String> myHohSignatureAvailableAliases;
142 
143 	@XmlAttribute(name = "hoh_signature_enabled")
144 	private boolean myHohSignatureEnabled;
145 	@XmlAttribute(name = "hoh_signature_key")
146 	private String myHohSignatureKey;
147 
148 	@XmlAttribute(name = "hoh_signature_key_password")
149 	private String myHohSignatureKeyPassword;
150 
151 	@XmlAttribute(name = "hoh_signature_keystore")
152 	private String myHohSignatureKeystore;
153 	private transient KeyStore myHohSignatureKeystore_;
154 	private boolean myHohSignatureKeystoreCheckIsScheduled;
155 	@XmlAttribute(name = "hoh_signature_keystore_password")
156 	private String myHohSignatureKeystorePassword;
157 	private WorkingStatusBean myHohSignatureKeystoreStatus;
158 	@XmlAttribute(required = true)
159 	private String myHost;
160 	@XmlAttribute(name = "httpUriPath", required = false)
161 	private String myHttpUriPath;
162 	@XmlAttribute(required = true)
163 	private String myId;
164 	@XmlAttribute(required = true)
165 	private int myIncomingOrSinglePort;
166 	@XmlAttribute(required = true)
167 	private String myName;
168 
169 	@XmlAttribute(required = true)
170 	private boolean myNameIsExplicitlySet;
171 
172 	private transient int myNewMessages;
173 
174 	@XmlAttribute(required = true)
175 	private int myOutgoingPort;
176 
177 	@XmlAttribute(required = true)
178 	private boolean myPersistent;
179 
180 	private transient ByteArrayOutputStream myReaderCapture = new ByteArrayOutputStream();
181 
182 	private transient List<ActivityBase> myRecentActivity = new ArrayList<ActivityBase>();
183 
184 	private transient StatusEnum myStatus = StatusEnum.STOPPED;
185 
186 	private transient String myStatusLine;
187 
188 	private transient StreamWatcherThread myStreamWatcherThread;
189 
190 	@XmlAttribute(required = true)
191 	private boolean myTls;
192 
193 	private transient KeyStore myTlsKeystore;
194 
195 	@XmlAttribute(required = false)
196 	private String myTlsKeystoreLocation;
197 
198 	@XmlAttribute(required = false)
199 	private String myTlsKeystorePassword;
200 	private transient WorkingStatusBean myTlsKeystoreStatus;
201 	@XmlAttribute(name = "transport", required = true)
202 	private TransportStyleEnum myTransport;
203 	private transient ByteArrayOutputStream myWriterCapture = new ByteArrayOutputStream();
204 
205 	public AbstractConnection() {
206 		myId = UUID.randomUUID().toString();
207 	}
208 
209 	protected void addActivity(ActivityBase theActivity) {
210 		myRecentActivity.add(theActivity);
211 		if (myRecentActivity.size() > 100) {
212 			myRecentActivity.remove(0);
213 		}
214 		firePropertyChange(RECENT_ACTIVITY_PROPERTY, null, null);
215 	}
216 
217 	SocketFactory getSocketFactory() {
218 		return new SocketFactory() {
219 
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 			public ServerSocket createTlsServerSocket() throws IOException {
232 				try {
233 					if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP && getHohSignatureKeystore_() != null) {
234 						return createHohSocketFactory().createServerSocket();
235 					}
236 				} catch (KeyStoreException e) {
237 					throw new IOException(e.getMessage(), e);
238 				}
239 				return SSLServerSocketFactory.getDefault().createServerSocket();
240 			}
241 
242 			private CustomCertificateTlsSocketFactory createHohSocketFactory() throws KeyStoreException {
243 				KeyStore keystore = getTlsKeystore();
244 				String keystorePassword = getTlsKeystorePassword();
245 				CustomCertificateTlsSocketFactory sf = new CustomCertificateTlsSocketFactory(keystore, keystorePassword);
246 				return sf;
247 			}
248 
249 			public Socket createSocket() throws IOException {
250 				return javax.net.SocketFactory.getDefault().createSocket();
251 			}
252 
253 			public ServerSocket createServerSocket() throws IOException {
254 				return ServerSocketFactory.getDefault().createServerSocket();
255 			}
256 
257 			public void configureNewAcceptedSocket(Socket theSocket) throws SocketException {
258 				// nothing
259 			}
260 		};
261 	}
262 
263 	public void addNewMessage() {
264 		int oldValue = myNewMessages;
265 		int newValue = myNewMessages + 1;
266 
267 		try {
268 			fireVetoableChange(NEW_MESSAGES_PROPERTY, oldValue, newValue);
269 		} catch (PropertyVetoException e) {
270 			ourLog.debug("Property {} vetoed", NEW_MESSAGES_PROPERTY);
271 			return;
272 		}
273 
274 		myNewMessages = newValue;
275 		firePropertyChange(NEW_MESSAGES_PROPERTY, oldValue, myNewMessages);
276 	}
277 
278 	protected void beforeProcessingNewMessageIn() {
279 		if (isCaptureBytes()) {
280 			checkInboundCapture();
281 		}
282 	}
283 
284 	protected void beforeProcessingNewMessageOut() {
285 		if (isCaptureBytes()) {
286 			checkOutboundCapture();
287 		}
288 	}
289 
290 	private void checkInboundCapture() {
291 		synchronized (myReaderCapture) {
292 			byte[] inboundBytes = myReaderCapture.toByteArray();
293 			if (inboundBytes.length > 0) {
294 				addActivity(new ActivityIncomingBytes(new Date(), inboundBytes));
295 				myReaderCapture.reset();
296 			}
297 		}
298 	}
299 
300 	protected void addActivityInfoInSwingThread(final String msg) {
301 		final ActivityBase activity = new ActivityInfo(new Date(), msg);
302 		addActivityInSwingThread(activity);
303 	}
304 
305 	protected void addActivityInSwingThread(final ActivityBase theActivity) {
306 		SwingUtilities.invokeLater(new Runnable() {
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./ca/uhn/hl7v2/testpanel/model/conn/AbstractConnection.html#AbstractConnection">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 			public void run() {
977 				firePropertyChange(HOH_SIGNATURE_KEYSTORE_STATUS, oldValue, theStatusBean);
978 			}
979 		});
980 	}
981 
982 	/**
983 	 * @param theHost
984 	 *            the host to set
985 	 */
986 	public void setHost(String theHost) {
987 		myHost = theHost;
988 		updateName();
989 	}
990 
991 	/**
992 	 * @param theHttpUriPath
993 	 *            the httpUriPath to set
994 	 */
995 	public void setHttpUriPath(String theHttpUriPath) {
996 		myHttpUriPath = theHttpUriPath;
997 	}
998 
999 	/**
1000 	 * @param theIncomingOrSinglePort
1001 	 *            the incomingOrSinglePort to set
1002 	 */
1003 	public void setIncomingOrSinglePort(int theIncomingOrSinglePort) {
1004 		myIncomingOrSinglePort = theIncomingOrSinglePort;
1005 		updateName();
1006 	}
1007 
1008 	/**
1009 	 * @param theName
1010 	 *            the name to set
1011 	 */
1012 	public void setName(String theName) {
1013 		String oldValue = myName;
1014 		myName = theName;
1015 		firePropertyChange(NAME_PROPERTY, oldValue, myName);
1016 	}
1017 
1018 	/**
1019 	 * @param theName
1020 	 *            the name to set
1021 	 */
1022 	public void setNameExplicitly(String theName) {
1023 		if (theName == null) {
1024 			return;
1025 		}
1026 		String oldValue = myName;
1027 		myName = theName;
1028 		if (StringUtils.equals(oldValue, theName) == false) {
1029 			myNameIsExplicitlySet = true;
1030 		}
1031 		firePropertyChange(NAME_PROPERTY, oldValue, myName);
1032 	}
1033 
1034 	/**
1035 	 * @param theNameIsExplicitlySet
1036 	 *            the nameIsExplicitlySet to set
1037 	 */
1038 	public void setNameIsExplicitlySet(boolean theNameIsExplicitlySet) {
1039 		myNameIsExplicitlySet = theNameIsExplicitlySet;
1040 	}
1041 
1042 	/**
1043 	 * @param theOutgoingPort
1044 	 *            the outgoingPort to set
1045 	 */
1046 	public void setOutgoingPort(int theOutgoingPort) {
1047 		myOutgoingPort = theOutgoingPort;
1048 	}
1049 
1050 	/**
1051 	 * @param thePersistent
1052 	 *            the persistent to set
1053 	 */
1054 	public void setPersistent(boolean thePersistent) {
1055 		boolean oldValue = myPersistent;
1056 		myPersistent = thePersistent;
1057 		firePropertyChange(PERSISTENT_PROPERTY, oldValue, myPersistent);
1058 	}
1059 
1060 	protected void setStatus(StatusEnum theTryingToStart) {
1061 		StatusEnum oldValue = myStatus;
1062 		myStatus = theTryingToStart;
1063 		firePropertyChange(STATUS_PROPERTY, oldValue, myStatus);
1064 	}
1065 
1066 	/**
1067 	 * @param theStatusLine
1068 	 *            the statusLine to set
1069 	 */
1070 	public void setStatusLine(String theStatusLine) {
1071 		String oldValue = myStatusLine;
1072 		myStatusLine = theStatusLine;
1073 		firePropertyChange(STATUS_LINE_PROPERTY, oldValue, theStatusLine);
1074 	}
1075 
1076 	public void setTls(boolean theSelected) {
1077 		boolean oldValue = myTls;
1078 		myTls = theSelected;
1079 		if (oldValue != myTls) {
1080 			myTlsKeystore = null;
1081 		}
1082 	}
1083 
1084 	public void setTlsKeystoreLocation(String theTlsKeystoreLocation) {
1085 		String oldValue = myTlsKeystoreLocation;
1086 		myTlsKeystoreLocation = theTlsKeystoreLocation;
1087 		if (StringUtils.equals(oldValue, theTlsKeystoreLocation) == false) {
1088 			myTlsKeystore = null;
1089 		}
1090 		scheduleHohSecurityKeystoreCheck();
1091 	}
1092 
1093 	public void setTlsKeystorePassword(String theTlsKeystorePassword) {
1094 		String oldValue = myTlsKeystorePassword;
1095 		myTlsKeystorePassword = theTlsKeystorePassword;
1096 		if (StringUtils.equals(oldValue, theTlsKeystorePassword) == false) {
1097 			myTlsKeystore = null;
1098 		}
1099 		scheduleHohSecurityKeystoreCheck();
1100 	}
1101 
1102 	private void setTlsKeystoreStatus(final WorkingStatusBean theStatusBean) {
1103 		final WorkingStatusBean oldValue = myTlsKeystoreStatus;
1104 		myTlsKeystoreStatus = theStatusBean;
1105 		EventQueue.invokeLater(new Runnable() {
1106 			public void run() {
1107 				firePropertyChange(TLS_KEYSTORE_STATUS, oldValue, theStatusBean);
1108 			}
1109 		});
1110 	}
1111 
1112 	/**
1113 	 * @param theTransport
1114 	 *            the transport to set
1115 	 */
1116 	public void setTransport(TransportStyleEnum theTransport) {
1117 		TransportStyleEnum oldValue = myTransport;
1118 		myTransport = theTransport;
1119 		firePropertyChange(TRANSPORT_PROPERTY, oldValue, myTransport);
1120 	}
1121 
1122 	public void start() {
1123 		if (isCaptureBytes()) {
1124 			myStreamWatcherThread = new StreamWatcherThread();
1125 			myStreamWatcherThread.start();
1126 		}
1127 	}
1128 
1129 	public void stop() {
1130 		if (myStreamWatcherThread != null) {
1131 			StreamWatcherThread streamWatcherThread = myStreamWatcherThread;
1132 			myStreamWatcherThread = null;
1133 			streamWatcherThread.interrupt();
1134 		}
1135 	}
1136 
1137 	private void updateName() {
1138 		if (myName != null && isNameIsExplicitlySet()) {
1139 			return;
1140 		}
1141 
1142 		String name = createDescription();
1143 		setName(name);
1144 	}
1145 
1146 	private final class CheckHohSecurityKeystoreRunnable implements Runnable {
1147 		public void run() {
1148 			try {
1149 				Thread.sleep(500);
1150 			} catch (InterruptedException e) {
1151 				// wait a bit
1152 			}
1153 
1154 			synchronized (AbstractConnection.this) {
1155 				if (!myHohSecurityKeystoreCheckIsScheduled) {
1156 					return;
1157 				}
1158 				myHohSecurityKeystoreCheckIsScheduled = false;
1159 			}
1160 
1161 			KeyStore keystore;
1162 			try {
1163 				keystore = getTlsKeystore();
1164 				if (keystore == null) {
1165 					if (isTls()) {
1166 						setTlsKeystoreStatus(new WorkingStatusBean("Using system keystore", WorkingStatusBean.StatusEnum.OK));
1167 					} else {
1168 						setTlsKeystoreStatus(new WorkingStatusBean("", WorkingStatusBean.StatusEnum.OK));
1169 					}
1170 				} else {
1171 					setTlsKeystoreStatus(new WorkingStatusBean("Keystore appears good", WorkingStatusBean.StatusEnum.OK));
1172 				}
1173 			} catch (KeyStoreException e) {
1174 				ourLog.error("Keystore problem", e);
1175 				setTlsKeystoreStatus(new WorkingStatusBean(e.getMessage(), WorkingStatusBean.StatusEnum.ERROR));
1176 			}
1177 		}
1178 
1179 	}
1180 
1181 	private final class CheckHohSignatureKeystoreRunnable implements Runnable {
1182 		public void run() {
1183 
1184 			if (!isHohSignatureEnabled()) {
1185 				setHohSignatureKeystoreStatus(new WorkingStatusBean("", WorkingStatusBean.StatusEnum.OK));
1186 				return;
1187 			}
1188 
1189 			if (isBlank(getHohSignatureKeystore())) {
1190 				setHohSignatureKeystoreStatus(new WorkingStatusBean("Select a KeyStore", WorkingStatusBean.StatusEnum.ERROR));
1191 				return;
1192 			}
1193 
1194 			try {
1195 				Thread.sleep(500);
1196 			} catch (InterruptedException e) {
1197 				// wait a bit
1198 			}
1199 
1200 			synchronized (AbstractConnection.this) {
1201 				if (!myHohSignatureKeystoreCheckIsScheduled) {
1202 					return;
1203 				}
1204 				myHohSignatureKeystoreCheckIsScheduled = false;
1205 			}
1206 
1207 			KeyStore keystore;
1208 			try {
1209 				keystore = getHohSignatureKeystore_();
1210 				if (keystore == null) {
1211 					setHohSignatureKeystoreStatus(new WorkingStatusBean("No keystore selected", WorkingStatusBean.StatusEnum.ERROR));
1212 					setHohSignatureAvailableAliases(new ArrayList<String>());
1213 				} else {
1214 
1215 					List<String> aliases = CollectionUtils.enumerationToList(keystore.aliases());
1216 					if (aliases.size() > 0) {
1217 						if (isBlank(myHohSignatureKey)) {
1218 							myHohSignatureKey = aliases.get(0);
1219 						}
1220 					} else {
1221 						setHohSignatureKeystoreStatus(new WorkingStatusBean("Keystore contains no suitable keys/aliases", WorkingStatusBean.StatusEnum.ERROR));
1222 						return;
1223 					}
1224 
1225 					setHohSignatureAvailableAliases(aliases);
1226 
1227 					if (!isInbound()) {
1228 						if (isBlank(myHohSignatureKeyPassword)) {
1229 							setHohSignatureKeystoreStatus(new WorkingStatusBean("Key password not specified", WorkingStatusBean.StatusEnum.ERROR));
1230 							return;
1231 						}
1232 						if (!KeystoreUtils.canRecoverKey(getHohSignatureKeystore_(), getHohSignatureKey(), getHohSignatureKeyPassword())) {
1233 							setHohSignatureKeystoreStatus(new WorkingStatusBean("Key password is incorrect", WorkingStatusBean.StatusEnum.ERROR));
1234 							return;
1235 						}
1236 						if (!KeystoreUtils.validateKeyForSignatureSigning(getHohSignatureKeystore_(), getHohSignatureKey(), getHohSignatureKeyPassword())) {
1237 							setHohSignatureKeystoreStatus(new WorkingStatusBean("Key is not appropriate for signing", WorkingStatusBean.StatusEnum.ERROR));
1238 							return;
1239 						}
1240 					}
1241 
1242 					Certificate cert = keystore.getCertificate(myHohSignatureKey);
1243 					if (cert instanceof X509Certificate) {
1244 						String signer = ((X509Certificate) cert).getSubjectX500Principal().getName();
1245 						setHohSignatureKeystoreStatus(new WorkingStatusBean("Key belongs to: " + signer, WorkingStatusBean.StatusEnum.OK));
1246 					} else {
1247 						setHohSignatureKeystoreStatus(new WorkingStatusBean("Keystore appears good", WorkingStatusBean.StatusEnum.OK));
1248 					}
1249 
1250 					// TODO: verify that key alias is usable
1251 					// TODO: verify whether separate key password is needed
1252 
1253 				}
1254 			} catch (KeyStoreException e) {
1255 				ourLog.error("Keystore problem", e);
1256 				setHohSignatureKeystoreStatus(new WorkingStatusBean(e.getMessage(), WorkingStatusBean.StatusEnum.ERROR));
1257 				setHohSignatureAvailableAliases(new ArrayList<String>());
1258 			}
1259 		}
1260 
1261 	}
1262 
1263 	public enum StatusEnum {
1264 		FAILED(false), STARTED(true), STOPPED(false), TRYING_TO_START(true);
1265 
1266 		private boolean myRunning;
1267 
1268 		private StatusEnum(boolean theRunning) {
1269 			myRunning = theRunning;
1270 		}
1271 
1272 		public boolean isRunning() {
1273 			return myRunning;
1274 		}
1275 	}
1276 
1277 	private class StreamWatcherThread extends Thread {
1278 
1279 		@Override
1280 		public void run() {
1281 			while (myStreamWatcherThread == this) {
1282 				checkInboundCapture();
1283 				checkOutboundCapture();
1284 
1285 				try {
1286 					Thread.sleep(500);
1287 				} catch (InterruptedException e) {
1288 					// ignore
1289 				}
1290 			}
1291 		}
1292 
1293 	}
1294 
1295 }