001/** 002 * 003 * Copyright the original author or authors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack; 018 019import org.jivesoftware.smack.XMPPException.StreamErrorException; 020import org.jivesoftware.smack.packet.StreamError; 021 022import java.util.Random; 023import java.util.logging.Logger; 024/** 025 * Handles the automatic reconnection process. Every time a connection is dropped without 026 * the application explicitly closing it, the manager automatically tries to reconnect to 027 * the server.<p> 028 * 029 * The reconnection mechanism will try to reconnect periodically: 030 * <ol> 031 * <li>For the first minute it will attempt to connect once every ten seconds. 032 * <li>For the next five minutes it will attempt to connect once a minute. 033 * <li>If that fails it will indefinitely try to connect once every five minutes. 034 * </ol> 035 * 036 * @author Francisco Vives 037 */ 038public class ReconnectionManager extends AbstractConnectionListener { 039 private static final Logger LOGGER = Logger.getLogger(ReconnectionManager.class.getName()); 040 041 // Holds the connection to the server 042 private XMPPConnection connection; 043 private Thread reconnectionThread; 044 private int randomBase = new Random().nextInt(11) + 5; // between 5 and 15 seconds 045 046 // Holds the state of the reconnection 047 boolean done = false; 048 049 static { 050 // Create a new PrivacyListManager on every established connection. In the init() 051 // method of PrivacyListManager, we'll add a listener that will delete the 052 // instance when the connection is closed. 053 XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() { 054 public void connectionCreated(XMPPConnection connection) { 055 connection.addConnectionListener(new ReconnectionManager(connection)); 056 } 057 }); 058 } 059 060 private ReconnectionManager(XMPPConnection connection) { 061 this.connection = connection; 062 } 063 064 065 /** 066 * Returns true if the reconnection mechanism is enabled. 067 * 068 * @return true if automatic reconnections are allowed. 069 */ 070 private boolean isReconnectionAllowed() { 071 return !done && !connection.isConnected() 072 && connection.getConfiguration().isReconnectionAllowed(); 073 } 074 075 /** 076 * Starts a reconnection mechanism if it was configured to do that. 077 * The algorithm is been executed when the first connection error is detected. 078 * <p/> 079 * The reconnection mechanism will try to reconnect periodically in this way: 080 * <ol> 081 * <li>First it will try 6 times every 10 seconds. 082 * <li>Then it will try 10 times every 1 minute. 083 * <li>Finally it will try indefinitely every 5 minutes. 084 * </ol> 085 */ 086 synchronized protected void reconnect() { 087 if (this.isReconnectionAllowed()) { 088 // Since there is no thread running, creates a new one to attempt 089 // the reconnection. 090 // avoid to run duplicated reconnectionThread -- fd: 16/09/2010 091 if (reconnectionThread!=null && reconnectionThread.isAlive()) return; 092 093 reconnectionThread = new Thread() { 094 095 /** 096 * Holds the current number of reconnection attempts 097 */ 098 private int attempts = 0; 099 100 /** 101 * Returns the number of seconds until the next reconnection attempt. 102 * 103 * @return the number of seconds until the next reconnection attempt. 104 */ 105 private int timeDelay() { 106 attempts++; 107 if (attempts > 13) { 108 return randomBase*6*5; // between 2.5 and 7.5 minutes (~5 minutes) 109 } 110 if (attempts > 7) { 111 return randomBase*6; // between 30 and 90 seconds (~1 minutes) 112 } 113 return randomBase; // 10 seconds 114 } 115 116 /** 117 * The process will try the reconnection until the connection succeed or the user 118 * cancel it 119 */ 120 public void run() { 121 // The process will try to reconnect until the connection is established or 122 // the user cancel the reconnection process {@link XMPPConnection#disconnect()} 123 while (ReconnectionManager.this.isReconnectionAllowed()) { 124 // Find how much time we should wait until the next reconnection 125 int remainingSeconds = timeDelay(); 126 // Sleep until we're ready for the next reconnection attempt. Notify 127 // listeners once per second about how much time remains before the next 128 // reconnection attempt. 129 while (ReconnectionManager.this.isReconnectionAllowed() && 130 remainingSeconds > 0) 131 { 132 try { 133 Thread.sleep(1000); 134 remainingSeconds--; 135 ReconnectionManager.this 136 .notifyAttemptToReconnectIn(remainingSeconds); 137 } 138 catch (InterruptedException e1) { 139 LOGGER.warning("Sleeping thread interrupted"); 140 // Notify the reconnection has failed 141 ReconnectionManager.this.notifyReconnectionFailed(e1); 142 } 143 } 144 145 // Makes a reconnection attempt 146 try { 147 if (ReconnectionManager.this.isReconnectionAllowed()) { 148 connection.connect(); 149 } 150 } 151 catch (Exception e) { 152 // Fires the failed reconnection notification 153 ReconnectionManager.this.notifyReconnectionFailed(e); 154 } 155 } 156 } 157 }; 158 reconnectionThread.setName("Smack Reconnection Manager"); 159 reconnectionThread.setDaemon(true); 160 reconnectionThread.start(); 161 } 162 } 163 164 /** 165 * Fires listeners when a reconnection attempt has failed. 166 * 167 * @param exception the exception that occured. 168 */ 169 protected void notifyReconnectionFailed(Exception exception) { 170 if (isReconnectionAllowed()) { 171 for (ConnectionListener listener : connection.connectionListeners) { 172 listener.reconnectionFailed(exception); 173 } 174 } 175 } 176 177 /** 178 * Fires listeners when The XMPPConnection will retry a reconnection. Expressed in seconds. 179 * 180 * @param seconds the number of seconds that a reconnection will be attempted in. 181 */ 182 protected void notifyAttemptToReconnectIn(int seconds) { 183 if (isReconnectionAllowed()) { 184 for (ConnectionListener listener : connection.connectionListeners) { 185 listener.reconnectingIn(seconds); 186 } 187 } 188 } 189 190 @Override 191 public void connectionClosed() { 192 done = true; 193 } 194 195 @Override 196 public void connectionClosedOnError(Exception e) { 197 done = false; 198 if (e instanceof StreamErrorException) { 199 StreamErrorException xmppEx = (StreamErrorException) e; 200 StreamError error = xmppEx.getStreamError(); 201 String reason = error.getCode(); 202 203 if ("conflict".equals(reason)) { 204 return; 205 } 206 } 207 208 if (this.isReconnectionAllowed()) { 209 this.reconnect(); 210 } 211 } 212}