ca.uhn.hl7v2.testpanel.model.conn.AbstractConnection.java Source code

Java tutorial

Introduction

Here is the source code for ca.uhn.hl7v2.testpanel.model.conn.AbstractConnection.java

Source

/**
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
 * specific language governing rights and limitations under the License.
 *
 * The Original Code is ""  Description:
 * ""
 *
 * The Initial Developer of the Original Code is University Health Network. Copyright (C)
 * 2001.  All Rights Reserved.
 *
 * Contributor(s): ______________________________________.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * GNU General Public License (the  "GPL"), in which case the provisions of the GPL are
 * applicable instead of those above.  If you wish to allow use of your version of this
 * file only under the terms of the GPL and not to allow others to use your version
 * of this file under the MPL, indicate your decision by deleting  the provisions above
 * and replace  them with the notice and other provisions required by the GPL License.
 * If you do not delete the provisions above, a recipient may use your version of
 * this file under either the MPL or the GPL.
 */
package ca.uhn.hl7v2.testpanel.model.conn;

import static org.apache.commons.lang.StringUtils.*;

import java.awt.EventQueue;
import java.beans.PropertyVetoException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.swing.SwingUtilities;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.uhn.hl7v2.DefaultHapiContext;
import ca.uhn.hl7v2.HapiContext;
import ca.uhn.hl7v2.hoh.auth.SingleCredentialClientCallback;
import ca.uhn.hl7v2.hoh.auth.SingleCredentialServerCallback;
import ca.uhn.hl7v2.hoh.llp.Hl7OverHttpLowerLayerProtocol;
import ca.uhn.hl7v2.hoh.sign.BouncyCastleCmsMessageSigner;
import ca.uhn.hl7v2.hoh.sockets.CustomCertificateTlsSocketFactory;
import ca.uhn.hl7v2.hoh.sockets.TlsSocketFactory;
import ca.uhn.hl7v2.hoh.util.HapiSocketTlsFactoryWrapper;
import ca.uhn.hl7v2.hoh.util.KeystoreUtils;
import ca.uhn.hl7v2.hoh.util.ServerRoleEnum;
import ca.uhn.hl7v2.llp.ExtendedMinLowerLayerProtocol;
import ca.uhn.hl7v2.llp.LLPException;
import ca.uhn.hl7v2.llp.LowerLayerProtocol;
import ca.uhn.hl7v2.llp.MinLowerLayerProtocol;
import ca.uhn.hl7v2.parser.DefaultXMLParser;
import ca.uhn.hl7v2.parser.Parser;
import ca.uhn.hl7v2.parser.PipeParser;
import ca.uhn.hl7v2.testpanel.api.WorkingStatusBean;
import ca.uhn.hl7v2.testpanel.controller.Controller;
import ca.uhn.hl7v2.testpanel.model.AbstractModelClass;
import ca.uhn.hl7v2.testpanel.model.ActivityBase;
import ca.uhn.hl7v2.testpanel.model.ActivityIncomingBytes;
import ca.uhn.hl7v2.testpanel.model.ActivityInfo;
import ca.uhn.hl7v2.testpanel.model.ActivityOutgoingBytes;
import ca.uhn.hl7v2.testpanel.ui.IDestroyable;
import ca.uhn.hl7v2.testpanel.util.CollectionUtils;
import ca.uhn.hl7v2.testpanel.util.llp.ByteCapturingMinLowerLayerProtocolWrapper;
import ca.uhn.hl7v2.testpanel.xsd.Hl7V2EncodingTypeEnum;
import ca.uhn.hl7v2.util.SocketFactory;
import ca.uhn.hl7v2.validation.builder.support.NoValidationBuilder;
import ca.uhn.hl7v2.validation.impl.ValidationContextImpl;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "AbstractConnection")
public abstract class AbstractConnection extends AbstractModelClass implements IDestroyable {
    public static final String HOH_SIGNATURE_KEYSTORE_STATUS = AbstractConnection.class.getName()
            + "_HOH_SIGNATURE_KEYSTORE_STATUS";
    public static final String HOH_SIGNER_AVAILABLE_ALIASES_PROPERTY = AbstractConnection.class.getName()
            + "_HOH_SIGNER_AVAILABLE_ALIASES";
    public static final String NAME_PROPERTY = AbstractConnection.class.getName() + "_NAME";
    public static final String NEW_MESSAGES_PROPERTY = InboundConnection.class.getName() + "_NEW_MESSAGES_PROP";
    private static final Logger ourLog = LoggerFactory.getLogger(AbstractConnection.class);
    public static final String PERSISTENT_PROPERTY = AbstractConnection.class.getName() + "_PERSISTENT";
    public static final String RECENT_ACTIVITY_PROPERTY = AbstractConnection.class.getName() + "_RECENT_ACTIVITY";
    public static final String STATUS_LINE_PROPERTY = AbstractConnection.class.getName() + "_STATUS_LINE";
    public static final String STATUS_PROPERTY = AbstractConnection.class.getName() + "_STATUS";
    public static final String TLS_KEYSTORE_STATUS = AbstractConnection.class.getName() + "_TLS_KEYSTORE_STATUS";
    public static final String TRANSPORT_PROPERTY = AbstractConnection.class.getName() + "_TRANSPORT";

