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

org.objectfabric.Remote Maven / Gradle / Ivy

There is a newer version: 0.9.1
Show newest version
/**
 * This file is part of ObjectFabric (http://objectfabric.org).
 *
 * ObjectFabric is licensed under the Apache License, Version 2.0, the terms
 * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html.
 * 
 * Copyright ObjectFabric Inc.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package org.objectfabric;

import org.objectfabric.Actor.Message;

/**
 * Network location.
 */
@SuppressWarnings("rawtypes")
public abstract class Remote extends Origin {

    public enum Status {
        DISCONNECTED, CONNECTING, WAITING_RETRY, SYNCHRONIZING, UP_TO_DATE
    }

    public static final String TCP = "tcp", SSL = "ssl", WS = "ws", WSS = "wss";

    public static final String WS_PATH = "/websocket";

    private static final long NO_RETRY = -1;

    private final Address _address;

    private final Run _run = new Run();

    private int _actives;

    private ConnectionAttempt _attempt;

    private String _info;

    private long _lastAttempt;

    private volatile Connection _connection;

    protected Remote(boolean cache, Address address) {
        super(cache);

        _address = address;

        _run.onStarted();
    }

    final Address address() {
        return _address;
    }

    final Connection connection() {
        return _connection;
    }

    // TODO listener
    public final Status status() {
        if (_connection != null)
            return InFlight.idle() ? Status.UP_TO_DATE : Status.SYNCHRONIZING;

        if (_actives == 0 || _lastAttempt == NO_RETRY)
            return Status.DISCONNECTED;

        if (_attempt != null)
            return Status.CONNECTING;

        return Status.WAITING_RETRY;
    }

    public final String statusInfo() {
        return _info;
    }

    //

    @Override
    View newView(URI uri) {
        return new ClientView(this);
    }

    final void onOpen(final URI uri) {
        _run.addAndRun(new Message() {

            @Override
            void run(Actor actor) {
                invariants();

                if (_connection != null)
                    _connection.postSubscribe(uri);

                if (_actives++ == 0 && _lastAttempt != NO_RETRY)
                    retry();

                invariants();
            }
        });
    }

    final void onClose(final URI uri) {
        _run.addAndRun(new Message() {

            @Override
            void run(Actor actor) {
                invariants();

                // TODO grace period
                if (--_actives == 0) {
                    if (_connection != null) {
                        _connection.disconnect();
                        _connection = null;
                    } else
                        closeAttempt();
                }

                invariants();
            }
        });
    }

    /*
     * 
     */

    interface ConnectionAttempt {

        void start();

        void cancel();
    }

    abstract ConnectionAttempt createAttempt();

    private final void closeAttempt() {
        if (_attempt != null) {
            _attempt.cancel();
            _attempt = null;
        }
    }

    //

    final void retry() {
        _run.addAndRun(new Message() {

            @Override
            void run(Actor actor) {
                invariants();
                ConnectionAttempt toStart = null;
                closeAttempt();

                if (_actives > 0) {
                    if (_connection == null) {
                        _attempt = toStart = createAttempt();
                        _lastAttempt = Platform.get().approxTimeMs();

                        if (!Debug.COMMUNICATIONS_DISABLE_TIMERS) {
                            Platform.get().schedule(new Runnable() {

                                @Override
                                public void run() {
                                    onError(null, Strings.TIMEOUT, true);
                                }
                            }, 4000);
                        }
                    }
                }

                invariants();

                if (toStart != null)
                    toStart.start();
            }
        });
    }

    final void onConnection(final Connection connection) {
        if (Debug.ENABLED)
            Debug.assertion(connection != null);

        _run.addAndRun(new Message() {

            @SuppressWarnings("null")
            @Override
            void run(Actor actor) {
                invariants();

                if (_attempt == null || _actives == 0)
                    connection.disconnect();
                else {
                    _attempt = null;
                    _connection = connection;

                    if (!isCache()) {
                        for (final URI uri : uris().values()) {
                            uri.runIf(new Runnable() {

                                @Override
                                public void run() {
                                    connection.postSubscribe(uri);
                                }
                            }, true);
                        }
                    }
                }

                invariants();
            }
        });
    }

    final void unsubscribe(final URI uri) {
        _run.addAndRun(new Message() {

            @Override
            void run(Actor actor) {
                if (_connection != null) {
                    uri.runIf(new Runnable() {

                        @Override
                        public void run() {
                            _connection.postUnsubscribe(uri);
                        }
                    }, false);
                }
            }
        });
    }

    final void onError(final Connection connection, final String info, final boolean canRetry) {
        if (Debug.ENABLED)
            Debug.assertion(info != null);

        _run.addAndRun(new Message() {

            @Override
            void run(Actor actor) {
                invariants();
                long now = 0, next = 0;
                boolean ignore = true;

                if (connection == null && _attempt != null) {
                    closeAttempt();
                    ignore = false;
                }

                if (connection != null && connection == _connection) {
                    connection.disconnect();
                    _connection = null;
                    ignore = false;
                }

                if (!ignore) {
                    if (!canRetry)
                        _lastAttempt = NO_RETRY;

                    if (_actives > 0 && _lastAttempt != NO_RETRY) {
                        now = Platform.get().approxTimeMs(); // try every rand(8s)
                        next = _lastAttempt + Platform.get().randomInt(8192);
                    }

                    _info = info;
                }

                if (next == 0) {
                    if (connection != null)
                        Log.write(this + ": " + info);
                } else {
                    if (!Debug.COMMUNICATIONS_DISABLE_TIMERS) {
                        int delay = next <= now ? 0 : (int) (next - now);

                        Platform.get().schedule(new Runnable() {

                            @Override
                            public void run() {
                                retry();
                            }
                        }, delay);
                    }
                }

                invariants();
            }
        });
    }

    @SuppressWarnings("serial")
    private final class Run extends Actor implements Runnable {

        @Override
        protected void enqueue() {
            Platform.get().execute(this);
        }

        @Override
        public void run() {
            if (Debug.ENABLED)
                ThreadAssert.resume(this, false);

            onRunStarting();
            runMessages();

            if (Debug.ENABLED)
                ThreadAssert.suspend(this);

            onRunEnded();
        }
    }

    //

    abstract Headers headers();

    @Override
    boolean start(WorkspaceSave save) {
        Connection connection = _connection;

        if (connection != null)
            return connection.start(save);

        return false;
    }

    @Override
    void start(WorkspaceLoad load) {
        Connection connection = _connection;

        if (connection != null)
            connection.start(load);
        else
            load.onResponseNull();
    }

    //

    @Override
    void sha1(SHA1Digest sha1) {
        sha1.update(_address.Scheme);
        sha1.update(_address.Host);
        sha1.update((byte) (_address.Port >> 8));
        sha1.update((byte) (_address.Port >> 0));
    }

    @Override
    public String toString() {
        String s = "";

        if (Debug.ENABLED)
            s += Platform.get().defaultToString(this) + "-";

        return s + _address.toString();
    }

    // Debug

    private final void invariants() {
        if (Debug.ENABLED) {
            if (_actives == 0)
                Debug.assertion(_attempt == null && _connection == null);
            else
                Debug.assertion(_attempt == null || _connection == null);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy