package org.jivesoftware.smack;
import java.util.Random;
import org.jivesoftware.smack.packet.StreamError;
/**
* 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>
*
* The reconnection mechanism will try to reconnect periodically:
* <ol>
* <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.
* </ol>
*
* @author Francisco Vives
*/
public class ReconnectionManager implements ConnectionListener {
// Holds the connection to the server
private final Connection connection;
private Thread reconnectionThread;
private final 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() {
@Override
public void connectionCreated(Connection connection) {
connection
.addConnectionListener(new ReconnectionManager(
connection));
}
});
}
private ReconnectionManager(Connection connection) {
this.connection = connection;
}
@Override
public void connectionClosed() {
done = true;
}
@Override
public void connectionClosedOnError(Exception e) {
done = false;
if (e instanceof XMPPException) {
final XMPPException xmppEx = (XMPPException) e;
final StreamError error = xmppEx.getStreamError();
// Make sure the error is not null
if (error != null) {
final String reason = error.getCode();
if ("conflict".equals(reason)) {
return;
}
}
}
if (isReconnectionAllowed()) {
reconnect();
}
}
/**
* Returns true if the reconnection mechanism is enabled.
*
* @return true if automatic reconnections are allowed.
*/
private boolean isReconnectionAllowed() {
return !done && !connection.isConnected()
&& connection.isReconnectionAllowed();
}
/**
* 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 (final ConnectionListener listener : connection.connectionListeners) {
listener.reconnectingIn(seconds);
}
}
}
/**
* Fires listeners when a reconnection attempt has failed.
*
* @param exception
* the exception that occured.
*/
protected void notifyReconnectionFailed(Exception exception) {
if (isReconnectionAllowed()) {
for (final ConnectionListener listener : connection.connectionListeners) {
listener.reconnectionFailed(exception);
}
}
}
/**
* 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 (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;
/**
* The process will try the reconnection until the connection
* succeed or the user cancell it
*/
@Override
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 (final 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 (final XMPPException e) {
// Fires the failed reconnection notification
ReconnectionManager.this
.notifyReconnectionFailed(e);
}
}
}
/**
* 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
}
};
reconnectionThread.setName("Smack Reconnection Manager");
reconnectionThread.setDaemon(true);
reconnectionThread.start();
}
}
@Override
public void reconnectingIn(int seconds) {
// ignore
}
@Override
public void reconnectionFailed(Exception e) {
// ignore
}
/**
* The connection has successfull gotten connected.
*/
@Override
public void reconnectionSuccessful() {
// ignore
}
}