/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * 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 net.java.sip.communicator.impl.protocol.jabber; import java.util.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import org.jivesoftware.smack.*; /** * When provider registers, check whether we are connected * to the first server in the dns records, if its not - considers in * failover state and start a task that will periodically checks * to see if server has come back and when this happens, re-register * to it. If in the meanwhile, while in failover state the server we are * connected became primary (became with highest priority in srv records) * we consider ourselves out of failover state and stop all checks considering * as we are connected to primary one. * * @author Damian Minkov */ public class FailoverConnectionMonitor implements RegistrationStateChangeListener { /** * Property to enable/disable failover functionality. */ public static final String REVERSE_FAILOVER_ENABLED_PROP = "net.java.sip.communicator.impl.protocol.jabber.REVERSE_FAILOVER_ENABLED"; /** * Property to specify the interval between checks when in failover state. * Default is one minute. */ public static final String FAILOVER_CHECK_INTERVAL_PROP = "net.java.sip.communicator.impl.protocol.jabber.FAILOVER_CHECK_INTERVAL"; /** * The logger. */ private static final Logger logger = Logger.getLogger(FailoverConnectionMonitor.class); /** * The parent provider. */ private ProtocolProviderServiceJabberImpl parentProvider; /** * Table of all failover connection monitors for a jabber PP. */ private static Hashtable<ProtocolProviderServiceJabberImpl, FailoverConnectionMonitor> providerFilovers = new Hashtable<ProtocolProviderServiceJabberImpl, FailoverConnectionMonitor>(); /** * The timer that periodically will trigger a task to check primary server * if we are in failover state. */ private Timer checkTimer; /** * The task to be triggered to check primary server or whether we are now * connected to primary one. */ private CheckPrimaryTask task; /** * The interval between checks (default is 1 minute). */ private static int CHECK_FOR_PRIMARY_UP_INTERVAL = 60000; /** * The current address we are connecting to. */ private String currentAddress; /** * The current service name. */ private String serviceName; /** * Only we create failover monitor. * @param provider the provider which connection we will monitor. */ private FailoverConnectionMonitor( ProtocolProviderServiceJabberImpl provider) { this.parentProvider = provider; this.parentProvider.addRegistrationStateChangeListener(this); // checks for custom interval check configuration CHECK_FOR_PRIMARY_UP_INTERVAL = JabberActivator.getConfigurationService().getInt( FAILOVER_CHECK_INTERVAL_PROP, CHECK_FOR_PRIMARY_UP_INTERVAL); } /** * Returns instance of the monitor for provider, if missing create it. * @param provider the povider for the monitor we will return * @return the monitor for the provider. */ public static FailoverConnectionMonitor getInstance( ProtocolProviderServiceJabberImpl provider) { FailoverConnectionMonitor fov; synchronized(providerFilovers) { fov = providerFilovers.get(provider); if(fov == null) { fov = new FailoverConnectionMonitor(provider); providerFilovers.put(provider, fov); } } return fov; } /** * Sets current values that PP will use for connecting. * @param serviceName the service name. * @param currentAddress the current address used. */ void setCurrent(String serviceName, String currentAddress) { this.currentAddress = currentAddress; this.serviceName = serviceName; } /** * Whether we are connected to primary server for supplied records. * @param recs the srv records. * @return whether we are connected to primary server for supplied records. */ private boolean isConnectedToPrimary(SRVRecord[] recs) { String primaryAddress = getPrimaryServerRecord(recs).getTarget(); if(primaryAddress != null && primaryAddress.equals(currentAddress)) return true; else return false; } /** * Returns the primary server record, the one with highest priority. * @param recs the srv records to search. * @return the primary server record. */ private SRVRecord getPrimaryServerRecord(SRVRecord[] recs) { if(recs.length >= 1) { SRVRecord primary = recs[0]; for(SRVRecord srv : recs) { if(srv.getPriority() < primary.getPriority()) { primary = srv; } } return primary; } else return null; } /** * Get notified for server registration change events. * @param evt the event */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if(evt.getNewState() == RegistrationState.REGISTERED) { if(checkTimer == null) checkTimer = new Timer( FailoverConnectionMonitor.class.getName(), true); if(task == null) task = new CheckPrimaryTask(); checkTimer.schedule(task, CHECK_FOR_PRIMARY_UP_INTERVAL, CHECK_FOR_PRIMARY_UP_INTERVAL); } else if(evt.getNewState() == RegistrationState.UNREGISTERED || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED || evt.getNewState() == RegistrationState.CONNECTION_FAILED) { synchronized(providerFilovers) { providerFilovers.remove(parentProvider); parentProvider.removeRegistrationStateChangeListener(this); } if(checkTimer != null) { checkTimer.cancel(); checkTimer = null; } if(task != null) { task.cancel(); task = null; } } } /** * The task that will make the checks. */ private class CheckPrimaryTask extends TimerTask { @Override public void run() { try { // make srv lookup to check if dns changed and we are // moved to first place, go out of failover state SRVRecord[] currentRecords = NetworkUtils.getSRVRecords( "xmpp-client", "tcp", serviceName, false); if(isConnectedToPrimary(currentRecords)) return; // Clear DNS cache. NetworkUtils.clearDefaultDNSCache(); SRVRecord srv = getPrimaryServerRecord(currentRecords); ConnectionConfiguration confConn = new ConnectionConfiguration( srv.getTarget(), srv.getPort()); confConn.setReconnectionAllowed(false); XMPPConnection connection = new XMPPConnection(confConn); connection.connect(); connection.disconnect(); // connection to primary server is successful its back // lets reconnect ot it try { // first disconnect from slave and clean connection. parentProvider.unregister(); } catch(Throwable t) { logger.error("Error un-registering before " + "connecting to primary", t); } // no connect parentProvider.register( JabberActivator.getUIService().getDefaultSecurityAuthority( parentProvider)); } catch(Throwable t) { // primary server is not up } } } }