    @XmlAttribute(required = true)
    private boolean myCaptureBytes;

    @XmlAttribute(required = true)
    private String myCharSet;

    private transient Controller myController;

    @XmlAttribute(required = true)
    private boolean myDetectCharSetInMessage;

    @XmlAttribute(required = true)
    private boolean myDualPort;

    @XmlAttribute(required = true)
    private Hl7V2EncodingTypeEnum myEncoding;

    @XmlAttribute(name = "hoh_authentication")
    private boolean myHohAuthenticationEnabled;

    @XmlAttribute(name = "hoh_auth_pass")
    private String myHohAuthenticationPassword;

    @XmlAttribute(name = "hoh_auth_user")
    private String myHohAuthenticationUsername;

    private boolean myHohSecurityKeystoreCheckIsScheduled;

    private transient List<String> myHohSignatureAvailableAliases;

    @XmlAttribute(name = "hoh_signature_enabled")
    private boolean myHohSignatureEnabled;
    @XmlAttribute(name = "hoh_signature_key")
    private String myHohSignatureKey;

    @XmlAttribute(name = "hoh_signature_key_password")
    private String myHohSignatureKeyPassword;

    @XmlAttribute(name = "hoh_signature_keystore")
    private String myHohSignatureKeystore;
    private transient KeyStore myHohSignatureKeystore_;
    private boolean myHohSignatureKeystoreCheckIsScheduled;
    @XmlAttribute(name = "hoh_signature_keystore_password")
    private String myHohSignatureKeystorePassword;
    private WorkingStatusBean myHohSignatureKeystoreStatus;
    @XmlAttribute(required = true)
    private String myHost;
    @XmlAttribute(name = "httpUriPath", required = false)
    private String myHttpUriPath;
    @XmlAttribute(required = true)
    private String myId;
    @XmlAttribute(required = true)
    private int myIncomingOrSinglePort;
    @XmlAttribute(required = true)
    private String myName;

    @XmlAttribute(required = true)
    private boolean myNameIsExplicitlySet;

    private transient int myNewMessages;

    @XmlAttribute(required = true)
    private int myOutgoingPort;

    @XmlAttribute(required = true)
    private boolean myPersistent;

    private transient ByteArrayOutputStream myReaderCapture = new ByteArrayOutputStream();

    private transient List<ActivityBase> myRecentActivity = new ArrayList<ActivityBase>();

    private transient StatusEnum myStatus = StatusEnum.STOPPED;

    private transient String myStatusLine;

    private transient StreamWatcherThread myStreamWatcherThread;

    @XmlAttribute(required = true)
    private boolean myTls;

    private transient KeyStore myTlsKeystore;

    @XmlAttribute(required = false)
    private String myTlsKeystoreLocation;

    @XmlAttribute(required = false)
    private String myTlsKeystorePassword;
    private transient WorkingStatusBean myTlsKeystoreStatus;
    @XmlAttribute(name = "transport", required = true)
    private TransportStyleEnum myTransport;
    private transient ByteArrayOutputStream myWriterCapture = new ByteArrayOutputStream();

    public AbstractConnection() {
        myId = UUID.randomUUID().toString();
    }

    protected void addActivity(ActivityBase theActivity) {
        myRecentActivity.add(theActivity);
        if (myRecentActivity.size() > 100) {
            myRecentActivity.remove(0);
        }
        firePropertyChange(RECENT_ACTIVITY_PROPERTY, null, null);
    }

    SocketFactory getSocketFactory() {
        return new SocketFactory() {

            @Override
            public Socket createTlsSocket() throws IOException {
                try {
                    if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP && getTlsKeystore() != null) {
                        return createHohSocketFactory().createClientSocket();
                    }
                } catch (KeyStoreException e) {
                    throw new IOException(e.getMessage(), e);
                }
                return SSLSocketFactory.getDefault().createSocket();
            }

            @Override
            public ServerSocket createTlsServerSocket() throws IOException {
                try {
                    if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP && getHohSignatureKeystore_() != null) {
                        return createHohSocketFactory().createServerSocket();
                    }
                } catch (KeyStoreException e) {
                    throw new IOException(e.getMessage(), e);
                }
                return SSLServerSocketFactory.getDefault().createServerSocket();
            }

            private CustomCertificateTlsSocketFactory createHohSocketFactory() throws KeyStoreException {
                KeyStore keystore = getTlsKeystore();
                String keystorePassword = getTlsKeystorePassword();
                CustomCertificateTlsSocketFactory sf = new CustomCertificateTlsSocketFactory(keystore,
                        keystorePassword);
                return sf;
            }

            @Override
            public Socket createSocket() throws IOException {
                return javax.net.SocketFactory.getDefault().createSocket();
            }

            @Override
            public ServerSocket createServerSocket() throws IOException {
                return ServerSocketFactory.getDefault().createServerSocket();
            }

