All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.sdk.iot.device.MultiplexingClient Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
package com.microsoft.azure.sdk.iot.device;

import com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException;
import com.microsoft.azure.sdk.iot.device.exceptions.MultiplexingClientRegistrationException;
import com.microsoft.azure.sdk.iot.device.transport.RetryPolicy;
import lombok.extern.slf4j.Slf4j;

import javax.net.ssl.SSLContext;
import java.util.*;

import static com.microsoft.azure.sdk.iot.device.ClientConfiguration.DEFAULT_KEEP_ALIVE_INTERVAL_IN_SECONDS;
import static com.microsoft.azure.sdk.iot.device.ClientConfiguration.DEFAULT_SEND_INTERVAL_IN_MILLISECONDS;

/**
 * A client for creating multiplexed connections to IoT hub. A multiplexed connection allows for multiple device clients
 * to communicate to the service through a single AMQPS connection.
 * 

* A given AMQPS connection requires a TLS connection, so multiplexing may be worthwhile if you want to limit the number * of TLS connections needed to connect multiple device clients to IoT hub. *

* A given multiplexing client also has a fixed amount of worker threads regardless of how many device clients are * being multiplexed. Comparatively, every non-multiplexed device client instance has its own set of worker * threads. Multiplexing may be worthwhile if you want fewer worker threads. *

* Only AMQPS and AMQPS_WS support multiplexing, and only symmetric key authenticated devices can be multiplexed. *

* {@link ModuleClient} instances cannot be multiplexed. */ @Slf4j public class MultiplexingClient { static final long DEFAULT_SEND_PERIOD_MILLIS = 10L; static final long DEFAULT_RECEIVE_PERIOD_MILLIS = 10L; static final int DEFAULT_MAX_MESSAGES_TO_SEND_PER_THREAD = 10; private static final long DEFAULT_REGISTRATION_TIMEOUT_MILLISECONDS = 60 * 1000; // 1 minute private static final long DEFAULT_UNREGISTRATION_TIMEOUT_MILLISECONDS = 60 * 1000; // 1 minute private static final long DEFAULT_MESSAGE_EXPIRATION_CHECK_PERIOD = 10000; // keys are deviceIds. Helps to optimize look ups later on which device Ids are already registered. private final Map multiplexedDeviceClients; private final DeviceIO deviceIO; private final String hostName; private final IotHubClientProtocol protocol; // This lock is used to keep open/close/register/unregister operations atomic to prevent race conditions private final Object operationLock = new Object(); private final ProxySettings proxySettings; /** * The maximum number of devices that can be multiplexed together on a single multiplexed AMQPS connection */ public static final int MAX_MULTIPLEX_DEVICE_COUNT_AMQPS = 1000; /** * The maximum number of devices that can be multiplexed together on a single multiplexed AMQPS_WS connection */ public static final int MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS = 500; /** * Instantiate a new MultiplexingClient that will establish a multiplexed connection through a proxy. * * @param hostName The hostname of your IoT Hub instance (For instance, "your-iot-hub.azure-devices.net") * @param protocol The transport protocol that this client will build the multiplexed connection on. Must be either * {@link IotHubClientProtocol#AMQPS} or {@link IotHubClientProtocol#AMQPS_WS}. */ public MultiplexingClient(String hostName, IotHubClientProtocol protocol) { this(hostName, protocol, null); } /** * Instantiate a new MultiplexingClient that will establish a multiplexed connection through a proxy. * * @param hostName The hostname of your IoT Hub instance (For instance, "your-iot-hub.azure-devices.net") * @param protocol The transport protocol that this client will build the multiplexed connection on. Must be * {@link IotHubClientProtocol#AMQPS_WS} since using {@link IotHubClientProtocol#AMQPS} does not support proxies. * @param options The optional parameters to configure this client to use. */ public MultiplexingClient(String hostName, IotHubClientProtocol protocol, MultiplexingClientOptions options) { Objects.requireNonNull(hostName); Objects.requireNonNull(protocol); switch (protocol) { case AMQPS: case AMQPS_WS: break; default: throw new IllegalArgumentException("Multiplexing is only supported for AMQPS and AMQPS_WS"); } // Deliberately using HashMap instead of ConcurrentHashMap here. HashMap is faster for several operations such // .size() and we can control the adding/removing of elements to it with synchronization within this client. this.multiplexedDeviceClients = new HashMap<>(); this.hostName = hostName; this.protocol = protocol; this.proxySettings = options != null ? options.getProxySettings() : null; long sendPeriod = options != null ? options.getSendInterval() : DEFAULT_SEND_PERIOD_MILLIS; long receivePeriod = options != null ? options.getReceiveInterval() : DEFAULT_RECEIVE_PERIOD_MILLIS; int sendMessagesPerThread = options != null ? options.getMaxMessagesSentPerSendInterval() : DEFAULT_MAX_MESSAGES_TO_SEND_PER_THREAD; int keepAliveInterval = options != null ? options.getKeepAliveInterval() : DEFAULT_KEEP_ALIVE_INTERVAL_IN_SECONDS; int sendInterval = (int) (options != null ? options.getSendInterval() : DEFAULT_SEND_INTERVAL_IN_MILLISECONDS); String threadNamePrefix = options != null ? options.getThreadNamePrefix() : null; String threadNameSuffix = options != null ? options.getThreadNameSuffix() : null; boolean useIdentifiableThreadNames = options == null || options.isUsingIdentifiableThreadNames(); long messageExpiredCheckPeriod = options != null ? options.getMessageExpirationCheckPeriod() : DEFAULT_MESSAGE_EXPIRATION_CHECK_PERIOD; if (sendPeriod < 0) { throw new IllegalArgumentException("Send period cannot be negative"); } else if (sendPeriod == 0) //default builder value for this option, signals that user didn't set a value { sendPeriod = DEFAULT_SEND_PERIOD_MILLIS; } if (receivePeriod < 0) { throw new IllegalArgumentException("Receive period cannot be negative"); } else if (receivePeriod == 0) //default builder value for this option, signals that user didn't set a value { receivePeriod = DEFAULT_RECEIVE_PERIOD_MILLIS; } if (sendMessagesPerThread == 0) //default builder value for this option, signals that user didn't set a value { sendMessagesPerThread = DEFAULT_MAX_MESSAGES_TO_SEND_PER_THREAD; } // Optional settings from MultiplexingClientOptions SSLContext sslContext = options != null ? options.getSslContext() : null; this.deviceIO = new DeviceIO( hostName, protocol, sslContext, proxySettings, keepAliveInterval, sendInterval, useIdentifiableThreadNames, threadNamePrefix, threadNameSuffix, messageExpiredCheckPeriod); this.deviceIO.setMaxNumberOfMessagesSentPerSendThread(sendMessagesPerThread); this.deviceIO.setSendPeriodInMilliseconds(sendPeriod); this.deviceIO.setReceivePeriodInMilliseconds(receivePeriod); } /** * Opens this multiplexing client. This may be done before or after registering any number of device clients. *

* This call behaves synchronously, so if it returns without throwing, then all registered device clients were * successfully opened. *

* If this client is already open, then this method will do nothing. *

* @param withRetry if true, this open call will apply the current retry policy to allow for the open call to be * retried if it fails. * * @throws IotHubClientException If any IO or authentication errors occur while opening the multiplexed connection. * @throws MultiplexingClientRegistrationException If one or many of the registered devices failed to authenticate. * Any devices not found in the map of registration exceptions provided by * {@link MultiplexingClientRegistrationException#getRegistrationExceptions()} have registered successfully. * Even when this is thrown, the AMQPS/AMQPS_WS connection is still open, and other clients may be registered to it. */ public void open(boolean withRetry) throws IotHubClientException { synchronized (this.operationLock) { log.info("Opening multiplexing client"); this.deviceIO.open(withRetry); log.info("Successfully opened multiplexing client"); } } /** * Close this multiplexing client. This will close all active device sessions as well as the AMQP connection. *

* If this client is already closed, then this method will do nothing. *

* Once closed, this client can be re-opened. It will preserve all previously registered device clients. *

*/ public void close() { synchronized (this.operationLock) { log.info("Closing multiplexing client"); this.deviceIO.close(); // Note that this method does not close each of the registered device client instances. This is intentional // as the calls to deviceClient.close() do nothing besides close the deviceIO layer, which is already closed // by the above code. log.info("Successfully closed multiplexing client"); } } /** * Register a device client to this multiplexing client. This method may be called before or after opening the * multiplexed connection. *

* Users should use {@link #registerDeviceClients(Iterable)} for registering multiple devices as it has some * performance improvements over repeatedly calling this method for individual device registrations. This method blocks on each registration, whereas * {@link #registerDeviceClients(Iterable)} blocks on all of the registrations after starting them all asynchronously. *

* A device client can be unregistered using {@link #unregisterDeviceClient(DeviceClient)}, {@link #unregisterDeviceClient(DeviceClient, long)}, * {@link #unregisterDeviceClients(Iterable)}, or {@link #unregisterDeviceClients(Iterable, long)}. A device client will not be unregistered * automatically if it encounters a non-retryable exception, so users are responsible for unregistering a device client * when they no longer want it in this multiplexing client. *

* Up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS} devices can be registered on a multiplexed AMQPS connection, * and up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS} devices can be registered on a multiplexed AMQPS_WS connection. *

* If the multiplexing client is already open, then this device client will automatically * be opened, too. If the multiplexing client is not open yet, then this device client will not be opened until * {@link MultiplexingClient#open(boolean)} is called. *

* If the multiplexed connection is already open, then this call will add this device client to the * multiplexed connection, and then will block until the registration has been completed. *

* Any proxy settings set to the provided device clients will be overwritten by the proxy settings of this multiplexing client. *

* The registered device client must use the same transport protocol (AMQPS or AMQPS_WS) that this multiplexing client uses. *

* Each registered device client may have its own retry policy and its own SAS token expiry time, separate from every other registered device client. *

* The registered device client must use symmetric key based authentication. *

* The registered device client must belong to the same IoT hub as all previously registered device clients. *

* If the provided device client is already registered to this multiplexing client, then then this method will do nothing. *

* Any subscriptions (twin, methods, cloud to device messages) set on this device client from when it was * previously registered to any multiplexing client will need to be set again as subscriptions and their callbacks are not preserved. *

* @throws InterruptedException If the thread gets interrupted while waiting for the registration to succeed. This * will never be thrown if the multiplexing client is not open yet. * @throws MultiplexingClientRegistrationException If the device failed to register. Details for * this failure can be found nested within the map given by * {@link MultiplexingClientRegistrationException#getRegistrationExceptions()}. If this exception is * thrown, the device was not registered, and therefore it does not need to be unregistered. * @throws IotHubClientException If this operation takes longer than the default timeout allows. * @param deviceClient The device client to associate with this multiplexing client. */ public void registerDeviceClient(DeviceClient deviceClient) throws InterruptedException, IotHubClientException { this.registerDeviceClient(deviceClient, DEFAULT_REGISTRATION_TIMEOUT_MILLISECONDS); } /** * Register a device client to this multiplexing client. This method may be called before or after opening the * multiplexed connection. *

* Users should use {@link #registerDeviceClients(Iterable)} for registering multiple devices as it has some * performance improvements over repeatedly calling this method for individual device registrations. This method blocks on each registration, whereas * {@link #registerDeviceClients(Iterable)} blocks on all of the registrations after starting them all asynchronously. *

* A device client can be unregistered using {@link #unregisterDeviceClient(DeviceClient)}, {@link #unregisterDeviceClient(DeviceClient, long)}, * {@link #unregisterDeviceClients(Iterable)}, or {@link #unregisterDeviceClients(Iterable, long)}. A device client will not be unregistered * automatically if it encounters a non-retryable exception, so users are responsible for unregistering a device client * when they no longer want it in this multiplexing client. *

* Up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS} devices can be registered on a multiplexed AMQPS connection, * and up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS} devices can be registered on a multiplexed AMQPS_WS connection. *

* If the multiplexing client is already open, then this device client will automatically * be opened, too. If the multiplexing client is not open yet, then this device client will not be opened until * {@link MultiplexingClient#open(boolean)} is called. *

* If the multiplexed connection is already open, then this call will add this device client to the * multiplexed connection, and then will block until the registration has been completed. *

* Any proxy settings set to the provided device clients will be overwritten by the proxy settings of this multiplexing client. *

* The registered device client must use the same transport protocol (AMQPS or AMQPS_WS) that this multiplexing client uses. *

* Each registered device client may have its own retry policy and its own SAS token expiry time, separate from every other registered device client. *

* The registered device client must use symmetric key based authentication. *

* The registered device client must belong to the same IoT hub as all previously registered device clients. *

* If the provided device client is already registered to this multiplexing client, then then this method will do nothing. *

* Any subscriptions (twin, methods, cloud to device messages) set on this device client from when it was * previously registered to any multiplexing client will need to be set again as subscriptions and their callbacks are not preserved. *

* @throws InterruptedException If the thread gets interrupted while waiting for the registration to succeed. This * will never be thrown if the multiplexing client is not open yet. * @throws MultiplexingClientRegistrationException If the device failed to register. Details for * this failure can be found nested within the map given by * {@link MultiplexingClientRegistrationException#getRegistrationExceptions()}. If this exception is * thrown, the device was not registered, and therefore it does not need to be unregistered. * @throws IotHubClientException If this operation takes longer than the provided timeout allows. * @param deviceClient The device client to associate with this multiplexing client. * @param timeoutMilliseconds How long (in milliseconds) to let this operation wait for all registrations to complete. * If this threshold is passed, a {@link com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException} with status code * {@link IotHubStatusCode#DEVICE_OPERATION_TIMED_OUT} is thrown. */ public void registerDeviceClient(DeviceClient deviceClient, long timeoutMilliseconds) throws InterruptedException, IotHubClientException { Objects.requireNonNull(deviceClient); List clientList = new ArrayList<>(); clientList.add(deviceClient); registerDeviceClients(clientList, timeoutMilliseconds); } /** * Register multiple device clients to this multiplexing client. This method may be called before or after opening the multiplexed * connection. *

* Up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS} devices can be registered on a multiplexed AMQPS connection, * and up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS} devices can be registered on a multiplexed AMQPS_WS connection. *

* A device client can be unregistered using {@link #unregisterDeviceClient(DeviceClient)}, {@link #unregisterDeviceClient(DeviceClient, long)}, * {@link #unregisterDeviceClients(Iterable)}, or {@link #unregisterDeviceClients(Iterable, long)}. A device client will not be unregistered * automatically if it encounters a non-retryable exception, so users are responsible for unregistering a device client * when they no longer want it in this multiplexing client. *

* If the multiplexing client is already open, then these device clients will automatically * be opened, too. If the multiplexing client is not open yet, then these device clients will not be opened until * {@link MultiplexingClient#open(boolean)} is called. *

* If the multiplexed connection is already open, then this call will asynchronously add each device client to the * multiplexed connection, and then will block until all registrations have been completed. *

* Any proxy settings set to the provided device clients will be overwritten by the proxy settings of this multiplexing client. *

* The registered device clients must use the same transport protocol (AMQPS or AMQPS_WS) that this multiplexing client uses. *

* Each registered device client may have its own retry policy and its own SAS token expiry time, separate from every other registered device client. *

* The registered device clients must use symmetric key based authentication. *

* The registered device clients must belong to the same IoT hub as all previously registered device clients. *

* If any of these device clients are already registered to this multiplexing client, then then this method will * not do anything to that particular device client. All other provided device clients will still be registered though. *

* Any subscriptions (twin, methods, cloud to device messages) set on these device clients from when it was * previously registered to any multiplexing client will need to be set again as subscriptions and their callbacks are not preserved. *

* @throws InterruptedException If the thread gets interrupted while waiting for the registration to succeed. This * will never be thrown if the multiplexing client is not open yet. * @throws MultiplexingClientRegistrationException If one or more devices failed to register. * Details for each failure can be found in the map provided by * {@link MultiplexingClientRegistrationException#getRegistrationExceptions()}. Any devices not * found in the map of registration exceptions provided by this exception have registered successfully. Any devices * that are found in the map of registration exceptions provided by this exception were not registered, and therefore * do not need to be unregistered. * @throws IotHubClientException If this operation takes longer than the default timeout allows. * @param deviceClients The device clients to associate with this multiplexing client. */ public void registerDeviceClients(Iterable deviceClients) throws InterruptedException, IotHubClientException { this.registerDeviceClients(deviceClients, DEFAULT_REGISTRATION_TIMEOUT_MILLISECONDS); } /** * Register multiple device clients to this multiplexing client. This method may be called before or after opening the multiplexed * connection. *

* Up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS} devices can be registered on a multiplexed AMQPS connection, * and up to {@link #MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS} devices can be registered on a multiplexed AMQPS_WS connection. *

* A device client can be unregistered using {@link #unregisterDeviceClient(DeviceClient)}, {@link #unregisterDeviceClient(DeviceClient, long)}, * {@link #unregisterDeviceClients(Iterable)}, or {@link #unregisterDeviceClients(Iterable, long)}. A device client will not be unregistered * automatically if it encounters a non-retryable exception, so users are responsible for unregistering a device client * when they no longer want it in this multiplexing client. *

* If the multiplexing client is already open, then these device clients will automatically * be opened, too. If the multiplexing client is not open yet, then these device clients will not be opened until * {@link MultiplexingClient#open(boolean)} is called. *

* If the multiplexed connection is already open, then this call will asynchronously add each device client to the * multiplexed connection, and then will block until all registrations have been completed. *

* Any proxy settings set to the provided device clients will be overwritten by the proxy settings of this multiplexing client. *

* The registered device clients must use the same transport protocol (AMQPS or AMQPS_WS) that this multiplexing client uses. *

* Each registered device client may have its own retry policy and its own SAS token expiry time, separate from every other registered device client. *

* The registered device clients must use symmetric key based authentication. *

* The registered device clients must belong to the same IoT hub as all previously registered device clients. *

* If any of these device clients are already registered to this multiplexing client, then then this method will * not do anything to that particular device client. All other provided device clients will still be registered though. *

* Any subscriptions (twin, methods, cloud to device messages) set on these device clients from when it was * previously registered to any multiplexing client will need to be set again as subscriptions and their callbacks are not preserved. *

* @throws InterruptedException If the thread gets interrupted while waiting for the registration to succeed. This * will never be thrown if the multiplexing client is not open yet. * @throws MultiplexingClientRegistrationException If one or more devices failed to register. * Details for each failure can be found in the map provided by * {@link MultiplexingClientRegistrationException#getRegistrationExceptions()}. Any devices not * found in the map of registration exceptions provided by this exception have registered successfully. Any devices * that are found in the map of registration exceptions provided by this exception were not registered, and therefore * do not need to be unregistered. * @throws IotHubClientException If this operation takes longer than the provided timeout allows. * @param deviceClients The device clients to associate with this multiplexing client. * @param timeoutMilliseconds How long (in milliseconds) to let this operation wait for all registrations to complete. * If this threshold is passed, a {@link com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException} with status code * {@link IotHubStatusCode#DEVICE_OPERATION_TIMED_OUT} is thrown. */ public void registerDeviceClients(Iterable deviceClients, long timeoutMilliseconds) throws InterruptedException, IotHubClientException { Objects.requireNonNull(deviceClients); if (timeoutMilliseconds <= 0) { throw new IllegalArgumentException("Cannot set a device registration timeout of less than or equal to 0 milliseconds"); } synchronized (this.operationLock) { List clientConfigsToRegister = new ArrayList<>(); Map devicesToRegisterMap = new HashMap<>(); for (DeviceClient deviceClientToRegister : deviceClients) { devicesToRegisterMap.put(deviceClientToRegister.getConfig().getDeviceId(), deviceClientToRegister); ClientConfiguration configToAdd = deviceClientToRegister.getConfig(); // Overwrite the proxy settings of the new client to match the multiplexing client settings configToAdd.setProxySettings(this.proxySettings); if (configToAdd.getAuthenticationType() != ClientConfiguration.AuthType.SAS_TOKEN) { throw new UnsupportedOperationException("Can only register to multiplex a device client that uses SAS token based authentication"); } if (configToAdd.getProtocol() != this.protocol) { throw new UnsupportedOperationException("A device client cannot be registered to a multiplexing client that specifies a different transport protocol."); } if (this.protocol == IotHubClientProtocol.AMQPS && this.multiplexedDeviceClients.size() > MAX_MULTIPLEX_DEVICE_COUNT_AMQPS) { throw new UnsupportedOperationException(String.format("Multiplexed connections over AMQPS only support up to %d devices", MAX_MULTIPLEX_DEVICE_COUNT_AMQPS)); } // Typically client side validation is duplicate work, but IoT hub doesn't give a good error message when closing the // AMQPS_WS connection so this is the only way that users will know about this limit if (this.protocol == IotHubClientProtocol.AMQPS_WS && this.multiplexedDeviceClients.size() > MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS) { throw new UnsupportedOperationException(String.format("Multiplexed connections over AMQPS_WS only support up to %d devices", MAX_MULTIPLEX_DEVICE_COUNT_AMQPS_WS)); } if (!this.hostName.equalsIgnoreCase(configToAdd.getIotHubHostname())) { throw new UnsupportedOperationException("A device client cannot be registered to a multiplexing client that specifies a different host name."); } if (deviceClientToRegister.getDeviceIO() != null && deviceClientToRegister.getDeviceIO().isOpen() && !deviceClientToRegister.isMultiplexed) { throw new UnsupportedOperationException("Cannot register a device client to a multiplexed connection when the device client was already opened."); } deviceClientToRegister.setAsMultiplexed(); deviceClientToRegister.setDeviceIO(this.deviceIO); deviceClientToRegister.markAsMultiplexed(); // Set notifies us if the device client is already in the set boolean deviceAlreadyRegistered = this.multiplexedDeviceClients.containsKey(deviceClientToRegister.getConfig().getDeviceId()); if (deviceAlreadyRegistered) { log.debug("Device {} wasn't registered to the multiplexed connection because it is already registered.", configToAdd.getDeviceId()); } else { clientConfigsToRegister.add(configToAdd); } } // if the device IO hasn't been created yet, then this client will be registered once it is created. for (ClientConfiguration configBeingRegistered : clientConfigsToRegister) { log.info("Registering device {} to multiplexing client", configBeingRegistered.getDeviceId()); } try { this.deviceIO.registerMultiplexedDeviceClient(clientConfigsToRegister, timeoutMilliseconds); // Only update the local state map once the register call has succeeded this.multiplexedDeviceClients.putAll(devicesToRegisterMap); } catch (MultiplexingClientRegistrationException e) { // If registration failed, 1 or more clients should not be considered registered in this layer's state. // Save the exception so it can be rethrown once the local state has been updated to match the actual state // of the multiplexed connection. for (DeviceClient clientsThatAttemptedToRegister : deviceClients) { // Only update the local state map once the register call has succeeded String deviceIdThatAttemptedToRegister = clientsThatAttemptedToRegister.getConfig().getDeviceId(); if (!e.getRegistrationExceptions().containsKey(deviceIdThatAttemptedToRegister)) { this.multiplexedDeviceClients.put(deviceIdThatAttemptedToRegister, clientsThatAttemptedToRegister); } } throw e; } } } /** * Unregister a device client from this multiplexing client. This method may be called before or after opening the * multiplexed connection. *

* Users should use {@link #unregisterDeviceClients(Iterable)} for unregistering multiple devices as it has some * performance improvements over repeatedly calling this method for individual device unregistrations. This method blocks on each unregistration, whereas * {@link #registerDeviceClients(Iterable)} blocks on all of the unregistrations after starting them all asynchronously. *

* If the multiplexed connection is already open, then this call will close the AMQP device session associated with * this device, but it will not close any other registered device sessions or the multiplexing client itself. *

* If the multiplexed connection is already open, and this call would unregister the last device client, * the multiplexed connection will remain open. The multiplexed connection can only be closed by calling * {@link #close()} *

* Once a device client is unregistered, it may be re-registered to this or any other multiplexing client. It cannot * be used in non-multiplexing scenarios. *

* Any subscriptions set on this device client for twin/methods/cloud to device messages will need to be set again * after this device is re-registered. *

* @param deviceClient The device client to unregister from this multiplexing client. * @throws InterruptedException If the thread gets interrupted while waiting for the unregistration to succeed. * @throws IotHubClientException If the unregistration takes longer than the default timeout allows. */ public void unregisterDeviceClient(DeviceClient deviceClient) throws InterruptedException, IotHubClientException { this.unregisterDeviceClient(deviceClient, DEFAULT_UNREGISTRATION_TIMEOUT_MILLISECONDS); } /** * Unregister a device client from this multiplexing client. This method may be called before or after opening the * multiplexed connection. *

* Users should use {@link #unregisterDeviceClients(Iterable)} for unregistering multiple devices as it has some * performance improvements over repeatedly calling this method for individual device unregistrations. This method blocks on each unregistration, whereas * {@link #registerDeviceClients(Iterable)} blocks on all of the unregistrations after starting them all asynchronously. *

* If the multiplexed connection is already open, then this call will close the AMQP device session associated with * this device, but it will not close any other registered device sessions or the multiplexing client itself. *

* If the multiplexed connection is already open, and this call would unregister the last device client, * the multiplexed connection will remain open. The multiplexed connection can only be closed by calling * {@link #close()} *

* Once a device client is unregistered, it may be re-registered to this or any other multiplexing client. It cannot * be used in non-multiplexing scenarios. *

* Any subscriptions set on this device client for twin/methods/cloud to device messages will need to be set again * after this device is re-registered. *

* @param deviceClient The device client to unregister from this multiplexing client. * @param timeoutMilliseconds How long (in milliseconds) to let this operation wait for all registrations to complete. * If this threshold is passed, a {@link com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException} with status code * {@link IotHubStatusCode#DEVICE_OPERATION_TIMED_OUT} is thrown. * @throws InterruptedException If the thread gets interrupted while waiting for the unregistration to succeed. * @throws IotHubClientException If the unregistration takes longer than the provided timeout allows. */ public void unregisterDeviceClient(DeviceClient deviceClient, long timeoutMilliseconds) throws InterruptedException, IotHubClientException { Objects.requireNonNull(deviceClient); List clientList = new ArrayList<>(); clientList.add(deviceClient); unregisterDeviceClients(clientList, timeoutMilliseconds); } /** * Unregister multiple device clients from this multiplexing client. This method may be called before or after opening the * multiplexed connection. *

* If the multiplexed connection is already open, then this call will close the AMQP device session associated with * this device, but it will not close any other registered device sessions or the multiplexing client itself. *

* If the multiplexed connection is already open, and this call would unregister the last device clients, * the multiplexed connection will remain open. The multiplexed connection can only be closed by calling * {@link #close()} *

* Once a device client is unregistered, it may be re-registered to this or any other multiplexing client. It cannot * be used in non-multiplexing scenarios. *

* Any subscriptions set on these device clients for twin/methods/cloud to device messages will need to be set again * after these devices are re-registered. *

* @param deviceClients The device clients to unregister from this multiplexing client. * @throws InterruptedException If the thread gets interrupted while waiting for the unregistration to succeed. * @throws IotHubClientException If the unregistration takes longer than the default timeout allows. */ public void unregisterDeviceClients(Iterable deviceClients) throws InterruptedException, IotHubClientException { this.unregisterDeviceClients(deviceClients, DEFAULT_UNREGISTRATION_TIMEOUT_MILLISECONDS); } /** * Unregister multiple device clients from this multiplexing client. This method may be called before or after opening the * multiplexed connection. *

* If the multiplexed connection is already open, then this call will close the AMQP device session associated with * this device, but it will not close any other registered device sessions or the multiplexing client itself. *

* If the multiplexed connection is already open, and this call would unregister the last device clients, * the multiplexed connection will remain open. The multiplexed connection can only be closed by calling * {@link #close()} *

* Once a device client is unregistered, it may be re-registered to this or any other multiplexing client. It cannot * be used in non-multiplexing scenarios. *

* Any subscriptions set on these device clients for twin/methods/cloud to device messages will need to be set again * after these devices are re-registered. *

* @param deviceClients The device clients to unregister from this multiplexing client. * @param timeoutMilliseconds How long (in milliseconds) to let this operation wait for all registrations to complete. * If this threshold is passed, a {@link com.microsoft.azure.sdk.iot.device.exceptions.IotHubClientException} with status code * {@link IotHubStatusCode#DEVICE_OPERATION_TIMED_OUT} is thrown. * @throws InterruptedException If the thread gets interrupted while waiting for the unregistration to succeed. * @throws IotHubClientException If the unregistration takes longer than the provided timeout allows. */ public void unregisterDeviceClients(Iterable deviceClients, long timeoutMilliseconds) throws InterruptedException, IotHubClientException { Objects.requireNonNull(deviceClients); if (timeoutMilliseconds <= 0) { throw new IllegalArgumentException("Cannot set a device unregistration timeout of less than 0 milliseconds"); } synchronized (this.operationLock) { List clientConfigsToRegister = new ArrayList<>(); for (DeviceClient deviceClientToUnregister : deviceClients) { ClientConfiguration configToUnregister = deviceClientToUnregister.getConfig(); clientConfigsToRegister.add(configToUnregister); log.info("Unregistering device {} from multiplexing client", deviceClientToUnregister.getConfig().getDeviceId()); this.multiplexedDeviceClients.remove(deviceClientToUnregister.getConfig().getDeviceId()); deviceClientToUnregister.setDeviceIO(null); // Clear the device client's subscriptions after it has been unregistered from the multiplexing client // We may make this optional at some point in the future if users want to be able to preserve subscriptions. deviceClientToUnregister.markTwinAsUnsubscribed(); deviceClientToUnregister.markMethodsAsUnsubscribed(); deviceClientToUnregister.setMessageCallback(null, null); } this.deviceIO.unregisterMultiplexedDeviceClient(clientConfigsToRegister, timeoutMilliseconds); } } /** * Registers a callback to be executed when the connection status of the multiplexed connection as a whole changes. * The callback will be fired with a status and a reason why the multiplexed connection's status changed. When the * callback is fired, the provided context will be provided alongside the status and reason. * *

Note that this callback will not be fired for device specific connection status changes. In order to be notified * when a particular device's connection status changes, you will need to register a connection status change callback * on that device client instance using {@link DeviceClient#setConnectionStatusChangeCallback(IotHubConnectionStatusChangeCallback, Object)}. * *

Note that the thread used to deliver this callback should not be used to call open()/close() on the client * that this callback belongs to. All open()/close() operations should be done on a separate thread

* * @param callback The callback to be fired when the connection status of the multiplexed connection changes. * Can be null to unset this listener as long as the provided callbackContext is also null. * @param callbackContext a context to be passed to the callback. Can be {@code null}. */ public void setConnectionStatusChangeCallback(IotHubConnectionStatusChangeCallback callback, Object callbackContext) { this.deviceIO.setMultiplexingConnectionStateCallback(callback, callbackContext); } /** * Returns if a device client for the provided device Id is already registered to this multiplexing client. * @param deviceId The Id of the device client to look for. * @return True if a device client is already registered with this Id. False otherwise. */ public boolean isDeviceRegistered(String deviceId) { synchronized (this.operationLock) { return this.multiplexedDeviceClients.containsKey(deviceId); } } /** * Get the number of currently registered devices on this multiplexing client. * @return The number of currently registered devices on this multiplexing client. */ public int getRegisteredDeviceCount() { synchronized (this.operationLock) { // O(1) operation since HashMap saves this value as an integer rather than iterating over each element. // So there is no need to be more clever about this. // Note that ConcurrentHashMap's version of this method has O(n) time complexity, so we avoid using that type here. // Instead we just make every method synchronous to protect against race conditions. return this.multiplexedDeviceClients.size(); } } /** * Sets the given retry policy for the multiplexing client level connection management. * * See more details about the default retry policy and about using custom retry policies here * @param retryPolicy The policy that the multiplexing client will use when reconnecting. */ public void setRetryPolicy(RetryPolicy retryPolicy) { this.deviceIO.setMultiplexingRetryPolicy(retryPolicy); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy