/** * $RCSfile$ * $Revision$ * $Date$ * * All rights reserved. Licensed 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.jivesoftware.smack; import org.jivesoftware.smack.packet.StreamError; import java.util.Random; /** * 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.

* * The reconnection mechanism will try to reconnect periodically: *

    *
  1. For the first minute it will attempt to connect once every ten seconds. *
  2. For the next five minutes it will attempt to connect once a minute. *
  3. If that fails it will indefinitely try to connect once every five minutes. *
* * @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 // 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() { return !done && !connection.isConnected() && connection.isReconnectionAllowed(); } /** * Starts a reconnection mechanism if it was configured to do that. * The algorithm is been executed when the first connection error is detected. *

* The reconnection mechanism will try to reconnect periodically in this way: *

    *
  1. First it will try 6 times every 10 seconds. *
  2. Then it will try 10 times every 1 minute. *
  3. Finally it will try indefinitely every 5 minutes. *
*/ 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 if (reconnectionThread!=null && reconnectionThread.isAlive()) return; reconnectionThread = new Thread() { /** * 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) { return randomBase*6*5; // between 2.5 and 7.5 minutes (~5 minutes) } if (attempts > 7) { return randomBase*6; // between 30 and 90 seconds (~1 minutes) } 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() && remainingSeconds > 0) { try { Thread.sleep(1000); remainingSeconds--; ReconnectionManager.this .notifyAttemptToReconnectIn(remainingSeconds); } catch (InterruptedException e1) { e1.printStackTrace(); // Notify the reconnection has failed ReconnectionManager.this.notifyReconnectionFailed(e1); } } // Makes a reconnection attempt try { if (ReconnectionManager.this.isReconnectionAllowed()) { connection.connect(); } } catch (XMPPException e) { // 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) { if (isReconnectionAllowed()) { for (ConnectionListener listener : connection.connectionListeners) { 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) { if (isReconnectionAllowed()) { for (ConnectionListener listener : connection.connectionListeners) { 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) { String reason = error.getCode(); if ("conflict".equals(reason)) { 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 } }