org.anhonesteffort.chnlbrkr.chnlzr.IdleChnlzrController.java Source code

Java tutorial

Introduction

Here is the source code for org.anhonesteffort.chnlbrkr.chnlzr.IdleChnlzrController.java

Source

/*
 * Copyright (C) 2015 An Honest Effort LLC, coping.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.anhonesteffort.chnlbrkr.chnlzr;

import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import org.anhonesteffort.chnlbrkr.BrkrStateChangeListener;
import org.anhonesteffort.chnlbrkr.ServerHandler;
import org.anhonesteffort.chnlzr.CapnpUtil;
import org.anhonesteffort.chnlzr.Util;
import org.apache.commons.math3.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;

import static org.anhonesteffort.chnlzr.Proto.Capabilities;
import static org.anhonesteffort.chnlzr.Proto.ChannelRequest;

public class IdleChnlzrController {

    private static final Logger log = LoggerFactory.getLogger(IdleChnlzrController.class);

    private final Map<String, IdleChnlzrConnection> queue = new ConcurrentHashMap<>();
    private final Queue<BrkrStateChangeListener> listeners = new ConcurrentLinkedQueue<>();
    private final Object txnLock = new Object();

    private final IdleChnlzrConnectionFactory factory;
    private long count = 0;

    public IdleChnlzrController(IdleChnlzrConnectionFactory factory) {
        this.factory = factory;
    }

    private Pair<IdleChnlzrConnection, Double> closest(ChannelRequest.Reader request,
            List<IdleChnlzrConnection> candidates) {
        IdleChnlzrConnection closestChnlzr = null;
        double minLocationDiff = -1d;

        for (IdleChnlzrConnection chnlzr : candidates) {
            Capabilities.Reader capabilities = chnlzr.getCapabilities();
            double locationDiffKm = Util.kmDistanceBetween(request.getLatitude(), request.getLongitude(),
                    capabilities.getLatitude(), capabilities.getLongitude());

            if (locationDiffKm < minLocationDiff || closestChnlzr == null) {
                minLocationDiff = locationDiffKm;
                closestChnlzr = chnlzr;
            }
        }

        return new Pair<>(closestChnlzr, minLocationDiff);
    }

    private void onStateChange() {
        List<Capabilities.Reader> capabilities = getCapabilities();
        listeners.forEach(listener -> listener.onBrkrCapabilitiesChange(capabilities));
    }

    public void addListener(BrkrStateChangeListener listener) {
        listeners.add(listener);
    }

    public void removeListener(BrkrStateChangeListener listener) {
        listeners.remove(listener);
    }

    public boolean registerAndSwap(ServerHandler connection, String chnlzrId) {
        if (!queue.containsKey(chnlzrId)) {
            Futures.addCallback(factory.create(connection, chnlzrId), new ChnlzrConnectionCallback(chnlzrId));
            log.debug(chnlzrId + " registered");
            return true;
        } else {
            return false;
        }
    }

    // todo: this won't work as intended if we're awaiting capabilities from $chnlzrId
    public void unregister(String chnlzrId) {
        synchronized (txnLock) {
            if (queue.remove(chnlzrId) != null) {
                log.info(chnlzrId + " unregistered");
                onStateChange();
            }
        }
    }

    public List<Capabilities.Reader> getCapabilities() {
        return queue.keySet().stream().map(queue::get).map(IdleChnlzrConnection::getCapabilities)
                .collect(Collectors.toList());
    }

    public Optional<IdleChnlzrConnection> removeCapable(ChannelRequest.Reader request) {
        Optional<IdleChnlzrConnection> capable = Optional.empty();

        synchronized (txnLock) {
            List<IdleChnlzrConnection> candidates = queue.keySet().stream().map(queue::get)
                    .filter(chnlzr -> request.getPolarization() == 0
                            || chnlzr.getCapabilities().getPolarization() == request.getPolarization())
                    .filter(chnlzr -> CapnpUtil.spec(chnlzr.getCapabilities())
                            .containsChannel(CapnpUtil.spec(request)))
                    .collect(Collectors.toList());

            if (candidates.size() > 0) {
                Pair<IdleChnlzrConnection, Double> closestChnlzr = closest(request, candidates);

                if (request.getMaxLocationDiff() <= 0
                        || closestChnlzr.getSecond() <= request.getMaxLocationDiff()) {
                    capable = Optional.of(closestChnlzr.getFirst());
                }
            }

            if (capable.isPresent()) {
                log.info(capable.get().getId() + " removing capable connection " + (++count));
                capable.get().getContext().writeAndFlush(CapnpUtil.punch());
                if (queue.remove(capable.get().getId()) != null) {
                    onStateChange();
                }
            }
        }

        return capable;
    }

    private class ChnlzrConnectionCallback implements FutureCallback<IdleChnlzrConnection> {
        private final String chnlzrId;

        public ChnlzrConnectionCallback(String chnlzrId) {
            this.chnlzrId = chnlzrId;
        }

        @Override
        public void onSuccess(IdleChnlzrConnection connection) {
            Optional<IdleChnlzrConnection> existingConnection = Optional
                    .ofNullable(queue.putIfAbsent(chnlzrId, connection));

            if (!existingConnection.isPresent()) {
                connection.getContext().channel().closeFuture()
                        .addListener(new ConnectionClosedCallback(connection));
                onStateChange();
            } else {
                connection.getContext().close();
            }
        }

        @Override
        public void onFailure(Throwable throwable) {
            log.warn(chnlzrId + " failed to read capabilities from chnlzr", throwable);
        }
    }

    private class ConnectionClosedCallback implements ChannelFutureListener {
        private final IdleChnlzrConnection connection;

        public ConnectionClosedCallback(IdleChnlzrConnection connection) {
            this.connection = connection;
        }

        @Override
        public void operationComplete(ChannelFuture channelFuture) {
            if (queue.remove(connection.getId(), connection)) {
                onStateChange();
            }
        }
    }

}