            @Override
            public void configureNewAcceptedSocket(Socket theSocket) throws SocketException {
                // nothing
            }
        };
    }

    public void addNewMessage() {
        int oldValue = myNewMessages;
        int newValue = myNewMessages + 1;

        try {
            fireVetoableChange(NEW_MESSAGES_PROPERTY, oldValue, newValue);
        } catch (PropertyVetoException e) {
            ourLog.debug("Property {} vetoed", NEW_MESSAGES_PROPERTY);
            return;
        }

        myNewMessages = newValue;
        firePropertyChange(NEW_MESSAGES_PROPERTY, oldValue, myNewMessages);
    }

    protected void beforeProcessingNewMessageIn() {
        if (isCaptureBytes()) {
            checkInboundCapture();
        }
    }

    protected void beforeProcessingNewMessageOut() {
        if (isCaptureBytes()) {
            checkOutboundCapture();
        }
    }

    private void checkInboundCapture() {
        synchronized (myReaderCapture) {
            byte[] inboundBytes = myReaderCapture.toByteArray();
            if (inboundBytes.length > 0) {
                addActivity(new ActivityIncomingBytes(new Date(), inboundBytes));
                myReaderCapture.reset();
            }
        }
    }

    protected void addActivityInfoInSwingThread(final String msg) {
        final ActivityBase activity = new ActivityInfo(new Date(), msg);
        addActivityInSwingThread(activity);
    }

    protected void addActivityInSwingThread(final ActivityBase theActivity) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                addActivity(theActivity);
            }
        });
    }

    private void checkOutboundCapture() {
        synchronized (myWriterCapture) {
            byte[] outboundBytes = myWriterCapture.toByteArray();
            if (outboundBytes.length > 0) {
                addActivity(new ActivityOutgoingBytes(new Date(), outboundBytes));
                myWriterCapture.reset();
            }
        }
    }

    public void clearNewMessages() {
        int oldValue = myNewMessages;

        try {
            fireVetoableChange(NEW_MESSAGES_PROPERTY, oldValue, 0);
        } catch (PropertyVetoException e) {
            ourLog.debug("Property {} vetoed", NEW_MESSAGES_PROPERTY);
            return;
        }

        myNewMessages = 0;
        firePropertyChange(NEW_MESSAGES_PROPERTY, oldValue, myNewMessages);
    }

    /**
     * Remove all entries from the recent activity list
     */
    public void clearRecentActivity() {
        myRecentActivity.clear();
        firePropertyChange(RECENT_ACTIVITY_PROPERTY, null, null);
    }

    protected String createDescription() {
        StringBuilder retVal = new StringBuilder();
        if (StringUtils.isNotBlank(myHost)) {
            retVal.append(myHost);
        } else {
            retVal.append("Unknown");
        }

        retVal.append(":");
        if (myIncomingOrSinglePort > 0) {
            retVal.append(myIncomingOrSinglePort);
        } else {
            retVal.append("Unknown");
        }

        if (myOutgoingPort > 0) {
            retVal.append(":");
            retVal.append(myOutgoingPort);
        }
        String name = retVal.toString();
        return name;
    }

    protected LowerLayerProtocol createLlp() throws LLPException {
        LowerLayerProtocol llpClass;
        if (getTransport() == TransportStyleEnum.HL7_OVER_HTTP) {

            ServerRoleEnum role = isInbound() ? ServerRoleEnum.SERVER : ServerRoleEnum.CLIENT;
            Hl7OverHttpLowerLayerProtocol hohLlp = new Hl7OverHttpLowerLayerProtocol(role);
            if (isHohAuthenticationEnabled()) {
                if (isInbound()) {
                    hohLlp.setAuthorizationCallback(new SingleCredentialServerCallback(
                            getHohAuthenticationUsername(), getHohAuthenticationPassword()));
                } else {
                    hohLlp.setAuthorizationCallback(new SingleCredentialClientCallback(
                            getHohAuthenticationUsername(), getHohAuthenticationPassword()));
                }
            }
            if (isHohSignatureEnabled()) {
                BouncyCastleCmsMessageSigner signer = new BouncyCastleCmsMessageSigner();
                try {
                    signer.setKeyStore(getHohSignatureKeystore_());
                } catch (KeyStoreException e) {
                    throw new LLPException(e.getMessage(), e);
                }
                signer.setKeyAlias(getHohSignatureKey());
                signer.setAliasPassword(getHohSignatureKeyPassword());
                hohLlp.setSigner(signer);
            }
            hohLlp.setUriPath(getHttpUriPath());

            llpClass = hohLlp;
        } else if (isDetectCharSetInMessage()) {
            llpClass = new ExtendedMinLowerLayerProtocol();
        } else {
            MinLowerLayerProtocol llp = new MinLowerLayerProtocol();
            llp.setCharset(Charset.forName(getCharSet()));
            llpClass = llp;
        }

        if (isCaptureBytes()) {
            llpClass = new ByteCapturingMinLowerLayerProtocolWrapper(llpClass, myReaderCapture, myWriterCapture);
        }

        return llpClass;
    }

    protected HapiContext createHapiContext() throws IOException {
        try {

            SocketFactory serverSocket;
            if (!isTls()) {
                serverSocket = new ca.uhn.hl7v2.util.StandardSocketFactory();
            } else if (getTlsKeystore() == null) {
                serverSocket = new HapiSocketTlsFactoryWrapper(new TlsSocketFactory());
            } else {
                serverSocket = new HapiSocketTlsFactoryWrapper(
                        new CustomCertificateTlsSocketFactory(getTlsKeystore(), getTlsKeystorePassword()));
            }

            HapiContext ctx = new DefaultHapiContext(new NoValidationBuilder());
            ctx.setLowerLayerProtocol(createLlp());
            ctx.setSocketFactory(serverSocket);

            if (getEncoding() == Hl7V2EncodingTypeEnum.ER_7) {
                ctx.getGenericParser().setPipeParserAsPrimary();
            } else {
                ctx.getGenericParser().setXMLParserAsPrimary();
            }

            return ctx;

        } catch (Exception e) {
            throw new IOException(e.getMessage(), e);
        }
    }

    protected Parser createParser() {
        Parser parser;
        if (getEncoding() == Hl7V2EncodingTypeEnum.ER_7) {
            parser = new PipeParser();
        } else {
            parser = new DefaultXMLParser();
        }
        parser.setValidationContext(new ValidationContextImpl());
        return parser;
    }

    public void destroy() {
        stop();
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object theObj) {
        return (theObj instanceof AbstractConnection) && ((AbstractConnection) theObj).myId.equals(myId);
    }

    /**
     * @return the charSet
     */
    public String getCharSet() {
        return myCharSet;
    }

    /**
     * @return the controller
     */
    public Controller getController() {
        return myController;
    }

    /**
     * @return the encoding
     */
    public Hl7V2EncodingTypeEnum getEncoding() {
        return myEncoding;
    }

    /**
     * @return the hohAuthenticationPassword
     */
    public String getHohAuthenticationPassword() {
        return myHohAuthenticationPassword;
    }

    /**
     * @return the hohAuthenticationUsername
     */
    public String getHohAuthenticationUsername() {
        return myHohAuthenticationUsername;
    }

    /**
     * @return the hohSignatureAvailableAliases
     */
    public List<String> getHohSignatureAvailableAliases() {
        List<String> retVal;
        if (myHohSignatureAvailableAliases != null) {
            retVal = myHohSignatureAvailableAliases;
        } else {
            retVal = Collections.emptyList();
        }
        return retVal;
    }

    /**
     * @return the hohSignatureKey
     */
    public String getHohSignatureKey() {
        return myHohSignatureKey;
    }

    /**
     * @return the hohSignatureKeyPassword
     */
    public String getHohSignatureKeyPassword() {
        return myHohSignatureKeyPassword;
    }

    /**
     * @return the hohSignatureKeystore
     */
    public String getHohSignatureKeystore() {
        return myHohSignatureKeystore;
    }

    /**
     * TODO: rename
     */
    public KeyStore getHohSignatureKeystore_() throws KeyStoreException {
        if (isBlank(getHohSignatureKeystore())) {
            return null;
        }
        if (myHohSignatureKeystore_ != null) {
            return myHohSignatureKeystore_;
        }

        File jksFile = new File(getHohSignatureKeystore());
        if (!jksFile.exists() || !jksFile.canRead()) {
            throw new KeyStoreException("File does not exist or can not be read: " + jksFile.getAbsolutePath());
        }

        char[] password = null;
        if (isNotBlank(myHohSignatureKeystorePassword)) {
            password = myHohSignatureKeystorePassword.toCharArray();
        }

        KeyStore keystore;
        try {
            keystore = KeystoreUtils.loadKeystore(jksFile, password);
        } catch (NoSuchAlgorithmException e) {
            ourLog.error("Failed to load keystore!", e);
            throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
        } catch (CertificateException e) {
            ourLog.error("Failed to load keystore!", e);
            throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
        } catch (IOException e) {
            ourLog.error("Failed to load keystore!", e);
            if (e.getCause() instanceof UnrecoverableKeyException) {
                throw new KeyStoreException("Keystore password appears to be incorrect");
            }
            throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
        }

        if (this instanceof InboundConnection) {
            if (!KeystoreUtils.validateKeystoreForSignatureVerifying(keystore)) {
                throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
            }
        } else if (this instanceof OutboundConnection) {
            if (!KeystoreUtils.validateKeystoreForSignatureSigning(keystore)) {
                throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
            }
        }

        myHohSignatureKeystore_ = keystore;
        return myHohSignatureKeystore_;
    }

    /**
     * @return the hohSignatureKeystorePassword
     */
    public String getHohSignatureKeystorePassword() {
        return myHohSignatureKeystorePassword;
    }

    /**
     * @return the host
     */
    public String getHost() {
        return myHost;
    }

    /**
     * @return the httpUriPath
     */
    public String getHttpUriPath() {
        return myHttpUriPath;
    }

    public String getId() {
        return myId;
    }

    /**
     * @return the incomingOrSinglePort
     */
    public int getIncomingOrSinglePort() {
        return myIncomingOrSinglePort;
    }

    /**
     * @return the name
     */
    public String getName() {
        updateName();
        return myName;
    }

    /**
     * @return the newMessages
     */
    public int getNewMessages() {
        return myNewMessages;
    }

    /**
     * @return the outgoingPort
     */
    public int getOutgoingPort() {
        return myOutgoingPort;
    }

    /**
     * @return the recentActivity
     */
    public List<ActivityBase> getRecentActivity() {
        return myRecentActivity;
    }

    @SuppressWarnings("unchecked")
    public <T extends ActivityBase> List<T> getRecentActivityEntriesOfType(Class<T> theClass) {
        ArrayList<T> retVal = new ArrayList<T>();
        for (Object next : getRecentActivity()) {
            if (theClass.isAssignableFrom(next.getClass())) {
                retVal.add((T) next);
            }
        }
        return retVal;
    }

    /**
     * @return the status
     */
    public StatusEnum getStatus() {
        return myStatus;
    }

    /**
     * @return the statusLine
     */
    public String getStatusLine() {
        return myStatusLine;
    }

    public KeyStore getTlsKeystore() throws KeyStoreException {
        if (isBlank(myTlsKeystoreLocation) || isTls() == false) {
            return null;
        }
        if (myTlsKeystore != null) {
            return myTlsKeystore;
        }

        File jksFile = new File(myTlsKeystoreLocation);
        if (!jksFile.exists() || !jksFile.canRead()) {
            throw new KeyStoreException("File does not exist or can not be read: " + jksFile.getAbsolutePath());
        }

        char[] password = null;
        if (isNotBlank(myTlsKeystorePassword)) {
            password = myTlsKeystorePassword.toCharArray();
        }

        KeyStore keystore;
        try {
            keystore = KeystoreUtils.loadKeystore(jksFile, password);
        } catch (NoSuchAlgorithmException e) {
            ourLog.error("Failed to load keystore!", e);
            throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
        } catch (CertificateException e) {
            ourLog.error("Failed to load keystore!", e);
            throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
        } catch (IOException e) {
            ourLog.error("Failed to load keystore!", e);
            if (e.getCause() instanceof UnrecoverableKeyException) {
                throw new KeyStoreException("Keystore password appears to be incorrect");
            }
            throw new KeyStoreException("Failed to load keystore: " + e.getMessage());
        }

        if (this instanceof InboundConnection) {
            if (!KeystoreUtils.validateKeystoreForTlsReceiving(keystore)) {
                throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
            }
        } else if (this instanceof OutboundConnection) {
            if (!KeystoreUtils.validateKeystoreForTlsSending(keystore)) {
                throw new KeyStoreException("Keystore contains no keys appropriate for receiving data");
            }
        }

        myTlsKeystore = keystore;
        return myTlsKeystore;
    }

    /**
     * @return the tlsKeystoreLocation
     */
    public String getTlsKeystoreLocation() {
        return myTlsKeystoreLocation;
    }

    /**
     * @return the tlsKeystorePassword
     */
    public String getTlsKeystorePassword() {
        return myTlsKeystorePassword;
    }

    /**
     * @return the transport
     */
    public TransportStyleEnum getTransport() {
        if (myTransport == null) {
            if (myDualPort) {
                myTransport = TransportStyleEnum.DUAL_PORT_MLLP;
            } else {
                myTransport = TransportStyleEnum.SINGLE_PORT_MLLP;
            }
        }
        return myTransport;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return myId.hashCode();
    }

    /**
     * @return the captureBytes
     */
    public boolean isCaptureBytes() {
        return myCaptureBytes;
    }

    /**
     * @return the detectCharSetInMessage
     */
    public boolean isDetectCharSetInMessage() {
        return myDetectCharSetInMessage;
    }

    /**
     * @return the dualPort
     */
    public boolean isDualPort() {
        return myDualPort;
    }

    /**
     * @return the hohAuthenticationEnabled
     */
    public boolean isHohAuthenticationEnabled() {
        return myHohAuthenticationEnabled;
    }

    /**
     * @return the hohSignatureEnabled
     */
    public boolean isHohSignatureEnabled() {
        return myHohSignatureEnabled;
    }

    private boolean isInbound() {
        return this instanceof InboundConnection;
    }

    /**
     * @return the nameIsExplicitlySet
     */
    public boolean isNameIsExplicitlySet() {
        return myNameIsExplicitlySet;
    }

    /**
     * @return the persistent
     */
    public boolean isPersistent() {
        return myPersistent;
    }

    /**
     * @return the tls
     */
    public boolean isTls() {
        return myTls;
    }

    private void scheduleHohSecurityKeystoreCheck() {
        synchronized (this) {
            if (myHohSecurityKeystoreCheckIsScheduled == false) {
                setTlsKeystoreStatus(new WorkingStatusBean("Working...", WorkingStatusBean.StatusEnum.WORKING));
            }
            myHohSecurityKeystoreCheckIsScheduled = true;
            if (myController != null) {
                myController.invokeInBackground(new CheckHohSecurityKeystoreRunnable());
            }
        }
    }

    private void scheduleHohSignatureKeystoreCheck() {
        synchronized (this) {
            if (myHohSignatureKeystoreCheckIsScheduled == false) {
                setHohSignatureKeystoreStatus(
                        new WorkingStatusBean("Working...", WorkingStatusBean.StatusEnum.WORKING));
            }
            myHohSignatureKeystoreCheckIsScheduled = true;
            if (myController != null) {
                myController.invokeInBackground(new CheckHohSignatureKeystoreRunnable());
            }
        }
    }

    /**
     * @param theCaptureBytes
     *            the captureBytes to set
     */
    public void setCaptureBytes(boolean theCaptureBytes) {
        myCaptureBytes = theCaptureBytes;
    }

    /**
     * @param theCharSet
     *            the charSet to set
     */
    public void setCharSet(String theCharSet) {
        myCharSet = theCharSet;
    }

    /**
     * @param theController
     *            the controller to set
     */
    public void setController(Controller theController) {
        myController = theController;
        scheduleHohSecurityKeystoreCheck();
        scheduleHohSignatureKeystoreCheck();
    }

    /**
     * @param theDetectCharSetInMessage
     *            the detectCharSetInMessage to set
     */
    public void setDetectCharSetInMessage(boolean theDetectCharSetInMessage) {
        myDetectCharSetInMessage = theDetectCharSetInMessage;
    }

    /**
     * @param theDualPort
     *            the dualPort to set
     */
    public void setDualPort(boolean theDualPort) {
        myDualPort = theDualPort;
        updateName();
    }

    /**
     * @param theEncoding
     *            the encoding to set
     */
    public void setEncoding(Hl7V2EncodingTypeEnum theEncoding) {
        myEncoding = theEncoding;
    }

    /**
     * @param theHohAuthenticationEnabled
     *            the hohAuthenticationEnabled to set
     */
    public void setHohAuthenticationEnabled(boolean theHohAuthenticationEnabled) {
        myHohAuthenticationEnabled = theHohAuthenticationEnabled;
    }

    /**
     * @param theHohAuthenticationPassword
     *            the hohAuthenticationPassword to set
     */
    public void setHohAuthenticationPassword(String theHohAuthenticationPassword) {
        myHohAuthenticationPassword = theHohAuthenticationPassword;
    }

    /**
     * @param theHohAuthenticationUsername
     *            the hohAuthenticationUsername to set
     */
    public void setHohAuthenticationUsername(String theHohAuthenticationUsername) {
        myHohAuthenticationUsername = theHohAuthenticationUsername;
    }

    /**
     * @param theList
     *            the hohSignatureAvailableAliases to set
     */
    public void setHohSignatureAvailableAliases(List<String> theList) {
        List<String> oldValue = myHohSignatureAvailableAliases;
        myHohSignatureAvailableAliases = theList;
        firePropertyChange(HOH_SIGNER_AVAILABLE_ALIASES_PROPERTY, oldValue, theList);
    }

    /**
     * @param theHohSignatureEnabled
     *            the hohSignatureEnabled to set
     */
    public void setHohSignatureEnabled(boolean theHohSignatureEnabled) {
        myHohSignatureEnabled = theHohSignatureEnabled;
        scheduleHohSignatureKeystoreCheck();
    }

    /**
     * @param theHohSignatureKey
     *            the hohSignatureKey to set
     */
    public void setHohSignatureKey(String theHohSignatureKey) {
        myHohSignatureKey = theHohSignatureKey;
        scheduleHohSignatureKeystoreCheck();
    }

    /**
     * @param theHohSignatureKeyPassword
     *            the hohSignatureKeyPassword to set
     */
    public void setHohSignatureKeyPassword(String theHohSignatureKeyPassword) {
        myHohSignatureKeyPassword = theHohSignatureKeyPassword;
        scheduleHohSignatureKeystoreCheck();
    }

    /**
     * @param theHohSignatureKeystore
     *            the hohSignatureKeystore to set
     */
    public void setHohSignatureKeystore(String theHohSignatureKeystore) {
        myHohSignatureKeystore = theHohSignatureKeystore;
        scheduleHohSignatureKeystoreCheck();
    }

    /**
     * @param theHohSignatureKeystorePassword
     *            the hohSignatureKeystorePassword to set
     */
    public void setHohSignatureKeystorePassword(String theHohSignatureKeystorePassword) {
        myHohSignatureKeystorePassword = theHohSignatureKeystorePassword;
        scheduleHohSignatureKeystoreCheck();
    }

    private void setHohSignatureKeystoreStatus(final WorkingStatusBean theStatusBean) {
        final WorkingStatusBean oldValue = myHohSignatureKeystoreStatus;
        myHohSignatureKeystoreStatus = theStatusBean;
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                firePropertyChange(HOH_SIGNATURE_KEYSTORE_STATUS, oldValue, theStatusBean);
            }
        });
    }

    /**
     * @param theHost
     *            the host to set
     */
    public void setHost(String theHost) {
        myHost = theHost;
        updateName();
    }

    /**
     * @param theHttpUriPath
     *            the httpUriPath to set
     */
    public void setHttpUriPath(String theHttpUriPath) {
        myHttpUriPath = theHttpUriPath;
    }

    /**
     * @param theIncomingOrSinglePort
     *            the incomingOrSinglePort to set
     */
    public void setIncomingOrSinglePort(int theIncomingOrSinglePort) {
        myIncomingOrSinglePort = theIncomingOrSinglePort;
        updateName();
    }

    /**
     * @param theName
     *            the name to set
     */
    public void setName(String theName) {
        String oldValue = myName;
        myName = theName;
        firePropertyChange(NAME_PROPERTY, oldValue, myName);
    }

    /**
     * @param theName
     *            the name to set
     */
    public void setNameExplicitly(String theName) {
        if (theName == null) {
            return;
        }
        String oldValue = myName;
        myName = theName;
        if (StringUtils.equals(oldValue, theName) == false) {
            myNameIsExplicitlySet = true;
        }
        firePropertyChange(NAME_PROPERTY, oldValue, myName);
    }

    /**
     * @param theNameIsExplicitlySet
     *            the nameIsExplicitlySet to set
     */
    public void setNameIsExplicitlySet(boolean theNameIsExplicitlySet) {
        myNameIsExplicitlySet = theNameIsExplicitlySet;
    }

    /**
     * @param theOutgoingPort
     *            the outgoingPort to set
     */
    public void setOutgoingPort(int theOutgoingPort) {
        myOutgoingPort = theOutgoingPort;
    }

    /**
     * @param thePersistent
     *            the persistent to set
     */
    public void setPersistent(boolean thePersistent) {
        boolean oldValue = myPersistent;
        myPersistent = thePersistent;
        firePropertyChange(PERSISTENT_PROPERTY, oldValue, myPersistent);
    }

    protected void setStatus(StatusEnum theTryingToStart) {
        StatusEnum oldValue = myStatus;
        myStatus = theTryingToStart;
        firePropertyChange(STATUS_PROPERTY, oldValue, myStatus);
    }

    /**
     * @param theStatusLine
     *            the statusLine to set
     */
    public void setStatusLine(String theStatusLine) {
        String oldValue = myStatusLine;
        myStatusLine = theStatusLine;
        firePropertyChange(STATUS_LINE_PROPERTY, oldValue, theStatusLine);
    }

    public void setTls(boolean theSelected) {
        boolean oldValue = myTls;
        myTls = theSelected;
        if (oldValue != myTls) {
            myTlsKeystore = null;
        }
    }

    public void setTlsKeystoreLocation(String theTlsKeystoreLocation) {
        String oldValue = myTlsKeystoreLocation;
        myTlsKeystoreLocation = theTlsKeystoreLocation;
        if (StringUtils.equals(oldValue, theTlsKeystoreLocation) == false) {
            myTlsKeystore = null;
        }
        scheduleHohSecurityKeystoreCheck();
    }

    public void setTlsKeystorePassword(String theTlsKeystorePassword) {
        String oldValue = myTlsKeystorePassword;
        myTlsKeystorePassword = theTlsKeystorePassword;
        if (StringUtils.equals(oldValue, theTlsKeystorePassword) == false) {
            myTlsKeystore = null;
        }
        scheduleHohSecurityKeystoreCheck();
    }

    private void setTlsKeystoreStatus(final WorkingStatusBean theStatusBean) {
        final WorkingStatusBean oldValue = myTlsKeystoreStatus;
        myTlsKeystoreStatus = theStatusBean;
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                firePropertyChange(TLS_KEYSTORE_STATUS, oldValue, theStatusBean);
            }
        });
    }

    /**
     * @param theTransport
     *            the transport to set
     */
    public void setTransport(TransportStyleEnum theTransport) {
        TransportStyleEnum oldValue = myTransport;
        myTransport = theTransport;
        firePropertyChange(TRANSPORT_PROPERTY, oldValue, myTransport);
    }

    public void start() {
        if (isCaptureBytes()) {
            myStreamWatcherThread = new StreamWatcherThread();
            myStreamWatcherThread.start();
        }
    }

    public void stop() {
        if (myStreamWatcherThread != null) {
            StreamWatcherThread streamWatcherThread = myStreamWatcherThread;
            myStreamWatcherThread = null;
            streamWatcherThread.interrupt();
        }
    }

    private void updateName() {
        if (myName != null && isNameIsExplicitlySet()) {
            return;
        }

        String name = createDescription();
        setName(name);
    }

    private final class CheckHohSecurityKeystoreRunnable implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // wait a bit
            }

            synchronized (AbstractConnection.this) {
                if (!myHohSecurityKeystoreCheckIsScheduled) {
                    return;
                }
                myHohSecurityKeystoreCheckIsScheduled = false;
            }

            KeyStore keystore;
            try {
                keystore = getTlsKeystore();
                if (keystore == null) {
                    if (isTls()) {
                        setTlsKeystoreStatus(
                                new WorkingStatusBean("Using system keystore", WorkingStatusBean.StatusEnum.OK));
                    } else {
                        setTlsKeystoreStatus(new WorkingStatusBean("", WorkingStatusBean.StatusEnum.OK));
                    }
                } else {
                    setTlsKeystoreStatus(
                            new WorkingStatusBean("Keystore appears good", WorkingStatusBean.StatusEnum.OK));
                }
            } catch (KeyStoreException e) {
                ourLog.error("Keystore problem", e);
                setTlsKeystoreStatus(new WorkingStatusBean(e.getMessage(), WorkingStatusBean.StatusEnum.ERROR));
            }
        }

    }

    private final class CheckHohSignatureKeystoreRunnable implements Runnable {
        @Override
        public void run() {

            if (!isHohSignatureEnabled()) {
                setHohSignatureKeystoreStatus(new WorkingStatusBean("", WorkingStatusBean.StatusEnum.OK));
                return;
            }

            if (isBlank(getHohSignatureKeystore())) {
                setHohSignatureKeystoreStatus(
                        new WorkingStatusBean("Select a KeyStore", WorkingStatusBean.StatusEnum.ERROR));
                return;
            }

            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // wait a bit
            }

            synchronized (AbstractConnection.this) {
                if (!myHohSignatureKeystoreCheckIsScheduled) {
                    return;
                }
                myHohSignatureKeystoreCheckIsScheduled = false;
            }

            KeyStore keystore;
            try {
                keystore = getHohSignatureKeystore_();
                if (keystore == null) {
                    setHohSignatureKeystoreStatus(
                            new WorkingStatusBean("No keystore selected", WorkingStatusBean.StatusEnum.ERROR));
                    setHohSignatureAvailableAliases(new ArrayList<String>());
                } else {

                    List<String> aliases = CollectionUtils.enumerationToList(keystore.aliases());
                    if (aliases.size() > 0) {
                        if (isBlank(myHohSignatureKey)) {
                            myHohSignatureKey = aliases.get(0);
                        }
                    } else {
                        setHohSignatureKeystoreStatus(new WorkingStatusBean(
                                "Keystore contains no suitable keys/aliases", WorkingStatusBean.StatusEnum.ERROR));
                        return;
                    }

                    setHohSignatureAvailableAliases(aliases);

                    if (!isInbound()) {
                        if (isBlank(myHohSignatureKeyPassword)) {
                            setHohSignatureKeystoreStatus(new WorkingStatusBean("Key password not specified",
                                    WorkingStatusBean.StatusEnum.ERROR));
                            return;
                        }
                        if (!KeystoreUtils.canRecoverKey(getHohSignatureKeystore_(), getHohSignatureKey(),
                                getHohSignatureKeyPassword())) {
                            setHohSignatureKeystoreStatus(new WorkingStatusBean("Key password is incorrect",
                                    WorkingStatusBean.StatusEnum.ERROR));
                            return;
                        }
                        if (!KeystoreUtils.validateKeyForSignatureSigning(getHohSignatureKeystore_(),
                                getHohSignatureKey(), getHohSignatureKeyPassword())) {
                            setHohSignatureKeystoreStatus(new WorkingStatusBean(
                                    "Key is not appropriate for signing", WorkingStatusBean.StatusEnum.ERROR));
                            return;
                        }
                    }

                    Certificate cert = keystore.getCertificate(myHohSignatureKey);
                    if (cert instanceof X509Certificate) {
                        String signer = ((X509Certificate) cert).getSubjectX500Principal().getName();
                        setHohSignatureKeystoreStatus(new WorkingStatusBean("Key belongs to: " + signer,
                                WorkingStatusBean.StatusEnum.OK));
                    } else {
                        setHohSignatureKeystoreStatus(
                                new WorkingStatusBean("Keystore appears good", WorkingStatusBean.StatusEnum.OK));
                    }

                    // TODO: verify that key alias is usable
                    // TODO: verify whether separate key password is needed

                }
            } catch (KeyStoreException e) {
                ourLog.error("Keystore problem", e);
                setHohSignatureKeystoreStatus(
                        new WorkingStatusBean(e.getMessage(), WorkingStatusBean.StatusEnum.ERROR));
                setHohSignatureAvailableAliases(new ArrayList<String>());
            }
        }

    }

    public enum StatusEnum {
        FAILED(false), STARTED(true), STOPPED(false), TRYING_TO_START(true);

        private boolean myRunning;

        private StatusEnum(boolean theRunning) {
            myRunning = theRunning;
        }

        public boolean isRunning() {
            return myRunning;
        }
    }

    private class StreamWatcherThread extends Thread {

        @Override
        public void run() {
            while (myStreamWatcherThread == this) {
                checkInboundCapture();
                checkOutboundCapture();

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }

    }

}