1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2024-06-13 07:04:49 +02:00
Smack/smack-core/src/main/java/org/jivesoftware/smack/ReconnectionManager.java
Florian Schmaus 9286a1decb Rework XMPP Error class design
Introduce AbstractError, change 'Conditions' to enums. Because of
AbstractError, it was necessary that PlainStreamElement and
TopLevelStreamElement becomes an interface. Thus the implementation of
TopLevelStreamElement.toString() had to be removed.

This adds

- policy-violation
- unexpected-request

to XMPPError.Condition, and removes the

- payment-required
- remote-server-error
- unexpected-condition
- request-timeout

Conditions

The file transfer code does now no longer throw XMPPErrorExceptions, but
SmackExceptions.

Fixes SMACK-608. Makes it possible to resolves SMACK-386.
2014-11-25 13:19:32 +01:00

290 lines
11 KiB
Java

/**
*
* Copyright the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.packet.StreamError;
import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Random;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Handles the automatic reconnection process. Every time a connection is dropped without
* the application explicitly 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 {
private static final Logger LOGGER = Logger.getLogger(ReconnectionManager.class.getName());
private static final Map<AbstractXMPPConnection, ReconnectionManager> INSTANCES = new WeakHashMap<AbstractXMPPConnection, ReconnectionManager>();
/**
* Get a instance of ReconnectionManager for the given connection.
*
* @param connection
* @return a ReconnectionManager for the connection.
*/
public static synchronized ReconnectionManager getInstanceFor(AbstractXMPPConnection connection) {
ReconnectionManager reconnectionManager = INSTANCES.get(connection);
if (reconnectionManager == null) {
reconnectionManager = new ReconnectionManager(connection);
INSTANCES.put(connection, reconnectionManager);
}
return reconnectionManager;
}
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
public void connectionCreated(XMPPConnection connection) {
if (connection instanceof AbstractXMPPConnection) {
ReconnectionManager.getInstanceFor((AbstractXMPPConnection) connection);
}
}
});
}
private static boolean enabledPerDefault = false;
/**
* Set if the automatic reconnection mechanism will be enabled per default for new XMPP connections. The default is
* 'false'.
*
* @param enabled
*/
public static void setEnabledPerDefault(boolean enabled) {
enabledPerDefault = enabled;
}
/**
* Get the current default reconnection mechanism setting for new XMPP connections.
*
* @return true if new connection will come with an enabled reconnection mechanism
*/
public static boolean getEnabledPerDefault() {
return enabledPerDefault;
}
// Holds the connection to the server
private final WeakReference<AbstractXMPPConnection> weakRefConnection;
private final int randomBase = new Random().nextInt(13) + 2; // between 2 and 15 seconds
private final Runnable reconnectionRunnable;
/**
* Flag that indicates if a reconnection should be attempted when abruptly disconnected
*/
private boolean automaticReconnectEnabled = false;
boolean done = false;
private Thread reconnectionThread;
private ReconnectionManager(AbstractXMPPConnection connection) {
weakRefConnection = new WeakReference<AbstractXMPPConnection>(connection);
reconnectionRunnable = new Thread() {
/**
* Holds the current number of reconnection attempts
*/
private int attempts = 0;
/**
* 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
}
/**
* The process will try the reconnection until the connection succeed or the user cancel it
*/
public void run() {
final AbstractXMPPConnection connection = weakRefConnection.get();
if (connection == null) {
return;
}
// The process will try to reconnect until the connection is established or
// the user cancel the reconnection process AbstractXMPPConnection.disconnect().
while (isReconnectionPossible(connection)) {
// 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 (isReconnectionPossible(connection) && remainingSeconds > 0) {
try {
Thread.sleep(1000);
remainingSeconds--;
for (ConnectionListener listener : connection.connectionListeners) {
listener.reconnectingIn(remainingSeconds);
}
}
catch (InterruptedException e) {
// We don't need to handle spurious interrupts, in the worst case, this will cause to
// reconnect a few seconds earlier, depending on how many (spurious) interrupts arrive while
// sleep() is called.
LOGGER.log(Level.FINE, "Supurious interrupt", e);
}
}
// Makes a reconnection attempt
try {
if (isReconnectionPossible(connection)) {
connection.connect();
}
}
catch (Exception e) {
// Fires the failed reconnection notification
for (ConnectionListener listener : connection.connectionListeners) {
listener.reconnectionFailed(e);
}
}
}
}
};
// If the reconnection mechanism is enable per default, enable it for this ReconnectionManager instance
if (getEnabledPerDefault()) {
enableAutomaticReconnection();
}
}
/**
* Enable the automatic reconnection mechanism. Does nothing if already enabled.
*/
public synchronized void enableAutomaticReconnection() {
if (automaticReconnectEnabled) {
return;
}
XMPPConnection connection = weakRefConnection.get();
if (connection == null) {
throw new IllegalStateException("Connection instance no longer available");
}
connection.addConnectionListener(connectionListener);
automaticReconnectEnabled = true;
}
/**
* Disable the automatic reconnection mechanism. Does nothing if already disabled.
*/
public synchronized void disableAutomaticReconnection() {
if (!automaticReconnectEnabled) {
return;
}
XMPPConnection connection = weakRefConnection.get();
if (connection == null) {
throw new IllegalStateException("Connection instance no longer available");
}
connection.removeConnectionListener(connectionListener);
automaticReconnectEnabled = false;
}
/**
* Returns if the automatic reconnection mechanism is enabled. You can disable the reconnection mechanism with
* {@link #disableAutomaticReconnection} and enable the mechanism with {@link #enableAutomaticReconnection()}.
*
* @return true, if the reconnection mechanism is enabled.
*/
public boolean isAutomaticReconnectEnabled() {
return automaticReconnectEnabled;
}
/**
* Returns true if the reconnection mechanism is enabled.
*
* @return true if automatic reconnection is allowed.
*/
private boolean isReconnectionPossible(XMPPConnection connection) {
return !done && !connection.isConnected()
&& isAutomaticReconnectEnabled();
}
/**
* Starts a reconnection mechanism if it was configured to do that.
* The algorithm is been executed when the first connection error is detected.
*/
private synchronized void reconnect() {
XMPPConnection connection = this.weakRefConnection.get();
if (connection == null) {
LOGGER.fine("Connection is null, will not reconnect");
return;
}
// 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(reconnectionRunnable);
reconnectionThread.setName("Smack Reconnection Manager (" + connection.getConnectionCounter() + ')');
reconnectionThread.setDaemon(true);
reconnectionThread.start();
}
private final ConnectionListener connectionListener = new AbstractConnectionListener() {
@Override
public void connectionClosed() {
done = true;
}
@Override
public void authenticated(XMPPConnection connection) {
done = false;
}
@Override
public void connectionClosedOnError(Exception e) {
done = false;
if (!isAutomaticReconnectEnabled()) {
return;
}
if (e instanceof StreamErrorException) {
StreamErrorException xmppEx = (StreamErrorException) e;
StreamError error = xmppEx.getStreamError();
if (StreamError.Condition.conflict == error.getCondition()) {
return;
}
}
reconnect();
}
};
}