All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bidib.jbidibc.netbidib.client.pairingstates.DefaultPairingStateHandler Maven / Gradle / Ivy

package org.bidib.jbidibc.netbidib.client.pairingstates;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.bidib.jbidibc.messages.enums.NetBidibSocketType;
import org.bidib.jbidibc.messages.enums.PairingResult;
import org.bidib.jbidibc.messages.exception.EstablishCommunicationFailedException;
import org.bidib.jbidibc.messages.exception.InvalidConfigurationException;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.jbidibc.messages.message.BidibCommandMessage;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.LocalLogoffMessage;
import org.bidib.jbidibc.messages.message.LocalLogonAckMessage;
import org.bidib.jbidibc.messages.message.LocalLogonMessage;
import org.bidib.jbidibc.messages.message.netbidib.BidibLinkData;
import org.bidib.jbidibc.messages.message.netbidib.LocalLinkMessage;
import org.bidib.jbidibc.messages.message.netbidib.LocalProtocolSignatureMessage;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData.PairingStatus;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.netbidib.NetBidibContextKeys;
import org.bidib.jbidibc.netbidib.pairingstore.LocalPairingStore.PairingLookupResult;
import org.bidib.jbidibc.netbidib.pairingstore.PairingStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DefaultPairingStateHandler implements PairingStateHandler, PairingStateInteractionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPairingStateHandler.class);

    public static final String PROPERTY_CURRENTPAIRINGSTATE = "currentPairingState";

    private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);

    private PairingStateEnum currentPairingState = PairingStateEnum.Initial;

    private Map pairingStateMap = new HashMap<>();

    private ProxyBidibLinkData remotePartnerLinkData;

    private ProxyBidibLinkData clientLinkData;

    private final NetBidibMessageSender netBidibMessageSender;

    private final PairingInteractionPublisher pairingInteractionPublisher;

    private final BidibRequestFactory bidibRequestFactory;

    private Object pairingStoreLock = new Object();

    private PairingStore pairingStore;

    public DefaultPairingStateHandler(final NetBidibMessageSender netBidibMessageSender,
        final PairingInteractionPublisher pairingInteractionPublisher, final BidibRequestFactory bidibRequestFactory) {
        this.netBidibMessageSender = netBidibMessageSender;
        this.pairingInteractionPublisher = pairingInteractionPublisher;
        this.bidibRequestFactory = bidibRequestFactory;
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        this.pcs.addPropertyChangeListener(propertyName, listener);
    }

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
        this.pcs.removePropertyChangeListener(propertyName, listener);
    }

    private NetBidibSocketType socketType;

    @Override
    public void setNetBidibSocketType(final NetBidibSocketType socketType) {
        this.socketType = socketType;
    }

    protected NetBidibSocketType getSocketType() {
        return this.socketType;
    }

    @Override
    public void initialize(
        final BidibLinkData remotePartnerLinkData, final BidibLinkData clientLinkData,
        final PairingStore pairingStore) {
        this.remotePartnerLinkData = new ProxyBidibLinkData(remotePartnerLinkData);
        this.clientLinkData = new ProxyBidibLinkData(clientLinkData);
        this.pairingStore = pairingStore;

        LOGGER.info("Current pairingStore content: {}", this.pairingStore);

        // check if the pairing store is available and valid
        this.pairingStore.checkValid();

        pairingStateMap
            .put(PairingStateEnum.Initial,
                new InitialPairingState(this, this.remotePartnerLinkData, this.clientLinkData));
        pairingStateMap
            .put(PairingStateEnum.Unpaired,
                new UnpairedPairingState(this, this.remotePartnerLinkData, this.clientLinkData));
        pairingStateMap
            .put(PairingStateEnum.MyRequest,
                new MyRequestPairingState(this, this.remotePartnerLinkData, this.clientLinkData));
        pairingStateMap
            .put(PairingStateEnum.TheirRequest,
                new TheirRequestPairingState(this, this.remotePartnerLinkData, this.clientLinkData));
        pairingStateMap
            .put(PairingStateEnum.Paired,
                new PairedPairingState(this, this.remotePartnerLinkData, this.clientLinkData));
        pairingStateMap
            .put(PairingStateEnum.Withdrawn,
                new WithdrawnPairingState(this, this.remotePartnerLinkData, this.clientLinkData));

        this.addPropertyChangeListener(DefaultPairingStateHandler.PROPERTY_CURRENTPAIRINGSTATE, evt -> {

            final PairingStateEnum currentPairingState = getCurrentPairingState();
            LOGGER
                .info("The pairing state has changed, currentPairingState: {}, clientLinkData.pairingStatus: {}",
                    currentPairingState, DefaultPairingStateHandler.this.clientLinkData.getPairingStatus());

            switch (currentPairingState) {
                case Paired:
                    LOGGER.info("We are in paired state.");
                    signalPairingFinished(PairingResult.PAIRED, DefaultPairingStateHandler.this.remotePartnerLinkData);
                    break;
                case Unpaired:
                    LOGGER.info("We are in unpaired state.");
                    if (DefaultPairingStateHandler.this.clientLinkData
                        .getPairingStatus() == PairingStatus.PAIRING_REQUESTED) {
                        LOGGER.info("The remote partner has rejected the pairing.");
                        signalPairingFinished(PairingResult.UNPAIRED,
                            DefaultPairingStateHandler.this.remotePartnerLinkData);
                    }
                    break;
                case Withdrawn:
                    LOGGER.info("We are in withdrawn state.");
                    if (DefaultPairingStateHandler.this.clientLinkData
                        .getPairingStatus() == PairingStatus.PAIRING_REQUESTED
                        || DefaultPairingStateHandler.this.clientLinkData.getPairingStatus() == PairingStatus.PAIRED) {
                        LOGGER.info("The remote partner has withdrawn the pairing.");
                        signalPairingFinished(PairingResult.UNPAIRED,
                            DefaultPairingStateHandler.this.remotePartnerLinkData);
                    }
                    break;
                default:
                    LOGGER.info("Ignored change of pairing state: {}", currentPairingState);
                    break;
            }

        });

        this.clientLinkData.addPropertyChangeListener(ProxyBidibLinkData.PROPERTY_PAIRINGSTATUS, evt -> {
            final PairingStatus pairingStatus = this.clientLinkData.getPairingStatus();
            LOGGER.info("The pairing status of the client has changed, pairingState: {}", pairingStatus);

            switch (pairingStatus) {
                case PAIRED:
                    LOGGER.info("The client signalled the paired state.");
                    // signalPairingState(PairingStateEnum.Paired, DefaultPairingStateHandler.this.clientLinkData,
                    // DefaultPairingStateHandler.this.remotePartnerLinkData);
                    break;
                case UNPAIRED:
                    LOGGER.info("The client signalled the unpaired state.");
                    // signalPairingState(PairingStateEnum.Unpaired, DefaultPairingStateHandler.this.clientLinkData,
                    // DefaultPairingStateHandler.this.remotePartnerLinkData);
                    break;
                default:
                    break;
            }
        });

        this.remotePartnerLinkData.addPropertyChangeListener(ProxyBidibLinkData.PROPERTY_PAIRINGSTATUS, evt -> {
            final PairingStatus pairingStatus = this.remotePartnerLinkData.getPairingStatus();
            LOGGER.info("The pairing status of the remote partner has changed, pairingState: {}", pairingStatus);

            switch (pairingStatus) {
                case PAIRED:
                    LOGGER.info("The remote partner signalled the paired state.");
                    signalPairingState(PairingStateEnum.Paired, DefaultPairingStateHandler.this.remotePartnerLinkData,
                        DefaultPairingStateHandler.this.clientLinkData);
                    break;
                case UNPAIRED:
                    LOGGER.info("The remote partner signalled the unpaired state.");
                    signalPairingState(PairingStateEnum.Unpaired, DefaultPairingStateHandler.this.remotePartnerLinkData,
                        DefaultPairingStateHandler.this.clientLinkData);
                    break;
                case PAIRING_REQUESTED:
                    LOGGER.info("The remote partner signalled the pairing requested.");
                    updatePairingStoreLinkData(DefaultPairingStateHandler.this.remotePartnerLinkData);
                    break;
                case WITHDRAWN:
                    LOGGER.info("The remote partner signalled the witdrawn state.");
                    updatePairingStoreLinkData(DefaultPairingStateHandler.this.remotePartnerLinkData);
                    break;
                default:
                    break;
            }
        });

    }

    protected BidibRequestFactory getRequestFactory() {
        return bidibRequestFactory;
    }

    /**
     * @return the current pairingState
     */
    protected PairingStateEnum getCurrentPairingState() {
        return currentPairingState;
    }

    /**
     * @param pairingState
     *            the pairingState to set
     */
    protected void setCurrentPairingState(PairingStateEnum pairingState) {
        LOGGER.info("Set the new pairingState: {}", pairingState);
        PairingStateEnum oldValue = this.currentPairingState;

        this.currentPairingState = pairingState;

        this.pcs.firePropertyChange(PROPERTY_CURRENTPAIRINGSTATE, oldValue, this.currentPairingState);
    }

    /**
     * @return the remotePartnerLinkData
     */
    public ProxyBidibLinkData getRemotePartnerLinkData() {
        return remotePartnerLinkData;
    }

    /**
     * @return the clientLinkData
     */
    public ProxyBidibLinkData getClientLinkData() {
        return clientLinkData;
    }

    protected void publishMessage(final BidibCommandMessage message) throws ProtocolException {
        LOGGER.info("Publish the message: {}", message);
        netBidibMessageSender.publishNetBidibMessage(message);
    }

    /**
     * Signal the pairing request from the remote partner to the UI.
     * 
     * @param remotePartnerLinkData
     *            the link data
     */
    protected void signalPairingRequest(final BidibLinkData remotePartnerLinkData) {
        LOGGER.info("Signal the pairing request, remotePartnerLinkData: {}", remotePartnerLinkData);

        Integer pairingTimeout = remotePartnerLinkData.getRequestedPairingTimeout();

        final Context context = new DefaultContext();

        // Prepare the remote partner information
        context.register(NetBidibContextKeys.KEY_REQUESTOR_NAME, remotePartnerLinkData.getRequestorName());
        context.register(NetBidibContextKeys.KEY_DESCRIPTOR_UID, remotePartnerLinkData.getUniqueId());
        context.register(NetBidibContextKeys.KEY_DESCRIPTOR_PROD_STRING, remotePartnerLinkData.getProdString());
        context.register(NetBidibContextKeys.KEY_DESCRIPTOR_USER_STRING, remotePartnerLinkData.getUserString());
        context.register(NetBidibContextKeys.KEY_PAIRING_TIMEOUT, pairingTimeout);

        if (getSocketType() == NetBidibSocketType.clientSocket) {
            context.register(NetBidibContextKeys.KEY_CONNECTION_TYPE, NetBidibContextKeys.VALUE_CONNECTION_TYPE_CLIENT);
        }
        else {
            context.register(NetBidibContextKeys.KEY_CONNECTION_TYPE, NetBidibContextKeys.VALUE_CONNECTION_TYPE_SERVER);
        }

        String actionKey = NetBidibContextKeys.KEY_ACTION_PAIRING_REQUESTED;

        LOGGER.info("Publish the user action actionKey: {}, with context: {}", actionKey, context);
        this.pairingInteractionPublisher.publishUserAction(actionKey, context);
    }

    protected void signalPairingFinished(final PairingResult pairingResult, final BidibLinkData remotePartnerLinkData) {

        // update the pairing store
        updatePairingStore(remotePartnerLinkData.getUniqueId(), pairingResult);

        // notify the UI
        this.pairingInteractionPublisher.publishPairingFinished(pairingResult);
    }

    protected void publishLocalLogon(int localNodeAddr, long uniqueId) {
        this.pairingInteractionPublisher.publishLocalLogon(localNodeAddr, uniqueId);
    }

    protected void publishLocalLogoff() {
        this.pairingInteractionPublisher.publishLocalLogoff();
    }

    /**
     * Signal the pairing state to the UI.
     * 
     * @param pairingState
     *            the pairing state
     * @param remotePartnerLinkData
     *            the link data
     */
    protected void signalPairingState(
        final PairingStateEnum pairingState, final ProxyBidibLinkData remotePartnerLinkData,
        final ProxyBidibLinkData clientLinkData) {
        LOGGER
            .info(
                "Signal the pairing state, pairingState: {}, remotePartnerLinkData: {}, final BidibLinkData remotePartnerLinkData: {}",
                pairingState, remotePartnerLinkData, clientLinkData);

        final Context context = new DefaultContext();

        Integer pairingTimeout = clientLinkData.getRequestedPairingTimeout();

        // Prepare the remote partner information
        context.register(NetBidibContextKeys.KEY_REQUESTOR_NAME, remotePartnerLinkData.getRequestorName());
        context.register(NetBidibContextKeys.KEY_DESCRIPTOR_UID, remotePartnerLinkData.getUniqueId());
        context.register(NetBidibContextKeys.KEY_DESCRIPTOR_PROD_STRING, remotePartnerLinkData.getProdString());
        context.register(NetBidibContextKeys.KEY_DESCRIPTOR_USER_STRING, remotePartnerLinkData.getUserString());
        context.register(NetBidibContextKeys.KEY_PAIRING_STATE, pairingState);
        context.register(NetBidibContextKeys.KEY_PAIRING_TIMEOUT, pairingTimeout);

        if (getSocketType() == NetBidibSocketType.clientSocket) {
            context.register(NetBidibContextKeys.KEY_CONNECTION_TYPE, NetBidibContextKeys.VALUE_CONNECTION_TYPE_CLIENT);
        }
        else {
            context.register(NetBidibContextKeys.KEY_CONNECTION_TYPE, NetBidibContextKeys.VALUE_CONNECTION_TYPE_SERVER);
        }

        String actionKey = NetBidibContextKeys.KEY_ACTION_PAIRING_STATE;

        LOGGER.info("Publish the user action actionKey: {}, with context: {}", actionKey, context);
        this.pairingInteractionPublisher.publishUserAction(actionKey, context);
    }

    @Override
    public void onLocalProtocolSignature(LocalProtocolSignatureMessage localProtocolSignatureMessage) {

        final AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {

            if (this.initialResponseLock != null) {
                LOGGER.info("The initial localProtocolSignature was received.");

                this.initialResponseLock.countDown();
            }

            currentState.onLocalProtocolSignature(localProtocolSignatureMessage);
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    @Override
    public void onLocalLink(LocalLinkMessage localLinkMessage) {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {
            currentState.onLocalLink(localLinkMessage);
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    @Override
    public void onLocalLogonAck(LocalLogonAckMessage localLogonAckMessage) {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {
            currentState.onLocalLogonAck(localLogonAckMessage);
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    @Override
    public void onLocalLogon(LocalLogonMessage localLogonMessage) {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {
            currentState.onLocalLogon(localLogonMessage);
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    @Override
    public void onLocalLogoff(LocalLogoffMessage localLogoffMessage) {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {
            currentState.onLocalLogoff(localLogoffMessage);
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    protected boolean isPaired(final BidibLinkData bidibLinkData) {
        LOGGER.info("Check if the remote partner is paired: {}", bidibLinkData);

        PairingLookupResult isPaired = PairingLookupResult.UNPAIRED;

        if (this.pairingStore != null) {
            isPaired = this.pairingStore.isPaired(bidibLinkData.getUniqueId());
            if (PairingLookupResult.MISSING == isPaired) {
                // create the entry
                try {
                    synchronized (this.pairingStoreLock) {
                        this.pairingStore
                            .setPaired(bidibLinkData.getUniqueId(), bidibLinkData.getRequestorName(),
                                bidibLinkData.getProdString(), bidibLinkData.getUserString(),
                                bidibLinkData.getProtocolVersion(), false);
                        this.pairingStore.store();
                    }
                }
                catch (InvalidConfigurationException ex) {
                    LOGGER.warn("Set the pairing result in the pairing store failed.", ex);

                    // show error in console
                    this.pairingInteractionPublisher.handleError(ex);
                }
                catch (Exception ex) {
                    LOGGER.warn("Set the pairing result in the pairing store failed.", ex);

                    // show error in console
                    this.pairingInteractionPublisher
                        .handleError(new RuntimeException("Set the pairing result in the pairing store failed.", ex));
                }

                isPaired = PairingLookupResult.UNPAIRED;
            }
        }
        else {
            LOGGER.warn("No pairing store configured!");
        }
        LOGGER.info("isPaired: {}, uniqueId: {}", isPaired, ByteUtils.formatHexUniqueId(bidibLinkData.getUniqueId()));

        return PairingLookupResult.PAIRED == isPaired;
    }

    /**
     * Update the pairing result in the pairing store.
     * 
     * @param uniqueId
     *            the uniqueId
     * @param pairingResult
     *            the pairing result
     */
    private void updatePairingStore(Long uniqueId, final PairingResult pairingResult) {
        LOGGER
            .info("Update the pairing store, uniqueId: {}, pairingResult: {}", ByteUtils.getUniqueIdAsString(uniqueId),
                pairingResult);

        if (this.pairingStore != null) {
            try {

                synchronized (this.pairingStoreLock) {
                    this.pairingStore.setPaired(uniqueId, PairingResult.PAIRED == pairingResult);
                    this.pairingStore.store();
                }
            }
            catch (InvalidConfigurationException ex) {
                LOGGER.warn("Set the pairing result in the pairing store failed.", ex);

                // show error in console
                this.pairingInteractionPublisher.handleError(ex);
            }
            catch (Exception ex) {
                LOGGER.warn("Set the pairing result in the pairing store failed.", ex);

                // show error in console
                this.pairingInteractionPublisher
                    .handleError(new RuntimeException("Set the pairing result in the pairing store failed.", ex));
            }
        }
        else {
            LOGGER.warn("No pairing store configured!");
        }
    }

    private void updatePairingStoreLinkData(final BidibLinkData bidibLinkData) {
        LOGGER.info("Update the pairing store, bidibLinkData: {}", bidibLinkData);

        if (this.pairingStore != null) {
            try {

                synchronized (this.pairingStoreLock) {
                    this.pairingStore.updateLinkData(bidibLinkData);
                    this.pairingStore.store();
                }
            }
            catch (InvalidConfigurationException ex) {
                LOGGER.warn("Set the pairing result in the pairing store failed.", ex);

                // show error in console
                this.pairingInteractionPublisher.handleError(ex);
            }
            catch (Exception ex) {
                LOGGER.warn("Set the pairing result in the pairing store failed.", ex);

                // show error in console
                this.pairingInteractionPublisher
                    .handleError(new RuntimeException("Set the pairing result in the pairing store failed.", ex));
            }
        }
        else {
            LOGGER.warn("No pairing store configured!");
        }
    }

    @Override
    public void initiatePairing() {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {

            currentState.initiatePairing(this.clientLinkData.getRequestedPairingTimeout());
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    @Override
    public void pairingResult(Long uniqueId, final PairingResult pairingResult) {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {
            LOGGER
                .info("Set the pairing result: {}, uniqueId: {}", pairingResult,
                    ByteUtils.getUniqueIdAsString(uniqueId));

            updatePairingStore(uniqueId, pairingResult);

            currentState.pairingResult(pairingResult);
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    @Override
    public void timeoutPairing() {

        AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {
            currentState.timeoutPairing();
        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }

    // check if the initial response from the bidib device is received
    private CountDownLatch initialResponseLock;

    @Override
    public void sendNetBidibStartupSequence() throws EstablishCommunicationFailedException {

        final AbstractPairingState currentState = pairingStateMap.get(currentPairingState);
        if (currentState != null) {

            // we must check if the response from the client was received
            initialResponseLock = new CountDownLatch(1);

            // send the message to the bidib interface
            currentState.sendNetBidibStartupSequence();

            try {
                LOGGER.info("Wait for startup of netBidibPort instance.");
                boolean completed = initialResponseLock.await(3000, TimeUnit.MILLISECONDS);

                LOGGER.info("Establish communication with bidib interface passed and has completed: {}", completed);
                if (!completed) {
                    throw new EstablishCommunicationFailedException(
                        "Establish communication with bidib interface did not complete in 3s", "");
                }

                initialResponseLock = null;
            }
            catch (InterruptedException ex) {
                LOGGER.warn("Wait for establish communication with bidib interface was interrupted.", ex);
                throw new EstablishCommunicationFailedException(
                    "Wait for establish communication with bidib interface was interrupted.", "");
            }

        }
        else {
            LOGGER.error("No pairing state configured for current pairing state: {}", currentPairingState);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy