XmppConnectionManager.java

  1. /**
  2.  *
  3.  * Copyright 2018-2023 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.igniterealtime.smack.inttest;

  18. import java.io.IOException;
  19. import java.lang.reflect.InvocationTargetException;
  20. import java.security.KeyManagementException;
  21. import java.security.NoSuchAlgorithmException;
  22. import java.util.ArrayList;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.HashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.logging.Level;
  31. import java.util.logging.Logger;

  32. import org.jivesoftware.smack.AbstractXMPPConnection;
  33. import org.jivesoftware.smack.ConnectionConfiguration;
  34. import org.jivesoftware.smack.SmackException;
  35. import org.jivesoftware.smack.SmackException.NoResponseException;
  36. import org.jivesoftware.smack.SmackException.NotConnectedException;
  37. import org.jivesoftware.smack.XMPPException;
  38. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  39. import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
  40. import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration;
  41. import org.jivesoftware.smack.compression.CompressionModuleDescriptor;
  42. import org.jivesoftware.smack.tcp.XMPPTCPConnection;
  43. import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
  44. import org.jivesoftware.smack.util.MultiMap;
  45. import org.jivesoftware.smack.util.StringUtils;
  46. import org.jivesoftware.smack.websocket.java11.Java11WebSocketFactory;
  47. import org.jivesoftware.smack.websocket.okhttp.OkHttpWebSocketFactory;
  48. import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
  49. import org.jivesoftware.smackx.iqregister.AccountManager;

  50. import org.igniterealtime.smack.inttest.Configuration.AccountRegistration;
  51. import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework.AccountNum;
  52. import org.jxmpp.jid.EntityBareJid;
  53. import org.jxmpp.jid.impl.JidCreate;
  54. import org.jxmpp.jid.parts.Localpart;
  55. import org.jxmpp.stringprep.XmppStringprepException;

  56. public class XmppConnectionManager {

  57.     private static final Logger LOGGER = Logger.getLogger(XmppConnectionManager.class.getName());

  58.     private static final XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> DEFAULT_CONNECTION_DESCRIPTOR;

  59.     private static final Map<String, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> NICKNAME_CONNECTION_DESCRIPTORS = new HashMap<>();

  60.     private static final MultiMap<
  61.         Class<? extends AbstractXMPPConnection>,
  62.         XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>
  63.     > CONNECTION_DESCRIPTORS = new MultiMap<>();

  64.     static {
  65.         try {
  66.             DEFAULT_CONNECTION_DESCRIPTOR = XmppConnectionDescriptor.buildWith(XMPPTCPConnection.class, XMPPTCPConnectionConfiguration.class)
  67.                             .withNickname("tcp")
  68.                             .build();
  69.             addConnectionDescriptor(DEFAULT_CONNECTION_DESCRIPTOR);

  70.             addConnectionDescriptor(
  71.                             XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class)
  72.                             .withNickname("modular")
  73.                             .build()
  74.             );
  75.             addConnectionDescriptor(
  76.                             XmppConnectionDescriptor.buildWith(ModularXmppClientToServerConnection.class, ModularXmppClientToServerConnectionConfiguration.class, ModularXmppClientToServerConnectionConfiguration.Builder.class)
  77.                             .withNickname("modular-nocompress")
  78.                             .applyExtraConfiguration(cb -> cb.removeModule(CompressionModuleDescriptor.class))
  79.                             .build()
  80.             );
  81.             addConnectionDescriptor(
  82.                             XmppConnectionDescriptor.buildWebsocketDescriptor("modular-websocket-okhttp", OkHttpWebSocketFactory.class)
  83.             );
  84.             addConnectionDescriptor(
  85.                             XmppConnectionDescriptor.buildWebsocketDescriptor("modular-websocket-java11", Java11WebSocketFactory.class)
  86.             );
  87.         } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
  88.                         | IllegalArgumentException | InvocationTargetException e) {
  89.             throw new AssertionError(e);
  90.         }
  91.     }

  92.     public static boolean addConnectionDescriptor(
  93.                     XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor) {
  94.         String nickname = connectionDescriptor.getNickname();
  95.         Class<? extends AbstractXMPPConnection> connectionClass = connectionDescriptor.getConnectionClass();

  96.         boolean alreadyExisted;
  97.         synchronized (CONNECTION_DESCRIPTORS) {
  98.             alreadyExisted = removeConnectionDescriptor(nickname);

  99.             CONNECTION_DESCRIPTORS.put(connectionClass, connectionDescriptor);
  100.             NICKNAME_CONNECTION_DESCRIPTORS.put(connectionDescriptor.getNickname(), connectionDescriptor);
  101.         }
  102.         return alreadyExisted;
  103.     }

  104.     public static boolean removeConnectionDescriptor(String nickname) {
  105.         synchronized (CONNECTION_DESCRIPTORS) {
  106.             XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor = NICKNAME_CONNECTION_DESCRIPTORS.remove(nickname);
  107.             if (connectionDescriptor == null) {
  108.                 return false;
  109.             }

  110.             boolean removed = CONNECTION_DESCRIPTORS.removeOne(connectionDescriptor.getConnectionClass(), connectionDescriptor);
  111.             assert removed;
  112.         }

  113.         return true;
  114.     }

  115.     private final XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> defaultConnectionDescriptor;

  116.     private final Map<String, XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> nicknameConnectionDescriptors;

  117.     private final MultiMap<
  118.         Class<? extends AbstractXMPPConnection>,
  119.         XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>
  120.     > connectionDescriptors;

  121.     private final SmackIntegrationTestFramework sinttestFramework;
  122.     private final Configuration sinttestConfiguration;
  123.     private final String testRunId;

  124.     private final AbstractXMPPConnection accountRegistrationConnection;
  125.     private final ServiceAdministrationManager adminManager;
  126.     private final AccountManager accountManager;

  127.     /**
  128.      * One of the three main connections. The type of the main connections is the default connection type.
  129.      */
  130.     AbstractXMPPConnection conOne, conTwo, conThree;

  131.     /**
  132.      * A pool of authenticated and free to use connections.
  133.      */
  134.     private final MultiMap<XmppConnectionDescriptor<?, ?, ?>, AbstractXMPPConnection> connectionPool = new MultiMap<>();

  135.     /**
  136.      * A list of all ever created connections.
  137.      */
  138.     private final Map<AbstractXMPPConnection, XmppConnectionDescriptor<?, ?, ?>> connections = new ConcurrentHashMap<>();

  139.     XmppConnectionManager(SmackIntegrationTestFramework sinttestFramework)
  140.             throws SmackException, IOException, XMPPException, InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  141.         synchronized (CONNECTION_DESCRIPTORS) {
  142.             connectionDescriptors = CONNECTION_DESCRIPTORS.clone();
  143.             nicknameConnectionDescriptors = new HashMap<>(NICKNAME_CONNECTION_DESCRIPTORS);
  144.         }

  145.         this.sinttestFramework = sinttestFramework;
  146.         this.sinttestConfiguration = sinttestFramework.config;
  147.         this.testRunId = sinttestFramework.testRunResult.testRunId;

  148.         String configuredDefaultConnectionNickname = sinttestConfiguration.defaultConnectionNickname;
  149.         if (configuredDefaultConnectionNickname != null) {
  150.             defaultConnectionDescriptor = nicknameConnectionDescriptors.get(configuredDefaultConnectionNickname);
  151.             if (defaultConnectionDescriptor == null) {
  152.                 throw new IllegalArgumentException("Could not find a connection descriptor for connection nickname '" + configuredDefaultConnectionNickname + "'");
  153.             }
  154.         } else {
  155.             defaultConnectionDescriptor = DEFAULT_CONNECTION_DESCRIPTOR;
  156.         }

  157.         switch (sinttestConfiguration.accountRegistration) {
  158.         case serviceAdministration:
  159.         case inBandRegistration:
  160.             accountRegistrationConnection = defaultConnectionDescriptor.construct(sinttestConfiguration);
  161.             accountRegistrationConnection.connect();

  162.             if (sinttestConfiguration.accountRegistration == AccountRegistration.inBandRegistration) {
  163.                 adminManager = null;
  164.                 accountManager = AccountManager.getInstance(accountRegistrationConnection);
  165.             } else {
  166.                 accountRegistrationConnection.login(sinttestConfiguration.adminAccountUsername,
  167.                                 sinttestConfiguration.adminAccountPassword);
  168.                 adminManager = ServiceAdministrationManager.getInstanceFor(accountRegistrationConnection);
  169.                 accountManager = null;
  170.             }
  171.             break;
  172.         case disabled:
  173.             accountRegistrationConnection = null;
  174.             adminManager = null;
  175.             accountManager = null;
  176.             break;
  177.         default:
  178.             throw new AssertionError();
  179.         }
  180.     }

  181.     SmackIntegrationTestEnvironment prepareEnvironment() throws KeyManagementException, NoSuchAlgorithmException,
  182.             InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
  183.             SmackException, IOException, XMPPException, InterruptedException {
  184.         prepareMainConnections();
  185.         return new SmackIntegrationTestEnvironment(conOne, conTwo, conThree,
  186.                 sinttestFramework.testRunResult.testRunId, sinttestConfiguration, this);
  187.     }

  188.     private void prepareMainConnections() throws KeyManagementException, NoSuchAlgorithmException, InstantiationException,
  189.             IllegalAccessException, IllegalArgumentException, InvocationTargetException, SmackException, IOException,
  190.             XMPPException, InterruptedException {
  191.         final int mainAccountCount = AccountNum.values().length;
  192.         List<AbstractXMPPConnection> connections = new ArrayList<>(mainAccountCount);
  193.         for (AccountNum mainAccountNum : AccountNum.values()) {
  194.             AbstractXMPPConnection mainConnection = getConnectedMainConnectionFor(mainAccountNum);
  195.             connections.add(mainConnection);
  196.         }
  197.         conOne = connections.get(0);
  198.         conTwo = connections.get(1);
  199.         conThree = connections.get(2);
  200.     }

  201.     public XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> getDefaultConnectionDescriptor() {
  202.         return defaultConnectionDescriptor;
  203.     }

  204.     public Collection<XmppConnectionDescriptor<? extends AbstractXMPPConnection, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>> getConnectionDescriptors() {
  205.         return Collections.unmodifiableCollection(nicknameConnectionDescriptors.values());
  206.     }

  207.     @SuppressWarnings("unchecked")
  208.     public <C extends AbstractXMPPConnection> XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> getConnectionDescriptorFor(
  209.                     Class<C> connectionClass) {
  210.         return (XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>) connectionDescriptors.getFirst(
  211.                         connectionClass);
  212.     }

  213.     void disconnectAndCleanup() throws InterruptedException {
  214.         int successfullyDeletedAccountsCount = 0;
  215.         for (AbstractXMPPConnection connection : connections.keySet()) {
  216.             if (sinttestConfiguration.accountRegistration == AccountRegistration.inBandRegistration) {
  217.                 // Note that we use the account manager from the to-be-deleted connection.
  218.                 AccountManager accountManager = AccountManager.getInstance(connection);
  219.                 try {
  220.                     accountManager.deleteAccount();
  221.                     successfullyDeletedAccountsCount++;
  222.                 } catch (NoResponseException | XMPPErrorException | NotConnectedException e) {
  223.                     LOGGER.log(Level.WARNING, "Could not delete dynamically registered account", e);
  224.                 }
  225.             }

  226.             connection.disconnect();

  227.             if (sinttestConfiguration.accountRegistration == AccountRegistration.serviceAdministration) {
  228.                 String username = connection.getConfiguration().getUsername().toString();
  229.                 Localpart usernameAsLocalpart;
  230.                 try {
  231.                     usernameAsLocalpart = Localpart.from(username);
  232.                 } catch (XmppStringprepException e) {
  233.                     throw new AssertionError(e);
  234.                 }

  235.                 EntityBareJid connectionAddress = JidCreate.entityBareFrom(usernameAsLocalpart, sinttestConfiguration.service);

  236.                 try {
  237.                     adminManager.deleteUser(connectionAddress);
  238.                     successfullyDeletedAccountsCount++;
  239.                 } catch (NoResponseException | XMPPErrorException | NotConnectedException e) {
  240.                     LOGGER.log(Level.WARNING, "Could not delete dynamically registered account", e);
  241.                 }
  242.             }
  243.         }

  244.         if (sinttestConfiguration.isAccountRegistrationPossible()) {
  245.             int unsuccessfullyDeletedAccountsCount = connections.size() - successfullyDeletedAccountsCount;
  246.             if (unsuccessfullyDeletedAccountsCount == 0) {
  247.                 LOGGER.info("Successfully deleted all created accounts ✔");
  248.             } else {
  249.                 LOGGER.warning("Could not delete all created accounts, " + unsuccessfullyDeletedAccountsCount + " remaining");
  250.             }
  251.         }

  252.         connections.clear();

  253.         if (accountRegistrationConnection != null) {
  254.             accountRegistrationConnection.disconnect();
  255.         }
  256.     }


  257.     private static final String USERNAME_PREFIX = "smack-inttest";

  258.     private AbstractXMPPConnection getConnectedMainConnectionFor(AccountNum accountNum) throws SmackException, IOException, XMPPException,
  259.             InterruptedException, KeyManagementException, NoSuchAlgorithmException, InstantiationException,
  260.             IllegalAccessException, IllegalArgumentException, InvocationTargetException {
  261.         String middlefix;
  262.         String accountUsername;
  263.         String accountPassword;
  264.         switch (accountNum) {
  265.         case One:
  266.             accountUsername = sinttestConfiguration.accountOneUsername;
  267.             accountPassword = sinttestConfiguration.accountOnePassword;
  268.             middlefix = "one";
  269.             break;
  270.         case Two:
  271.             accountUsername = sinttestConfiguration.accountTwoUsername;
  272.             accountPassword = sinttestConfiguration.accountTwoPassword;
  273.             middlefix = "two";
  274.             break;
  275.         case Three:
  276.             accountUsername = sinttestConfiguration.accountThreeUsername;
  277.             accountPassword = sinttestConfiguration.accountThreePassword;
  278.             middlefix = "three";
  279.             break;
  280.         default:
  281.             throw new IllegalStateException();
  282.         }

  283.         // Note that it is perfectly fine for account(Username|Password) to be 'null' at this point.
  284.         final String finalAccountUsername = StringUtils.isNullOrEmpty(accountUsername) ? USERNAME_PREFIX + '-' + middlefix + '-' + testRunId : accountUsername;
  285.         final String finalAccountPassword = StringUtils.isNullOrEmpty(accountPassword) ? StringUtils.insecureRandomString(16) : accountPassword;

  286.         if (sinttestConfiguration.isAccountRegistrationPossible()) {
  287.             registerAccount(finalAccountUsername, finalAccountPassword);
  288.         }

  289.         AbstractXMPPConnection mainConnection = defaultConnectionDescriptor.construct(sinttestConfiguration, builder -> {
  290.             try {
  291.                 builder.setUsernameAndPassword(finalAccountUsername, finalAccountPassword)
  292.                     .setResource(middlefix + '-' + testRunId);
  293.             } catch (XmppStringprepException e) {
  294.                 throw new IllegalArgumentException(e);
  295.             }
  296.         });

  297.         connections.put(mainConnection, defaultConnectionDescriptor);

  298.         mainConnection.connect();
  299.         mainConnection.login();

  300.         return mainConnection;
  301.     }

  302.     private void registerAccount(String username, String password) throws NoResponseException, XMPPErrorException,
  303.                     NotConnectedException, InterruptedException, XmppStringprepException {
  304.         if (accountRegistrationConnection == null) {
  305.             throw new IllegalStateException("Account registration not configured");
  306.         }

  307.         switch (sinttestConfiguration.accountRegistration) {
  308.         case serviceAdministration:
  309.             EntityBareJid userJid = JidCreate.entityBareFrom(Localpart.from(username),
  310.                             accountRegistrationConnection.getXMPPServiceDomain());
  311.             adminManager.addUser(userJid, password);
  312.             break;
  313.         case inBandRegistration:
  314.             if (!accountManager.supportsAccountCreation()) {
  315.                 throw new UnsupportedOperationException("Account creation/registration is not supported");
  316.             }
  317.             Set<String> requiredAttributes = accountManager.getAccountAttributes();
  318.             if (requiredAttributes.size() > 4) {
  319.                 throw new IllegalStateException("Unknown required attributes");
  320.             }
  321.             Map<String, String> additionalAttributes = new HashMap<>();
  322.             additionalAttributes.put("name", "Smack Integration Test");
  323.             additionalAttributes.put("email", "flow@igniterealtime.org");
  324.             Localpart usernameLocalpart = Localpart.from(username);
  325.             accountManager.createAccount(usernameLocalpart, password, additionalAttributes);
  326.             break;
  327.         case disabled:
  328.             throw new IllegalStateException("Account creation no possible");
  329.         }
  330.     }

  331.     <C extends AbstractXMPPConnection> List<C> constructConnectedConnections(XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>>  connectionDescriptor, int count)
  332.                     throws InterruptedException, SmackException, IOException, XMPPException {
  333.         List<C> connections = new ArrayList<>(count);

  334.         synchronized (connectionPool) {
  335.             @SuppressWarnings("unchecked")
  336.             List<C> pooledConnections = (List<C>) connectionPool.getAll(connectionDescriptor);
  337.             while (count > 0 && !pooledConnections.isEmpty()) {
  338.                 C connection = pooledConnections.remove(pooledConnections.size() - 1);
  339.                 connections.add(connection);
  340.                 count--;
  341.             }
  342.         }

  343.         for (int i = 0; i < count; i++) {
  344.             C connection = constructConnectedConnection(connectionDescriptor);
  345.             connections.add(connection);
  346.         }

  347.         return connections;
  348.     }

  349.     private <C extends AbstractXMPPConnection> C constructConnectedConnection(
  350.                     XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor)
  351.                     throws InterruptedException, SmackException, IOException, XMPPException {
  352.         C connection = constructConnection(connectionDescriptor, null);

  353.         connection.connect();
  354.         connection.login();

  355.         return connection;
  356.     }

  357.     AbstractXMPPConnection constructConnection()
  358.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  359.         return constructConnection(defaultConnectionDescriptor);
  360.     }

  361.     AbstractXMPPConnection constructConnectedConnection()
  362.                     throws InterruptedException, SmackException, IOException, XMPPException {
  363.         return constructConnectedConnection(defaultConnectionDescriptor);
  364.     }

  365.     <C extends AbstractXMPPConnection> C constructConnection(
  366.                     XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor)
  367.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  368.         return constructConnection(connectionDescriptor, null);
  369.     }

  370.     private <C extends AbstractXMPPConnection> C constructConnection(
  371.             XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor,
  372.             Collection<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliers)
  373.             throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  374.         String username = "sinttest-" + testRunId + '-' + (connections.size() + 1);
  375.         String password = StringUtils.randomString(24);

  376.         return constructConnection(username, password, connectionDescriptor, customConnectionConfigurationAppliers);
  377.     }

  378.     private <C extends AbstractXMPPConnection> C constructConnection(final String username, final String password,
  379.                     XmppConnectionDescriptor<C, ? extends ConnectionConfiguration, ? extends ConnectionConfiguration.Builder<?, ?>> connectionDescriptor,
  380.                     Collection<ConnectionConfigurationBuilderApplier> customConnectionConfigurationAppliers)
  381.                     throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  382.         try {
  383.             registerAccount(username, password);
  384.         } catch (XmppStringprepException e) {
  385.             throw new IllegalArgumentException(e);
  386.         }

  387.         ConnectionConfigurationBuilderApplier usernameAndPasswordApplier = configurationBuilder -> {
  388.             configurationBuilder.setUsernameAndPassword(username, password);
  389.         };

  390.         if (customConnectionConfigurationAppliers == null) {
  391.             customConnectionConfigurationAppliers = Collections.singleton(usernameAndPasswordApplier);
  392.         } else {
  393.             customConnectionConfigurationAppliers.add(usernameAndPasswordApplier);
  394.         }

  395.         C connection;
  396.         try {
  397.             connection = connectionDescriptor.construct(sinttestConfiguration, customConnectionConfigurationAppliers);
  398.         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
  399.                         | InvocationTargetException e) {
  400.             throw new IllegalStateException(e);
  401.         }

  402.         connections.put(connection, connectionDescriptor);

  403.         return connection;
  404.     }

  405.     void recycle(Collection<? extends AbstractXMPPConnection> connections) {
  406.         for (AbstractXMPPConnection connection : connections) {
  407.             recycle(connection);
  408.         }
  409.     }

  410.     void recycle(AbstractXMPPConnection connection) {
  411.         Class<? extends AbstractXMPPConnection> connectionClass = connection.getClass();
  412.         if (!connectionDescriptors.containsKey(connectionClass)) {
  413.             throw new IllegalStateException("Attempt to recycle unknown connection of class '" + connectionClass + "'");
  414.         }

  415.         if (connection.isAuthenticated()) {
  416.             XmppConnectionDescriptor<?, ?, ?> connectionDescriptor = connections.get(connection);
  417.             if (connectionDescriptor == null) {
  418.                 throw new IllegalStateException("Attempt to recycle unknown connection: " + connection);
  419.             }

  420.             synchronized (connectionPool) {
  421.                 connectionPool.put(connectionDescriptor, connection);
  422.             }
  423.         } else {
  424.             connection.disconnect();
  425.         }
  426.         // Note that we do not delete the account of the unauthenticated connection here, as it is done at the end of
  427.         // the test run together with all other dynamically created accounts.
  428.     }

  429. }