ReconnectionManager.java 7.56 KB
Newer Older
1 2 3 4 5 6
package org.jivesoftware.smack;

import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.StreamError.Type;

import java.util.Random;
7

8 9 10 11
/**
 * Handles the automatic reconnection process. Every time a connection is dropped without
 * the application explictly closing it, the manager automatically tries to reconnect to
 * the server.<p>
12
 * <p/>
13 14
 * The reconnection mechanism will try to reconnect periodically:
 * <ol>
15 16 17
 * <li>For the first minute it will attempt to connect once every ten seconds.
 * <li>For the next five minutes it will attempt to connect once a minute.
 * <li>If that fails it will indefinitely try to connect once every five minutes.
18 19 20 21 22 23 24 25 26 27
 * </ol>
 *
 * @author Francisco Vives
 */
public class ReconnectionManager implements ConnectionListener {

    // Holds the connection to the server
    private Connection connection;
    private Thread reconnectionThread;
    private int randomBase = new Random().nextInt(11) + 5; // between 5 and 15 seconds
28

29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    // Holds the state of the reconnection
    boolean done = false;

    static {
        // Create a new PrivacyListManager on every established connection. In the init()
        // method of PrivacyListManager, we'll add a listener that will delete the
        // instance when the connection is closed.
        Connection.addConnectionCreationListener(new ConnectionCreationListener() {
            public void connectionCreated(Connection connection) {
                connection.addConnectionListener(new ReconnectionManager(connection));
            }
        });
    }

    private ReconnectionManager(Connection connection) {
        this.connection = connection;
    }


    /**
     * Returns true if the reconnection mechanism is enabled.
     *
     * @return true if automatic reconnections are allowed.
     */
    private boolean isReconnectionAllowed() {
Alexander Ivanov's avatar
Alexander Ivanov committed
54 55
        return !done && !connection.isConnected()
                && connection.isReconnectionAllowed();
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
    }

    /**
     * Starts a reconnection mechanism if it was configured to do that.
     * The algorithm is been executed when the first connection error is detected.
     * <p/>
     * The reconnection mechanism will try to reconnect periodically in this way:
     * <ol>
     * <li>First it will try 6 times every 10 seconds.
     * <li>Then it will try 10 times every 1 minute.
     * <li>Finally it will try indefinitely every 5 minutes.
     * </ol>
     */
    synchronized protected void reconnect() {
        if (this.isReconnectionAllowed()) {
            // Since there is no thread running, creates a new one to attempt
            // the reconnection.
            // avoid to run duplicated reconnectionThread -- fd: 16/09/2010
74 75
            if (reconnectionThread != null && reconnectionThread.isAlive()) return;

76
            reconnectionThread = new Thread() {
77

78 79 80 81 82 83 84 85 86 87 88 89 90
                /**
                 * Holds the current number of reconnection attempts
                 */
                private int attempts = 0;

                /**
                 * Returns the number of seconds until the next reconnection attempt.
                 *
                 * @return the number of seconds until the next reconnection attempt.
                 */
                private int timeDelay() {
                    attempts++;
                    if (attempts > 13) {
91
                        return randomBase * 6 * 5;      // between 2.5 and 7.5 minutes (~5 minutes)
92 93
                    }
                    if (attempts > 7) {
94
                        return randomBase * 6;       // between 30 and 90 seconds (~1 minutes)
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
                    }
                    return randomBase;       // 10 seconds
                }

                /**
                 * The process will try the reconnection until the connection succeed or the user
                 * cancell it
                 */
                public void run() {
                    // The process will try to reconnect until the connection is established or
                    // the user cancel the reconnection process {@link Connection#disconnect()}
                    while (ReconnectionManager.this.isReconnectionAllowed()) {
                        // Find how much time we should wait until the next reconnection
                        int remainingSeconds = timeDelay();
                        // Sleep until we're ready for the next reconnection attempt. Notify
                        // listeners once per second about how much time remains before the next
                        // reconnection attempt.
                        while (ReconnectionManager.this.isReconnectionAllowed() &&
113
                                remainingSeconds > 0) {
114 115 116 117 118
                            try {
                                Thread.sleep(1000);
                                remainingSeconds--;
                                ReconnectionManager.this
                                        .notifyAttemptToReconnectIn(remainingSeconds);
119
                            } catch (InterruptedException e1) {
120 121 122 123 124 125 126 127 128 129 130
                                e1.printStackTrace();
                                // Notify the reconnection has failed
                                ReconnectionManager.this.notifyReconnectionFailed(e1);
                            }
                        }

                        // Makes a reconnection attempt
                        try {
                            if (ReconnectionManager.this.isReconnectionAllowed()) {
                                connection.connect();
                            }
131
                        } catch (XMPPException e) {
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
                            // Fires the failed reconnection notification
                            ReconnectionManager.this.notifyReconnectionFailed(e);
                        }
                    }
                }
            };
            reconnectionThread.setName("Smack Reconnection Manager");
            reconnectionThread.setDaemon(true);
            reconnectionThread.start();
        }
    }

    /**
     * Fires listeners when a reconnection attempt has failed.
     *
     * @param exception the exception that occured.
     */
    protected void notifyReconnectionFailed(Exception exception) {
Alexander Ivanov's avatar
Alexander Ivanov committed
150 151
        if (isReconnectionAllowed()) {
            for (ConnectionListener listener : connection.connectionListeners) {
152 153 154 155 156 157 158 159 160 161 162
                listener.reconnectionFailed(exception);
            }
        }
    }

    /**
     * Fires listeners when The Connection will retry a reconnection. Expressed in seconds.
     *
     * @param seconds the number of seconds that a reconnection will be attempted in.
     */
    protected void notifyAttemptToReconnectIn(int seconds) {
Alexander Ivanov's avatar
Alexander Ivanov committed
163 164
        if (isReconnectionAllowed()) {
            for (ConnectionListener listener : connection.connectionListeners) {
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
                listener.reconnectingIn(seconds);
            }
        }
    }

    public void connectionClosed() {
        done = true;
    }

    public void connectionClosedOnError(Exception e) {
        done = false;
        if (e instanceof XMPPException) {
            XMPPException xmppEx = (XMPPException) e;
            StreamError error = xmppEx.getStreamError();

            // Make sure the error is not null
            if (error != null && error.getType() == Type.conflict)
                return;
        }

        if (this.isReconnectionAllowed()) {
            this.reconnect();
        }
    }

    public void reconnectingIn(int seconds) {
        // ignore
    }

    public void reconnectionFailed(Exception e) {
        // ignore
    }

    /**
     * The connection has successfull gotten connected.
     */
    public void reconnectionSuccessful() {
        // ignore
    }

Alexander Ivanov's avatar
Alexander Ivanov committed
205
}