diff --git a/source/org/jivesoftware/smack/ReconnectionManager.java b/source/org/jivesoftware/smack/ReconnectionManager.java
new file mode 100644
index 000000000..258989113
--- /dev/null
+++ b/source/org/jivesoftware/smack/ReconnectionManager.java
@@ -0,0 +1,234 @@
+package org.jivesoftware.smack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles the reconnection process. Every time a connection is broken, it automatically
+ * tries to reconnect it. The reconnection is been executed when the first connection
+ * error is detected.
+ *
+ * The reconnection mechanism will try to reconnect periodically in this way:
+ *
+ * - First it will try 6 times every 10 seconds.
+ *
- Then it will try 10 times every 1 minute.
+ *
- Finally it will try indefinitely every 5 minutes.
+ *
+ *
+ * @author Francisco Vives
+ */
+public class ReconnectionManager implements ConnectionListener {
+
+ // Holds the time elapsed between each reconnection attempt
+ private int secondBetweenReconnection = 5 * 60; // 5 minutes
+
+ // Holds the thread that produces a periodical reconnection.
+ private Thread reconnectionThread;
+
+ // Holds the connection to the server
+ private XMPPConnection connection;
+
+ // 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.
+ XMPPConnection.addConnectionListener(new ConnectionEstablishedListener() {
+ public void connectionEstablished(XMPPConnection connection) {
+ connection.addConnectionListener(new ReconnectionManager(connection));
+ }
+ });
+ }
+
+ private ReconnectionManager(XMPPConnection connection) {
+ this.connection = connection;
+ }
+
+
+ /**
+ * Returns if the reconnection mechanism is allowed to use.
+ */
+ private boolean isReconnectionAllowed() {
+ return !done && !connection.isConnected()
+ && connection.getConfiguration().isReconnectionAllowed()
+ && connection.packetReader != null;
+ }
+
+ /**
+ * Returns the time elapsed between each reconnection attempt.
+ * By default it will try to reconnect every 5 minutes.
+ * It is used when the client has lost the server connection and the XMPPConnection
+ * automatically tries to reconnect.
+ *
+ * @return Returns the number of seconds between reconnection.
+ */
+ private int getSecondBetweenReconnection() {
+ return secondBetweenReconnection;
+ }
+
+ /**
+ * Sets the time elapsed between each reconnection attempt.
+ * It is used when the client has lost the server connection and the XMPPConnection
+ * automatically tries to reconnect.
+ *
+ * @param secondBetweenReconnection The number of seconds between reconnection.
+ */
+ protected void setSecondBetweenReconnection(
+ int secondBetweenReconnection) {
+ this.secondBetweenReconnection = secondBetweenReconnection;
+ }
+
+ /**
+ * 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:
+ *
+ * - First it will try 6 times every 10 seconds.
+ *
- Then it will try 10 times every 1 minute.
+ *
- Finally it will try indefinitely every 5 minutes.
+ *
+ */
+ protected void reconnect() {
+ if (this.isReconnectionAllowed() && reconnectionThread == null) {
+ // Since there is no thread running, creates a new one to attempt
+ // the reconnection.
+ reconnectionThread = new Thread() {
+ /**
+ * Holds the number of reconnection attempts
+ */
+ private int attempts = 0;
+ private int firstReconnectionPeriod = 7; // 6 attempts
+ private int secondReconnectionPeriod = 10 + firstReconnectionPeriod; // 16 attempts
+ private int firstReconnectionTime = 10; // 10 seconds
+ private int secondReconnectionTime = 1 * 60; // 1 minute
+ private int lastReconnectionTime =
+ getSecondBetweenReconnection(); // user defined in seconds
+ private int remainingSeconds = 0; // The seconds remaining to a reconnection
+ private int notificationPeriod = 1000; // 1 second
+
+ /**
+ * Answer the time it should wait until the next reconnection
+ * attempt
+ */
+ private int timeDelay() {
+ if (attempts > secondReconnectionPeriod) {
+ return lastReconnectionTime; // 5 minutes
+ }
+ if (attempts > firstReconnectionPeriod) {
+ return secondReconnectionTime; // 1 minute
+ }
+ return firstReconnectionTime; // 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 XMPPConnection#disconnect()}
+ while (ReconnectionManager.this.isReconnectionAllowed()) {
+ // Indicate how much time will wait until next reconnection
+ remainingSeconds = timeDelay();
+ // Notifies the remaining time until the next reconnection attempt
+ // every 1 second.
+ while (ReconnectionManager.this.isReconnectionAllowed() &&
+ remainingSeconds > 0) {
+ try {
+ Thread.sleep(notificationPeriod);
+ remainingSeconds = remainingSeconds - 1;
+ ReconnectionManager.this
+ .notifyAttemptToReconnectIn(remainingSeconds);
+ }
+ catch (InterruptedException e1) {
+ e1.printStackTrace();
+ // Notify the reconnection has failed
+ ReconnectionManager.this.notifyReconnectionFailed(e1);
+ }
+ }
+ // Waiting time have finished
+
+ // Makes the reconnection attempt
+ try {
+ if (ReconnectionManager.this.isReconnectionAllowed()) {
+ // Attempts to reconnect.
+ 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.
+ */
+ protected void notifyReconnectionFailed(Exception exception) {
+ List listenersCopy;
+ if (isReconnectionAllowed()) {
+ synchronized (connection.packetReader.connectionListeners) {
+ // Makes a copy since it's possible that a listener will be removed from the list
+ listenersCopy = new ArrayList(
+ connection.packetReader.connectionListeners);
+ for (ConnectionListener listener : listenersCopy) {
+ listener.reconnectionFailed(exception);
+ }
+ }
+ }
+ }
+
+ /**
+ * Fires listeners when The XMPPConnection will retry a reconnection. Expressed in seconds.
+ */
+ protected void notifyAttemptToReconnectIn(int seconds) {
+ List listenersCopy;
+ if (isReconnectionAllowed()) {
+ synchronized (connection.packetReader.connectionListeners) {
+ // Makes a copy since it's possible that a listener will be removed from the list
+ listenersCopy = new ArrayList(
+ connection.packetReader.connectionListeners);
+ for (ConnectionListener listener : listenersCopy) {
+ listener.reconnectingIn(seconds);
+ }
+ }
+ }
+ }
+
+ public void connectionClosed() {
+ done = true;
+ }
+
+ public void connectionClosedOnError(Exception e) {
+ done = false;
+ 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 reconectionSuccessful() {
+ // ignore
+ }
+
+}
diff --git a/test/org/jivesoftware/smack/ReconnectionTest.java b/test/org/jivesoftware/smack/ReconnectionTest.java
new file mode 100644
index 000000000..b2341cfe8
--- /dev/null
+++ b/test/org/jivesoftware/smack/ReconnectionTest.java
@@ -0,0 +1,188 @@
+package org.jivesoftware.smack;
+
+import org.jivesoftware.smack.test.SmackTestCase;
+
+/**
+ * Tests the connection and reconnection mechanism
+ *
+ * @author Francisco Vives
+ */
+
+public class ReconnectionTest extends SmackTestCase {
+
+ public ReconnectionTest(String arg0) {
+ super(arg0);
+ }
+
+ /**
+ * Tests an automatic reconnection.
+ * Simulates a connection error and then waits until gets reconnected.
+ */
+
+ public void testAutomaticReconnection() throws Exception {
+ XMPPConnection connection = getConnection(0);
+ XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
+ connection.addConnectionListener(listener);
+
+ // Simulates an error in the connection
+ connection.packetReader.notifyConnectionError(new Exception("Simulated Error"));
+ Thread.sleep(12000);
+ // After 10 seconds, the reconnection manager must reestablishes the connection
+ assertEquals("The ConnectionListener.connectionStablished() notification was not fired",
+ true, listener.reconnected);
+ assertEquals("The ConnectionListener.reconnectingIn() notification was not fired", 10,
+ listener.attemptsNotifications);
+ assertEquals("The ReconnectionManager algorithm has reconnected without waiting until 0", 0,
+ listener.remainingSeconds);
+
+ // Executes some server interaction testing the connection
+ executeSomeServerInteraction(connection);
+ }
+
+ /**
+ * Tests a manual reconnection.
+ * Simulates a connection error, disables the reconnection mechanism and then reconnects.
+ */
+ public void testManualReconnectionWithCancelation() throws Exception {
+ XMPPConnection connection = getConnection(0);
+ XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
+ connection.addConnectionListener(listener);
+
+ // Produces a connection error
+ connection.packetReader.notifyConnectionError(new Exception("Simulated Error"));
+ assertEquals(
+ "An error occurs but the ConnectionListener.connectionClosedOnError(e) was not notified",
+ true, listener.connectionClosedOnError);
+ Thread.sleep(1000);
+ // Cancels the automatic reconnection
+ connection.getConfiguration().setReconnectionAllowed(false);
+ // Waits for a reconnection that must not happened.
+ Thread.sleep(10500);
+ // Cancels the automatic reconnection
+ assertEquals("The connection was stablished but it was not allowed to", false,
+ listener.reconnected);
+
+ // Makes a manual reconnection from an error terminated connection without reconnection
+ connection.connect();
+
+ // Executes some server interaction testing the connection
+ executeSomeServerInteraction(connection);
+ }
+
+ /**
+ * Tests a manual reconnection after a login.
+ * Closes the connection and then reconnects.
+ */
+ public void testCloseAndManualReconnection() throws Exception {
+ XMPPConnection connection = getConnection(0);
+ String username = connection.getConfiguration().getUsername();
+ String password = connection.getConfiguration().getPassword();
+ XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
+ connection.addConnectionListener(listener);
+
+ // Produces a normal disconnection
+ connection.disconnect();
+ assertEquals("ConnectionListener.connectionClosed() was not notified",
+ true, listener.connectionClosed);
+ // Waits 10 seconds waiting for a reconnection that must not happened.
+ Thread.sleep(12200);
+ assertEquals("The connection was stablished but it was not allowed to", false,
+ listener.reconnected);
+
+ // Makes a manual reconnection
+ connection.connect();
+ connection.login(username, password);
+
+ // Executes some server interaction testing the connection
+ executeSomeServerInteraction(connection);
+ }
+
+ /**
+ * Tests a reconnection in a anonymously logged connection.
+ * Closes the connection and then reconnects.
+ */
+ public void testAnonymousReconnection() throws Exception {
+ XMPPConnection connection = createConnection();
+ connection.connect();
+ XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
+ connection.addConnectionListener(listener);
+
+ // Makes the anounymous login
+ connection.loginAnonymously();
+
+ // Produces a normal disconnection
+ connection.disconnect();
+ assertEquals("ConnectionListener.connectionClosed() was not notified",
+ true, listener.connectionClosed);
+ // Makes a manual reconnection
+ connection.connect();
+ connection.loginAnonymously();
+ assertEquals("Failed the manual connection", true, connection.isAnonymous());
+ }
+
+ private XMPPConnection createConnection() throws Exception {
+ XMPPConnection connection;
+ // Create the configuration
+ ConnectionConfiguration config = new ConnectionConfiguration(getHost(), getPort());
+ config.setTLSEnabled(true);
+ config.setCompressionEnabled(Boolean.getBoolean("test.compressionEnabled"));
+ config.setSASLAuthenticationEnabled(true);
+ connection = new XMPPConnection(config, getSocketFactory());
+
+ return connection;
+ }
+
+ /**
+ * Execute some server interaction in order to test that the regenerated connection works fine.
+ */
+ private void executeSomeServerInteraction(XMPPConnection connection) throws XMPPException {
+ PrivacyListManager privacyManager = PrivacyListManager.getInstanceFor(connection);
+ privacyManager.getPrivacyLists();
+ }
+
+ protected int getMaxConnections() {
+ return 1;
+ }
+
+ private class XMPPConnectionTestListener implements ConnectionListener {
+
+ // Variables to support listener notifications verification
+ private boolean connectionClosed = false;
+ private boolean connectionClosedOnError = false;
+ private boolean reconnected = false;
+ private boolean reconnectionFailed = false;
+ private int remainingSeconds = 0;
+ private int attemptsNotifications = 0;
+ private boolean reconnectionCanceled = false;
+
+ /**
+ * Methods to test the listener.
+ */
+ public void connectionClosed() {
+ connectionClosed = true;
+ }
+
+ public void connectionClosedOnError(Exception e) {
+ connectionClosedOnError = true;
+ }
+
+ public void reconnectionCanceled() {
+ reconnectionCanceled = true;
+ }
+
+ public void reconnectingIn(int seconds) {
+ attemptsNotifications = attemptsNotifications + 1;
+ remainingSeconds = seconds;
+
+ }
+
+ public void reconectionSuccessful() {
+ reconnected = true;
+ }
+
+ public void reconnectionFailed(Exception error) {
+ reconnectionFailed = true;
+ }
+ }
+
+}
\ No newline at end of file