Package org.apache.openejb.server.discovery

Source Code of org.apache.openejb.server.discovery.MultipointServer

/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (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.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package org.apache.openejb.server.discovery;

import org.apache.openejb.monitoring.Event;
import org.apache.openejb.monitoring.Managed;
import org.apache.openejb.server.ServerRuntimeException;
import org.apache.openejb.util.DaemonThreadFactory;
import org.apache.openejb.util.Duration;
import org.apache.openejb.util.Join;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;

import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @version $Rev$ $Date$
*/
@Managed
public class MultipointServer {

    private static final Logger log = Logger.getInstance(LogCategory.OPENEJB_SERVER.createChild("discovery").createChild("multipoint"), MultipointServer.class);

    private static final URI END_LIST = URI.create("end:list");

    private final int port;

    private final URI me;

    private final Set<URI> roots = new LinkedHashSet<URI>();

    private final Event runs = new Event();

    private final Event heartbeats = new Event();

    private final Event reconnects = new Event();
    private final Event sessionsCreated = new Event();

    /**
     * Only used for toString to make debugging easier
     */
    private final String name;

    private final Tracker tracker;

    private final LinkedList<Host> connect = new LinkedList<Host>();
    private final Map<URI, Session> connections = new HashMap<URI, Session>();

    private long joined = 0;

    private final long reconnectDelay;

    private final ServerSocketChannel serverChannel;

    private final Selector selector;

    private final Lock lock = new ReentrantLock();
    private final Condition started = lock.newCondition();
    private final Condition stopped = lock.newCondition();

    public MultipointServer(final int port, final Tracker tracker) throws IOException {
        this("localhost", "localhost", port, tracker, randomColor(), true, new HashSet<URI>(0), new Duration(30, TimeUnit.SECONDS));
    }

    public MultipointServer(final String bindHost, String broadcastHost, final int port, final Tracker tracker, final String name, final boolean debug, final Set<URI> roots, Duration reconnectDelay) throws IOException {
        if (tracker == null)
            throw new NullPointerException("tracker cannot be null");
        if (bindHost == null)
            throw new NullPointerException("host cannot be null");

        if (broadcastHost == null)
            broadcastHost = bindHost;
        if (reconnectDelay == null)
            reconnectDelay = new Duration(30, TimeUnit.SECONDS);

        this.tracker = tracker;
        this.name = name;

        if (roots != null) {
            for (final URI uri : roots) {
                this.roots.add(normalize(uri));
            }
        }

        this.reconnectDelay = reconnectDelay.getTime(TimeUnit.NANOSECONDS);

        final String format = String.format("MultipointServer(bindHost=%s, discoveryHost=%s, port=%s, name=%s, debug=%s, roots=%s, reconnectDelay='%s')",
            bindHost,
            broadcastHost,
            port,
            name,
            debug,
            this.roots.size(),
            reconnectDelay.toString());

        log.debug(format);

        selector = Selector.open();

        final InetSocketAddress address = new InetSocketAddress(bindHost, port);
        serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);

        final ServerSocket serverSocket = serverChannel.socket();
        serverSocket.bind(address);
        this.port = serverSocket.getLocalPort();

        me = createURI(broadcastHost, this.port);

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        println("Broadcasting");
    }

    private URI normalize(final URI uri) {
        return createURI(uri.getHost(), uri.getPort());
    }

    private URI createURI(final String host, final int port) {
        return URI.create("conn://" + host.toLowerCase() + ":" + port);
    }

    public URI getMe() {
        return me;
    }

    public Set<URI> getRoots() {
        return roots;
    }

    public Event getRuns() {
        return runs;
    }

    public Event getHeartbeats() {
        return heartbeats;
    }

    public Event getReconnects() {
        return reconnects;
    }

    public Event getSessionsCreated() {
        return sessionsCreated;
    }

    public String getName() {
        return name;
    }

    public long getJoined() {
        return joined;
    }

    public List<URI> getSessions() {
        return new ArrayList<URI>(connections.keySet());
    }

    public List<URI> getConnectionsQueued() {
        synchronized (connect) {
            final ArrayList<URI> uris = new ArrayList<URI>(connect.size());
            for (final Host host : connect) {
                uris.add(host.getUri());
            }
            return uris;
        }

    }

    public long getReconnectDelay() {
        return reconnectDelay;
    }

    public int getPort() {
        return port;
    }

    /**
     * Attempt to connect back to the network if
     * - We aren't already connected
     * - We aren't already attempting to connect
     * - It has been a while since we last tried (reconnectDelay)
     */
    private void rejoin() {
        if (roots.size() <= 0)
            return;
        if (System.nanoTime() - joined <= reconnectDelay)
            return;

        for (final URI uri : roots) {
            final Host host = new Host(uri);
            synchronized (connect) {
                if (!connections.containsKey(uri) && !connect.contains(host)) {
                    log.info("Reconnect{uri=" + uri + "}");
                    connect.addLast(host);
                    host.resolveDns();
                    this.joined = System.nanoTime();
                }
            }
        }
    }

    public MultipointServer start() {
        if (running.compareAndSet(false, true)) {

            final String multipointServer = Join.join(".", "MultipointServer", name, port);
            log.info("MultipointServer Starting : Thread '" + multipointServer + "'");

            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    signal(started);
                    try {
                        _run();
                    } finally {
                        signal(stopped);
                    }
                }
            });
            thread.setName(multipointServer);
            thread.start();

            await(started, 10, TimeUnit.SECONDS);
        }
        return this;
    }

    private void signal(final Condition condition) {
        lock.lock();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    private void await(final Condition condition, final long time, final TimeUnit unit) {
        lock.lock();
        try {
            condition.await(time, unit);
        } catch (final InterruptedException e) {
            Thread.interrupted();
        } finally {
            lock.unlock();
        }
    }

    public void stop() {
        running.set(false);
        try {
            serverChannel.close();
        } catch (final IOException e) {
            throw new CloseException(e);
        } finally {
            await(stopped, 10, TimeUnit.SECONDS);
        }
    }

    public static class CloseException extends RuntimeException {

        public CloseException(final Throwable cause) {
            super(cause);
        }
    }

    public class Session {

        private static final int EOF = 3;

        private final SocketChannel channel;
        private final ByteBuffer read = ByteBuffer.allocate(1024);
        private final SelectionKey key;
        private final List<URI> listed = new ArrayList<URI>();
        private final long created = System.currentTimeMillis();

        private ByteBuffer write;

        @Managed
        private State state = State.OPEN;
        private URI uri;
        public boolean hangup;
        private final boolean client;

        public Session(final SocketChannel channel, final InetSocketAddress address, final URI uri) throws ClosedChannelException {
            this.channel = channel;
            this.client = uri != null;
            this.uri = uri != null ? uri : createURI(address.getHostName(), address.getPort());
            this.key = channel.register(selector, 0, this);
            sessionsCreated.record();
            log.info("Constructing " + this);
        }

        public Session ops(final int ops) {
            key.interestOps(ops);
            return this;
        }

        public long getCreated() {
            return created;
        }

        public void state(final int ops, final State state) {
            //            trace("transition "+state +"  "+ops);
            if (this.state != state) {
                if (log.isDebugEnabled()) {
                    log.debug(message(state.name()));
                }
            }
            this.state = state;
            if (ops > 0)
                key.interestOps(ops);
        }

        public void setURI(final URI uri) {
            this.uri = uri;
        }

        private void trace(final String str) {
            //            println(message(str));

            if (log.isDebugEnabled()) {
                log.debug(message(str));
                //                new Exception().fillInStackTrace().printStackTrace();
            }
        }

        private void info(final String str) {
            //            println(message(str));

            if (log.isInfoEnabled()) {
                log.info(message(str));
            }
        }

        private String message(final String str) {
            final StringBuilder sb = new StringBuilder();
            sb.append(name);
            sb.append(":");
            sb.append(port);
            sb.append(" ");
            if (key.isValid()) {
                if ((key.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ)
                    sb.append("<");
                if ((key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
                    sb.append(">");
                if ((key.interestOps() == 0))
                    sb.append("-");
            } else {
                sb.append(":");
            }
            sb.append(" ");
            sb.append(uri.getPort());
            sb.append(" ");
            sb.append(this.state);
            sb.append(" ");
            sb.append(str);
            return sb.toString();
        }

        public void write(final URI uri) throws IOException {
            write(Arrays.asList(uri));
        }

        public void write(final Collection<?> uris) throws IOException {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();

            for (final Object uri : uris) {
                final String s = uri.toString();
                final byte[] b = s.getBytes("UTF-8");
                baos.write(b);
                baos.write(EOF);
            }

            this.write = ByteBuffer.wrap(baos.toByteArray());
        }

        public boolean drain() throws IOException {
            this.channel.write(write);
            return write.remaining() == 0;
        }

        public String read() throws IOException {

            if (channel.read(read) == -1)
                throw new EOFException();

            final byte[] buf = read.array();

            final int end = endOfText(buf, 0, read.position());

            if (end < 0)
                return null;

            // Copy the string without the terminator char
            final String text = new String(buf, 0, end, "UTF-8");

            final int newPos = read.position() - end;
            System.arraycopy(buf, end + 1, buf, 0, newPos - 1);
            read.position(newPos - 1);

            return text;
        }

        private int endOfText(final byte[] data, final int offset, final int pos) {
            for (int i = offset; i < pos; i++) {
                if (data[i] == EOF)
                    return i;
            }
            return -1;
        }

        @Override
        public String toString() {
            return "Session{" +
                "uri=" + uri +
                ", created=" + created +
                ", state=" + state +
                ", owner=" + port +
                ", s=" + (client ? channel.socket().getPort() : channel.socket().getLocalPort()) +
                ", c=" + (!client ? channel.socket().getPort() : channel.socket().getLocalPort()) +
                ", " + (client ? "client" : "server") +
                '}';
        }

        private long last = 0;

        public void tick() throws IOException {
            if (state != State.HEARTBEAT)
                return;

            final long now = System.currentTimeMillis();
            final long delay = now - last;

            if (delay >= tracker.getHeartRate()) {
                last = now;
                heartbeat();
            }

        }

        private void heartbeat() throws IOException {
            heartbeats.record();

            final Set<String> strings = tracker.getRegisteredServices();
            //            for (String string : strings) {
            //                trace(string);
            //            }
            write(strings);
            state(SelectionKey.OP_READ | SelectionKey.OP_WRITE, State.HEARTBEAT);
        }
    }

    private static enum State {
        OPEN,
        GREETING,
        LISTING,
        HEARTBEAT,
        CLOSED
    }

    private final AtomicBoolean running = new AtomicBoolean();

    private void _run() {

        // The selectorTimeout ensures that even when there are no IO events,
        // this loop will "wake up" and execute at least as frequently as the
        // expected heartrate.
        //
        // We initiate WRITE events (the heartbeats we send) in this loop, so that
        // detail is critical.
        long selectorTimeout = tracker.getHeartRate();

        // For reliability purposes we will actually adjust the selectorTimeout
        // on each iteration of the loop, shrinking it down just a little to a
        // account for the execution time of the loop itself.

        int failed = 0;
        while (running.get()) {
            runs.record();

            final long start = System.nanoTime();

            try {
                selector.select(selectorTimeout);
                failed = 0;
            } catch (final IOException ex) {
                if (failed++ > 100) {
                    log.fatal("Too many Multipoint Failures.  Terminating service.", ex);
                    return;
                }
                log.error("Multipoint Failure.", ex);
                try {
                    Thread.sleep(100);
                } catch (final InterruptedException e) {
                    Thread.interrupted();
                }
            }

            final Set keys = selector.selectedKeys();
            final Iterator iterator = keys.iterator();
            while (iterator.hasNext()) {
                final SelectionKey key = (SelectionKey) iterator.next();
                iterator.remove();

                try {
                    if (key.isAcceptable())
                        doAccept(key);

                    if (key.isConnectable())
                        doConnect(key);

                    if (key.isReadable())
                        doRead(key);

                    if (key.isWritable())
                        doWrite(key);

                } catch (final CancelledKeyException ex) {
                    synchronized (connect) {
                        final Session session = (Session) key.attachment();
                        if (session.state != State.CLOSED) {
                            close(key);
                        }
                    }
                } catch (final ClosedChannelException ex) {
                    synchronized (connect) {
                        final Session session = (Session) key.attachment();
                        if (session.state != State.CLOSED) {
                            close(key);
                        }
                    }
                } catch (final IOException ex) {
                    final Session session = (Session) key.attachment();
                    session.trace(ex.getClass().getSimpleName() + ": " + ex.getMessage());
                    close(key);
                }

            }

            // This loop can generate WRITE keys (the heartbeats we send)
            for (final SelectionKey key : selector.keys()) {
                final Session session = (Session) key.attachment();

                try {
                    if (session != null && session.state == State.HEARTBEAT)
                        session.tick();
                } catch (final IOException ex) {
                    close(key);
                }
            }

            // Here is where we actually will expire missing services
            tracker.checkServices();

            // Fill 'connections' list if we are fully disconnected
            rejoin();

            // Connect to anyone in the 'connections' list
            initiateConnections();

            // Adjust selector timeout so we execute in even increments
            // This keeps the heartbeat and rejoin regular
            selectorTimeout = adjustedSelectorTimeout(start);
        }
        log.info("MultipointServer has terminated.");
    }

    private long adjustedSelectorTimeout(final long start) {
        final long end = System.nanoTime();
        final long elapsed = TimeUnit.NANOSECONDS.toMillis(end - start);
        final long heartRate = tracker.getHeartRate();

        return Math.max(1, heartRate - elapsed);
    }

    private void initiateConnections() {

        synchronized (connect) {
            final LinkedList<Host> unresolved = new LinkedList<Host>();

            while (connect.size() > 0) {

                final Host host = connect.removeFirst();

                log.debug("Initiate(uri=" + host.getUri() + ")");

                if (connections.containsKey(host.getUri()))
                    continue;

                if (!host.isDone()) {
                    unresolved.add(host);
                    log.debug("Unresolved(uri=" + host.getUri() + ")");
                    continue;
                }

                final InetSocketAddress address;
                try {
                    address = host.getSocketAddress();
                } catch (final ExecutionException e) {
                    final Throwable t = (e.getCause() != null) ? e.getCause() : e;
                    final String message = String.format("Failed Connect{uri=%s} %s{message=\"%s\"}", host.getUri(), t.getClass().getSimpleName(), t.getMessage());
                    log.warning(message);
                    continue;
                } catch (final TimeoutException e) {
                    unresolved.add(host);
                    log.debug("Unresolved(uri=" + host.getUri() + ")");
                    continue;
                }

                try {
                    final URI uri = host.getUri();
                    println("open " + uri);

                    // Create a non-blocking NIO channel
                    final SocketChannel socketChannel = SocketChannel.open();
                    socketChannel.configureBlocking(false);

                    socketChannel.connect(address);

                    final Session session = new Session(socketChannel, address, uri);
                    session.ops(SelectionKey.OP_CONNECT);
                    session.trace("client");
                    connections.put(session.uri, session);

                    // seen - needs to get maintained as "connected"
                    // TODO remove from seen
                } catch (final IOException e) {
                    throw new ServerRuntimeException(e);
                }
            }

            connect.addAll(unresolved);
        }
    }

    private void doWrite(final SelectionKey key) throws IOException {
        final Session session = (Session) key.attachment();

        switch (session.state) {
            case GREETING: { // write

                // Only CLIENTs write a GREETING message
                // As we are a client, the first thing we do
                // is READ the server's LIST

                if (session.drain()) {
                    session.state(SelectionKey.OP_READ, State.LISTING);
                }

            }
            break;

            case LISTING: { // write

                if (session.drain()) {

                    if (session.client) {
                        // CLIENTs list last, so at this point we've read
                        // the server's list and have written ours

                        //                        session.trace("DONE WRITING");

                        session.state(SelectionKey.OP_READ, State.HEARTBEAT);

                    } else {
                        // SERVERs always write their list first, so at this
                        // point we switch to LIST READ mode

                        session.state(SelectionKey.OP_READ, State.LISTING);

                    }
                }
            }
            break;

            case HEARTBEAT: { // write

                if (session.drain()) {

                    session.last = System.currentTimeMillis();

                    //                    session.trace("send");

                    session.state(SelectionKey.OP_READ, State.HEARTBEAT);

                }

            }
            break;
        }
    }

    private void doRead(final SelectionKey key) throws IOException {
        final Session session = (Session) key.attachment();

        switch (session.state) {
            case GREETING: { // read

                // This state is only reachable as a SERVER
                // The client connected and said hello by sending
                // its URI to let us know who they are

                // Once this is read, the client will expect us
                // to send our full list of URIs followed by the
                // "end" address.

                // So we switch to WRITE LISTING and they switch
                // to READ LISTING

                // Then we will switch to READ LISTING and they
                // will switch to WRITE LISTING

                final String message = session.read();

                if (message == null)
                    break; // need to read more

                session.setURI(URI.create(message));

                connected(session);

                session.trace("welcome");

                final ArrayList<URI> list = connections();

                // When they read themselves on the list
                // they'll know it's time to list their URIs

                list.remove(me); // yank
                list.add(END_LIST); // add to the end

                session.write(list);

                session.state(SelectionKey.OP_WRITE, State.LISTING);

                session.trace("STARTING");

            }
            break;

            case LISTING: { // read

                String message;

                while ((message = session.read()) != null) {

                    //                    session.trace(message);

                    final URI uri = URI.create(message);

                    if (END_LIST.equals(uri)) {

                        if (session.client) {

                            final ArrayList<URI> list = connections();

                            for (final URI reported : session.listed) {
                                list.remove(reported);
                            }

                            // When they read us on the list
                            // they'll know it's time to switch to heartbeat

                            list.remove(session.uri);
                            list.add(END_LIST);

                            session.write(list);

                            session.state(SelectionKey.OP_WRITE, State.LISTING);

                        } else {

                            // We are a SERVER in this relationship, so we will have already
                            // listed our known peers by this point.  From here we switch to
                            // heartbeating

                            // heartbeat time
                            if (session.hangup) {
                                session.state(0, State.CLOSED);
                                session.trace("hangup");
                                hangup(key);

                            } else {

                                session.trace("DONE READING");

                                session.state(SelectionKey.OP_READ, State.HEARTBEAT);

                            }

                        }

                        break;

                    } else {

                        session.listed.add(uri);

                        try {
                            connect(uri);
                        } catch (final Exception e) {
                            println("connect failed " + uri + " - " + e.getMessage());
                            e.printStackTrace();
                        }
                    }
                }

            }
            break;

            case HEARTBEAT: { // read

                String message;
                while ((message = session.read()) != null) {
                    //                    session.trace(message);
                    tracker.processData(message);
                }
            }
            break;
        }
    }

    private void doConnect(final SelectionKey key) throws IOException {
        // we are a client

        final Session session = (Session) key.attachment();
        session.channel.finishConnect();
        session.trace("connected");

        // when you are a client, first say high to everyone
        // before accepting data

        // once a server reads our address, it will send it's
        // full list of known addresses, followed by the "end"
        // address to signal that it is done.

        // we will then send our full list of known addresses,
        // followed by the "end" address to signal we are done.

        // Afterward the server will only pulls its heartbeat

        // separately, we will initiate connections to everyone
        // in the list who we have not yet seen.

        // WRITE our GREETING
        session.write(me);

        session.state(SelectionKey.OP_WRITE, State.GREETING);
    }

    private void doAccept(final SelectionKey key) throws IOException {
        // we are a server

        // when you are a server, we must first listen for the
        // address of the client before sending data.

        // once they send us their address, we will send our
        // full list of known addresses, followed by the "end"
        // address to signal that we are done.

        // Afterward we will only pulls our heartbeat

        final ServerSocketChannel server = (ServerSocketChannel) key.channel();
        final SocketChannel client = server.accept();
        final InetSocketAddress address = (InetSocketAddress) client.socket().getRemoteSocketAddress();

        client.configureBlocking(false);

        final Session session = new Session(client, address, null);
        session.trace("accept");
        session.state(SelectionKey.OP_READ, State.GREETING);
    }

    private ArrayList<URI> connections() {
        synchronized (connect) {
            final ArrayList<URI> list = new ArrayList<URI>(connections.keySet());
            for (final Host host : connect) {
                list.add(host.getUri());
            }
            return list;
        }
    }

    private void close(final SelectionKey key) {
        final Session session = (Session) key.attachment();

        if (session.hangup) {
            // This was a duplicate connection and was closed
            // do not remove this URI from the 'connections'
            // map as this particular session is not in that
            // map -- only the good session that will not be
            // closed is in there.
            log.info("Hungup " + session);
            session.trace("hungup");
        } else {
            log.info("Closed " + session);
            session.trace("closed");
            synchronized (connect) {
                connections.remove(session.uri);
            }
        }

        session.state(0, State.CLOSED);
        hangup(key);
    }

    private void hangup(final SelectionKey key) {
        key.cancel();
        try {
            key.channel().close();
        } catch (final IOException cex) {
            //Ignore
        }
    }

    public void connect(final MultipointServer s) throws Exception {
        connect(s.port);
    }

    public void connect(final int port) throws Exception {
        connect(URI.create("conn://localhost:" + port));
    }

    public void connect(URI uri) {
        uri = normalize(uri);
        if (me.equals(uri))
            return;

        final Host host = new Host(uri);

        synchronized (connect) {
            if (!connections.containsKey(uri) && !connect.contains(host)) {
                log.info("Queuing{uri=" + uri + "}");
                connect.addLast(host);
                host.resolveDns();
            }
        }
    }

    private void connected(Session session) {

        synchronized (connect) {
            Session duplicate = connections.get(session.uri);
            //            Session duplicate = null;

            if (duplicate != null) {

                session.trace("duplicate");

                // At this point we know we have two sockets open
                // to the client, this can happen in two different ways
                //
                //  1. one created by them and one created by us.
                //  2. two created by them and none created by us.
                //
                // For case #1, we will both have detected this situation
                // and know it needs fixing.  Only one of us can hangup
                //
                // For case #2, the client was likely disconnected and
                // is calling back.

                final Session[] sessions = {session, duplicate};

                if (!sessions[0].client && !sessions[1].client) {
                    // Case 1 -- Client is calling back
                    Arrays.sort(sessions, new Comparator<Session>() {
                        @Override
                        public int compare(final Session a, final Session b) {
                            return (int) (b.created - a.created);
                        }
                    });
                } else {
                    // Case 2 -- We called each other at the same time

                    Arrays.sort(sessions, new Comparator<Session>() {
                        // Goal: Keep the connection with the lowest port number
                        ///
                        // Low vs high is not very significant.  The critical
                        // part is that they both choose the same connection.
                        //
                        // Port numbers are seen on both sides.  There are two
                        // ports (one client and one server) for each connection.
                        //
                        // Both sides will agree to kill the connection with the
                        // lowest server port.  If those are the same, then both
                        // sides will agree to kill the connection with the lowest
                        // client port.  If those are the same, we still close a
                        // connection and hope for the best.  If both connections
                        // are killed we will try again next time another node
                        // lists the server and we notice we are not connected.
                        //
                        @Override
                        public int compare(final Session a, final Session b) {
                            final int serverRank = server(a) - server(b);
                            if (serverRank != 0)
                                return serverRank;
                            return client(a) - client(b);
                        }

                        private int server(final Session a) {
                            final Socket socket = a.channel.socket();
                            return a.client ? socket.getPort() : socket.getLocalPort();
                        }

                        private int client(final Session a) {
                            final Socket socket = a.channel.socket();
                            return !a.client ? socket.getPort() : socket.getLocalPort();
                        }
                    });
                }

                session = sessions[0];
                duplicate = sessions[1];

                session.trace(session + "@" + session.hashCode() + " KEEP");
                duplicate.trace(duplicate + "@" + duplicate.hashCode() + " KILL");

                duplicate.hangup = true;
            }

            if (session.state == State.GREETING) {
                session.info(session + "@" + session.hashCode() + " DISCOVERED");
            }
            connections.put(session.uri, session);
        }
    }

    private void println(final String s) {
        //        if (debug && s.matches(".*(Listening|DONE|KEEP|KILL)")) {
        //            System.out.format("%1$tH:%1$tM:%1$tS.%1$tL - %2$s\n", System.currentTimeMillis(), s);
        //        }
    }

    @Override
    public String toString() {
        return "MultipointServer{" +
            "name='" + name + '\'' +
            ", me=" + me +
            '}';
    }

    public static String randomColor() {
        final String[] colors = {
            "almond",
            "amber",
            "amethyst",
            "apple",
            "apricot",
            "aqua",
            "aquamarine",
            "ash",
            "azure",
            "banana",
            "beige",
            "black",
            "blue",
            "brick",
            "bronze",
            "brown",
            "burgundy",
            "carrot",
            "charcoal",
            "cherry",
            "chestnut",
            "chocolate",
            "chrome",
            "cinnamon",
            "citrine",
            "cobalt",
            "copper",
            "coral",
            "cornflower",
            "cotton",
            "cream",
            "crimson",
            "cyan",
            "ebony",
            "emerald",
            "forest",
            "fuchsia",
            "ginger",
            "gold",
            "goldenrod",
            "gray",
            "green",
            "grey",
            "indigo",
            "ivory",
            "jade",
            "jasmine",
            "khaki",
            "lava",
            "lavender",
            "lemon",
            "lilac",
            "lime",
            "macaroni",
            "magenta",
            "magnolia",
            "mahogany",
            "malachite",
            "mango",
            "maroon",
            "mauve",
            "mint",
            "moonstone",
            "navy",
            "ocean",
            "olive",
            "onyx",
            "orange",
            "orchid",
            "papaya",
            "peach",
            "pear",
            "pearl",
            "periwinkle",
            "pine",
            "pink",
            "pistachio",
            "platinum",
            "plum",
            "prune",
            "pumpkin",
            "purple",
            "quartz",
            "raspberry",
            "red",
            "rose",
            "rosewood",
            "ruby",
            "salmon",
            "sapphire",
            "scarlet",
            "sienna",
            "silver",
            "slate",
            "strawberry",
            "tan",
            "tangerine",
            "taupe",
            "teal",
            "titanium",
            "topaz",
            "turquoise",
            "umber",
            "vanilla",
            "violet",
            "watermelon",
            "white",
            "yellow"
        };

        final Random random = new Random();
        long l = random.nextLong();

        if (l < 0)
            l *= -1;

        final long index = l % colors.length;

        return colors[(int) index];
    }

    private final Executor dnsResolutionQueue = Executors.newFixedThreadPool(2, new DaemonThreadFactory("multipoint-server-"));

    private class Host {

        private final URI uri;
        private final FutureTask<InetAddress> address;

        private Host(final URI uri) {
            this.uri = uri;
            this.address = new FutureTask<InetAddress>(new Callable<InetAddress>() {
                @Override
                public InetAddress call() throws Exception {
                    return InetAddress.getByName(Host.this.uri.getHost());
                }
            });
        }

        public void resolveDns() {
            dnsResolutionQueue.execute(address);
        }

        public boolean isDone() {
            return address.isDone();
        }

        public InetSocketAddress getSocketAddress() throws ExecutionException, TimeoutException {
            try {
                final InetAddress inetAddress = address.get(0, TimeUnit.NANOSECONDS);
                return new InetSocketAddress(inetAddress, uri.getPort());
            } catch (final InterruptedException e) {
                Thread.interrupted();
                throw new TimeoutException();
            }
        }

        public URI getUri() {
            return uri;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;

            final Host host = (Host) o;

            return uri.equals(host.uri);
        }

        @Override
        public int hashCode() {
            return uri.hashCode();
        }
    }
}
TOP

Related Classes of org.apache.openejb.server.discovery.MultipointServer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.