org.nuxeo.runtime.detection.MulticastDetector.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.runtime.detection.MulticastDetector.java

Source

/*
 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     bstefanescu
 *
 * $Id$
 */

package org.nuxeo.runtime.detection;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
 */
public class MulticastDetector<T> {

    private static final Log log = LogFactory.getLog(MulticastDetector.class);

    protected final InetAddress groupAddr;
    protected final int groupPort;

    protected final String identity;

    protected final Map<String, Peer<T>> peers;

    protected MulticastSocket socket;
    protected long heartBeatTimeout = 5000; // 10 sec

    private DetectionHandler handler;

    private HeartBeatDetection heartBeatDetection;
    private Timer heartBeatTimer;
    private Timer processingTimer;

    public MulticastDetector(String identity, InetAddress groupAddr, int groupPort) throws IOException {
        this.identity = identity;
        this.groupAddr = groupAddr;
        this.groupPort = groupPort;
        socket = new MulticastSocket(groupPort);
        peers = new HashMap<String, Peer<T>>();
    }

    public MulticastDetector(String identity) throws IOException {
        this(identity, "224.1.9.2", 4444);
    }

    public MulticastDetector(String identity, String groupAddr, int groupPort) throws IOException {
        this(identity, InetAddress.getByName(groupAddr), groupPort);
    }

    public void setDetectionHandler(DetectionHandler handler) {
        this.handler = handler;
    }

    public DetectionHandler getDetectionHandler() {
        return handler;
    }

    public MulticastSocket getSocket() {
        return socket;
    }

    public void setHeartBeatTimeout(long ms) {
        heartBeatTimeout = ms;
    }

    public long getHeartBeatTimeout() {
        return heartBeatTimeout;
    }

    public synchronized void start() {
        if (heartBeatDetection != null) {
            return;
        }
        try {
            socket.setSoTimeout((int) heartBeatTimeout); // give a chance to stop heart beat detector
            heartBeatDetection = new HeartBeatDetection();
            heartBeatDetection.start();
            processingTimer = new Timer("Nuxeo.Detection.Cleanup");
            processingTimer.schedule(new CleanupTask(), heartBeatTimeout, heartBeatTimeout);
            socket.joinGroup(groupAddr);
            heartBeatTimer = new Timer("Nuxeo.Detection.HeartBeat");
            heartBeatTimer.schedule(new HeartBeatTask(), 0, heartBeatTimeout);
        } catch (Throwable t) {
            stop();
        }
    }

    public synchronized void stop() {
        if (heartBeatDetection == null) {
            return;
        }
        heartBeatTimer.cancel();
        heartBeatTimer = null;
        heartBeatDetection.cancel();
        heartBeatDetection = null;
        processingTimer.cancel();
        processingTimer = null;
    }

    public String getIdentity() {
        return identity;
    }

    @SuppressWarnings("unchecked")
    public Peer<T>[] getPeers() {
        synchronized (peers) {
            return peers.values().toArray(new Peer[peers.size()]);
        }
    }

    private DatagramPacket createHeartBeat() {
        byte[] bytes = identity.getBytes();
        return new DatagramPacket(bytes, bytes.length, groupAddr, groupPort);
    }

    private String readHeartBeat(DatagramPacket p) {
        return new String(p.getData(), p.getOffset(), p.getLength());
    }

    protected void notifyPeerOnline(Peer<T> peer) {
        // async exec needed in that case otherwise
        // heartbeat detection may be significantly delayed and some heartbeats lost
        if (handler != null) {
            processingTimer.schedule(new NotifyTask(peer, true), 0);
        }
    }

    protected void notifyPeerOffline(Peer<T> peer) {
        // async exec not needed in that case
        if (handler != null) {
            handler.peerOffline(peer);
        }
    }

    class HeartBeatDetection extends Thread {
        private boolean running = false;
        private final Object runLock = new Object();

        HeartBeatDetection() {
            super("Nuxeo.HeartBeatDetection");
        }

        public void cancel() {
            synchronized (runLock) {
                running = false;
            }
            interrupt();
        }

        @Override
        public synchronized void start() {
            synchronized (runLock) {
                running = true;
            }
            super.start();
        }

        @Override
        public void run() {
            while (true) {
                //System.out.println(identity+": running heart beat listener");
                try {
                    synchronized (runLock) {
                        if (!running) {
                            break; // detector was stopped
                        }
                    }
                    byte[] bytes = new byte[4000];
                    DatagramPacket p = new DatagramPacket(bytes, bytes.length);
                    socket.receive(p); // block until a new message is received
                    String identity = readHeartBeat(p);
                    if (MulticastDetector.this.identity.equals(identity)) {
                        continue;
                    }
                    Peer<T> peer;
                    synchronized (peers) {
                        peer = peers.get(identity);
                        if (peer == null) { // create new peer
                            peer = new Peer<T>(p.getAddress(), p.getPort(), identity);
                            assert peer.addr.equals(p.getAddress());
                            assert peer.port == p.getPort();
                            peers.put(identity, peer);
                        } else { // update peer last heart beat
                            peer.lastHeartBeat = System.currentTimeMillis();
                            peer = null;
                        }
                    }
                    if (peer != null) { // new peer detected
                        System.out.println("Peer online: " + peer);
                        notifyPeerOnline(peer);
                    }
                } catch (SocketTimeoutException e) {
                    // socket timeout -> continue
                } catch (Throwable e) {
                    log.error(e, e);
                }
            }
        }
    }

    class HeartBeatTask extends TimerTask {
        @Override
        public void run() {
            //System.out.println(identity+": running heart beat task");
            try {
                socket.send(createHeartBeat());
            } catch (IOException e) {
                log.error(e, e);
            }
        }
    }

    class CleanupTask extends TimerTask {
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            //System.out.println(identity+": running cleanup task");
            long tm = System.currentTimeMillis();
            // copy existing peers into an array to avoid race conditions
            Peer[] arPeers = getPeers();
            // remove expired peers
            for (Peer peer : arPeers) {
                if (tm - peer.lastHeartBeat > heartBeatTimeout * 2) {
                    synchronized (peers) {
                        peers.remove(peer.identity);
                    }
                    System.out.println("Peer Offline: " + peer);
                    notifyPeerOffline(peer);
                    peer.data = null;
                }
            }
        }
    }

    class NotifyTask extends TimerTask {
        private final boolean online;
        private final Peer peer;

        NotifyTask(Peer<T> peer, boolean online) {
            this.peer = peer;
            this.online = online;
        }

        @Override
        public void run() {
            if (handler == null) {
                return;
            }
            if (online) {
                handler.peerOnline(peer);
            } else {
                handler.peerOffline(peer);
            }
        }
    }

}