blob: cc3e3af1952803d4542d5da7f3bfe034c0f53968 [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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}