1/** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18package org.jivesoftware.smack; 19 20import org.jivesoftware.smack.packet.StreamError; 21import java.util.Random; 22/** 23 * Handles the automatic reconnection process. Every time a connection is dropped without 24 * the application explictly closing it, the manager automatically tries to reconnect to 25 * the server.<p> 26 * 27 * The reconnection mechanism will try to reconnect periodically: 28 * <ol> 29 * <li>For the first minute it will attempt to connect once every ten seconds. 30 * <li>For the next five minutes it will attempt to connect once a minute. 31 * <li>If that fails it will indefinitely try to connect once every five minutes. 32 * </ol> 33 * 34 * @author Francisco Vives 35 */ 36public class ReconnectionManager implements ConnectionListener { 37 38 // Holds the connection to the server 39 private Connection connection; 40 private Thread reconnectionThread; 41 private int randomBase = new Random().nextInt(11) + 5; // between 5 and 15 seconds 42 43 // Holds the state of the reconnection 44 boolean done = false; 45 46 static { 47 // Create a new PrivacyListManager on every established connection. In the init() 48 // method of PrivacyListManager, we'll add a listener that will delete the 49 // instance when the connection is closed. 50 Connection.addConnectionCreationListener(new ConnectionCreationListener() { 51 public void connectionCreated(Connection connection) { 52 connection.addConnectionListener(new ReconnectionManager(connection)); 53 } 54 }); 55 } 56 57 private ReconnectionManager(Connection connection) { 58 this.connection = connection; 59 } 60 61 62 /** 63 * Returns true if the reconnection mechanism is enabled. 64 * 65 * @return true if automatic reconnections are allowed. 66 */ 67 private boolean isReconnectionAllowed() { 68 return !done && !connection.isConnected() 69 && connection.isReconnectionAllowed(); 70 } 71 72 /** 73 * Starts a reconnection mechanism if it was configured to do that. 74 * The algorithm is been executed when the first connection error is detected. 75 * <p/> 76 * The reconnection mechanism will try to reconnect periodically in this way: 77 * <ol> 78 * <li>First it will try 6 times every 10 seconds. 79 * <li>Then it will try 10 times every 1 minute. 80 * <li>Finally it will try indefinitely every 5 minutes. 81 * </ol> 82 */ 83 synchronized protected void reconnect() { 84 if (this.isReconnectionAllowed()) { 85 // Since there is no thread running, creates a new one to attempt 86 // the reconnection. 87 // avoid to run duplicated reconnectionThread -- fd: 16/09/2010 88 if (reconnectionThread!=null && reconnectionThread.isAlive()) return; 89 90 reconnectionThread = new Thread() { 91 92 /** 93 * Holds the current number of reconnection attempts 94 */ 95 private int attempts = 0; 96 97 /** 98 * Returns the number of seconds until the next reconnection attempt. 99 * 100 * @return the number of seconds until the next reconnection attempt. 101 */ 102 private int timeDelay() { 103 attempts++; 104 if (attempts > 13) { 105 return randomBase*6*5; // between 2.5 and 7.5 minutes (~5 minutes) 106 } 107 if (attempts > 7) { 108 return randomBase*6; // between 30 and 90 seconds (~1 minutes) 109 } 110 return randomBase; // 10 seconds 111 } 112 113 /** 114 * The process will try the reconnection until the connection succeed or the user 115 * cancell it 116 */ 117 public void run() { 118 // The process will try to reconnect until the connection is established or 119 // the user cancel the reconnection process {@link Connection#disconnect()} 120 while (ReconnectionManager.this.isReconnectionAllowed()) { 121 // Find how much time we should wait until the next reconnection 122 int remainingSeconds = timeDelay(); 123 // Sleep until we're ready for the next reconnection attempt. Notify 124 // listeners once per second about how much time remains before the next 125 // reconnection attempt. 126 while (ReconnectionManager.this.isReconnectionAllowed() && 127 remainingSeconds > 0) 128 { 129 try { 130 Thread.sleep(1000); 131 remainingSeconds--; 132 ReconnectionManager.this 133 .notifyAttemptToReconnectIn(remainingSeconds); 134 } 135 catch (InterruptedException e1) { 136 e1.printStackTrace(); 137 // Notify the reconnection has failed 138 ReconnectionManager.this.notifyReconnectionFailed(e1); 139 } 140 } 141 142 // Makes a reconnection attempt 143 try { 144 if (ReconnectionManager.this.isReconnectionAllowed()) { 145 connection.connect(); 146 } 147 } 148 catch (XMPPException e) { 149 // Fires the failed reconnection notification 150 ReconnectionManager.this.notifyReconnectionFailed(e); 151 } 152 } 153 } 154 }; 155 reconnectionThread.setName("Smack Reconnection Manager"); 156 reconnectionThread.setDaemon(true); 157 reconnectionThread.start(); 158 } 159 } 160 161 /** 162 * Fires listeners when a reconnection attempt has failed. 163 * 164 * @param exception the exception that occured. 165 */ 166 protected void notifyReconnectionFailed(Exception exception) { 167 if (isReconnectionAllowed()) { 168 for (ConnectionListener listener : connection.connectionListeners) { 169 listener.reconnectionFailed(exception); 170 } 171 } 172 } 173 174 /** 175 * Fires listeners when The Connection will retry a reconnection. Expressed in seconds. 176 * 177 * @param seconds the number of seconds that a reconnection will be attempted in. 178 */ 179 protected void notifyAttemptToReconnectIn(int seconds) { 180 if (isReconnectionAllowed()) { 181 for (ConnectionListener listener : connection.connectionListeners) { 182 listener.reconnectingIn(seconds); 183 } 184 } 185 } 186 187 public void connectionClosed() { 188 done = true; 189 } 190 191 public void connectionClosedOnError(Exception e) { 192 done = false; 193 if (e instanceof XMPPException) { 194 XMPPException xmppEx = (XMPPException) e; 195 StreamError error = xmppEx.getStreamError(); 196 197 // Make sure the error is not null 198 if (error != null) { 199 String reason = error.getCode(); 200 201 if ("conflict".equals(reason)) { 202 return; 203 } 204 } 205 } 206 207 if (this.isReconnectionAllowed()) { 208 this.reconnect(); 209 } 210 } 211 212 public void reconnectingIn(int seconds) { 213 // ignore 214 } 215 216 public void reconnectionFailed(Exception e) { 217 // ignore 218 } 219 220 /** 221 * The connection has successfull gotten connected. 222 */ 223 public void reconnectionSuccessful() { 224 // ignore 225 } 226 227}