Java tutorial
/** * Copyright (c) 2017, 2018 Bosch Software Innovations GmbH and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bosch Software Innovations GmbH - initial creation * Red Hat Inc */ package org.eclipse.hono.service; import java.net.HttpURLConnection; import java.util.Objects; import java.util.Optional; import java.util.function.BiConsumer; import io.vertx.proton.ProtonDelivery; import org.apache.qpid.proton.amqp.Binary; import org.apache.qpid.proton.amqp.messaging.Data; import org.apache.qpid.proton.message.Message; import org.eclipse.hono.client.ClientErrorException; import org.eclipse.hono.client.HonoClient; import org.eclipse.hono.client.MessageConsumer; import org.eclipse.hono.client.MessageSender; import org.eclipse.hono.client.RegistrationClient; import org.eclipse.hono.client.ServiceInvocationException; import org.eclipse.hono.client.TenantClient; import org.eclipse.hono.config.AbstractConfig; import org.eclipse.hono.config.ProtocolAdapterProperties; import org.eclipse.hono.service.auth.TenantApiTrustOptions; import org.eclipse.hono.service.auth.device.Device; import org.eclipse.hono.service.command.CommandConnection; import org.eclipse.hono.service.command.CommandResponseSender; import org.eclipse.hono.service.monitoring.ConnectionEventProducer; import org.eclipse.hono.util.CommandConstants; import org.eclipse.hono.util.Constants; import org.eclipse.hono.util.CredentialsConstants; import org.eclipse.hono.util.EventConstants; import org.eclipse.hono.util.MessageHelper; import org.eclipse.hono.util.RegistrationConstants; import org.eclipse.hono.util.ResourceIdentifier; import org.eclipse.hono.util.TenantConstants; import org.eclipse.hono.util.Strings; import org.eclipse.hono.util.TenantObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import io.vertx.core.CompositeFuture; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.core.net.TrustOptions; import io.vertx.ext.healthchecks.HealthCheckHandler; import io.vertx.ext.healthchecks.Status; import io.vertx.proton.ProtonConnection; import io.vertx.proton.ProtonHelper; /** * A base class for implementing protocol adapters. * <p> * Provides connections to device registration and telemetry and event service endpoints. * * @param <T> The type of configuration properties used by this service. */ public abstract class AbstractProtocolAdapterBase<T extends ProtocolAdapterProperties> extends AbstractServiceBase<T> { /** * The <em>application/octet-stream</em> content type. */ protected static final String CONTENT_TYPE_OCTET_STREAM = "application/octet-stream"; private HonoClient messagingClient; private HonoClient registrationClient; private HonoClient tenantClient; private HonoClient credentialsServiceClient; private CommandConnection commandConnection; private ConnectionEventProducer connectionEventProducer; /** * Sets the configuration by means of Spring dependency injection. * <p> * Most protocol adapters will support a single transport protocol to communicate with devices only. For those * adapters there will only be a single bean instance available in the application context of type <em>T</em>. */ @Autowired @Override public void setConfig(final T configuration) { setSpecificConfig(configuration); } /** * Sets the client to use for connecting to the Tenant service. * * @param tenantClient The client. * @throws NullPointerException if the client is {@code null}. */ @Qualifier(TenantConstants.TENANT_ENDPOINT) @Autowired public final void setTenantServiceClient(final HonoClient tenantClient) { this.tenantClient = Objects.requireNonNull(tenantClient); } /** * Gets the client used for connecting to the Tenant service. * * @return The client. */ public final HonoClient getTenantServiceClient() { return tenantClient; } /** * Gets a client for interacting with the Tenant service. * * @return The client. */ protected final Future<TenantClient> getTenantClient() { return getTenantServiceClient().getOrCreateTenantClient(); } /** * Sets the client to use for connecting to the Hono Messaging component. * * @param honoClient The client. * @throws NullPointerException if hono client is {@code null}. */ @Qualifier(Constants.QUALIFIER_MESSAGING) @Autowired public final void setHonoMessagingClient(final HonoClient honoClient) { this.messagingClient = Objects.requireNonNull(honoClient); } /** * Gets the client used for connecting to the Hono Messaging component. * * @return The client. */ public final HonoClient getHonoMessagingClient() { return messagingClient; } /** * Sets the client to use for connecting to the Device Registration service. * * @param registrationServiceClient The client. * @throws NullPointerException if the client is {@code null}. */ @Qualifier(RegistrationConstants.REGISTRATION_ENDPOINT) @Autowired public final void setRegistrationServiceClient(final HonoClient registrationServiceClient) { this.registrationClient = Objects.requireNonNull(registrationServiceClient); } /** * Gets the client used for connecting to the Device Registration service. * * @return The client. */ public final HonoClient getRegistrationServiceClient() { return registrationClient; } /** * Sets the client to use for connecting to the Credentials service. * * @param credentialsServiceClient The client. * @throws NullPointerException if the client is {@code null}. */ @Qualifier(CredentialsConstants.CREDENTIALS_ENDPOINT) @Autowired public final void setCredentialsServiceClient(final HonoClient credentialsServiceClient) { this.credentialsServiceClient = Objects.requireNonNull(credentialsServiceClient); } /** * Gets the client used for connecting to the Credentials service. * * @return The client. */ public final HonoClient getCredentialsServiceClient() { return credentialsServiceClient; } /** * Sets the producer for connections events. * * @param connectionEventProducer The instance which will handle the production of connection events. Depending on * the setup this could be a simple log message or an event using the Hono Event API. */ @Autowired(required = false) public void setConnectionEventProducer(final ConnectionEventProducer connectionEventProducer) { this.connectionEventProducer = connectionEventProducer; } /** * Gets the producer of connection events. * * @return The implementation for producing connection events. Maybe {@code null}. */ public ConnectionEventProducer getConnectionEventProducer() { return this.connectionEventProducer; } /** * Gets this adapter's type name. * <p> * The name should be unique among all protocol adapters that are part of a Hono installation. There is no specific * scheme to follow but it is recommended to include the adapter's origin and the protocol that the adapter supports * in the name and to use lower case letters only. * <p> * Based on this recommendation, Hono's standard HTTP adapter for instance might report <em>hono-http</em> as its * type name. * <p> * The name returned by this method is added to a downstream message by the * {@link #addProperties(Message, JsonObject, boolean)} method. * * @return The adapter's name. */ protected abstract String getTypeName(); /** * Sets the client to use for connecting to the AMQP 1.0 network to receive commands. * * @param commandConnection The command connection. */ @Autowired public final void setCommandConnection(final CommandConnection commandConnection) { this.commandConnection = commandConnection; } @Override protected final Future<Void> startInternal() { final Future<Void> result = Future.future(); if (Strings.isNullOrEmpty(getTypeName())) { result.fail(new IllegalStateException("adapter does not define a typeName")); } else if (tenantClient == null) { result.fail(new IllegalStateException("Tenant service client must be set")); } else if (messagingClient == null) { result.fail(new IllegalStateException("Hono Messaging client must be set")); } else if (registrationClient == null) { result.fail(new IllegalStateException("Device Registration service client must be set")); } else if (credentialsServiceClient == null) { result.fail(new IllegalStateException("Credentials service client must be set")); } else if (commandConnection == null) { result.fail(new IllegalStateException("Command and Control service client must be set")); } else { connectToService(tenantClient, "Tenant service"); connectToService(messagingClient, "Messaging"); connectToService(registrationClient, "Device Registration service"); connectToService(credentialsServiceClient, "Credentials service"); connectToService(commandConnection, "Command and Control service"); doStart(result); } return result; } /** * Invoked after the adapter has started up. * <p> * This default implementation simply completes the future. * <p> * Subclasses should override this method to perform any work required on start-up of this protocol adapter. * * @param startFuture The future to complete once start up is complete. */ protected void doStart(final Future<Void> startFuture) { startFuture.complete(); } @Override protected final Future<Void> stopInternal() { LOG.info("stopping protocol adapter"); final Future<Void> result = Future.future(); final Future<Void> doStopResult = Future.future(); doStop(doStopResult); doStopResult.compose(s -> closeServiceClients()).recover(t -> { LOG.info("error while stopping protocol adapter", t); return Future.failedFuture(t); }).compose(s -> { result.complete(); LOG.info("successfully stopped protocol adapter"); }, result); return result; } private CompositeFuture closeServiceClients() { return CompositeFuture.all(closeServiceClient(tenantClient), closeServiceClient(messagingClient), closeServiceClient(registrationClient), closeServiceClient(credentialsServiceClient)); } private Future<Void> closeServiceClient(final HonoClient client) { final Future<Void> shutdownTracker = Future.future(); if (client == null) { shutdownTracker.complete(); } else { client.shutdown(shutdownTracker.completer()); } return shutdownTracker; } /** * Invoked directly before the adapter is shut down. * <p> * Subclasses should override this method to perform any work required before shutting down this protocol adapter. * * @param stopFuture The future to complete once all work is done and shut down should commence. */ protected void doStop(final Future<Void> stopFuture) { // to be overridden by subclasses stopFuture.complete(); } /** * Gets the options for configuring the server side trust anchor. * <p> * This implementation returns the options returned by * {@link AbstractConfig#getTrustOptions()} if not {@code null}. * Otherwise, it returns trust options for using the configured * Tenant service client for retrieving tenant specific trust anchor * configuration. * * @return The trust options. */ @Override protected TrustOptions getServerTrustOptions() { return Optional.ofNullable(getConfig().getTrustOptions()) .orElse(new TenantApiTrustOptions(getTenantServiceClient())); } /** * Connects to a Hono Service component using the configured client. * * @param client The Hono client for the service that is to be connected. * @param serviceName The name of the service that is to be connected (used for logging). * @return A future that will succeed once the connection has been established. The future will fail if the * connection cannot be established. * @throws NullPointerException if serviceName is {@code null}. * @throws IllegalArgumentException if client is {@code null}. */ protected final Future<HonoClient> connectToService(final HonoClient client, final String serviceName) { Objects.requireNonNull(serviceName); if (client == null) { return Future.failedFuture( new IllegalArgumentException(String.format("Hono %s client not set", serviceName))); } else { final Handler<ProtonConnection> disconnectHandler = getHandlerForDisconnectHonoService(client, serviceName); return client.connect(disconnectHandler).map(connectedClient -> { LOG.info("connected to {}", serviceName); return connectedClient; }).recover(t -> { LOG.warn("failed to connect to {}", serviceName, t); return Future.failedFuture(t); }); } } /** * Gets a handler that attempts a reconnect for a Hono service client after * {@link Constants#DEFAULT_RECONNECT_INTERVAL_MILLIS} milliseconds. * * @param client The Hono client for the service that is to be connected. * @param serviceName The name of the service that is to be connected (used for logging). * @return A handler that attempts the reconnect. * @throws NullPointerException if any of the parameters are {@code null}. */ private Handler<ProtonConnection> getHandlerForDisconnectHonoService(final HonoClient client, final String serviceName) { return (connection) -> { vertx.setTimer(Constants.DEFAULT_RECONNECT_INTERVAL_MILLIS, reconnect -> { LOG.info("attempting to reconnect to {}", serviceName); client.connect(getHandlerForDisconnectHonoService(client, serviceName)) .setHandler(connectAttempt -> { if (connectAttempt.succeeded()) { LOG.info("reconnected to {}", serviceName); } else { LOG.debug("cannot reconnect to {}: {}", serviceName, connectAttempt.cause().getMessage()); } }); }); }; } /** * Checks if this adapter is connected to the services it depends on. * * <em>Hono Messaging</em> and the <em>Device Registration</em> service. * * @return A future indicating the outcome of the check. The future will succeed if this adapter is currently * connected to * <ul> * <li>a Tenant service</li> * <li>a Device Registration service</li> * <li>a Credentials service</li> * <li>a service implementing the Telemetry & Event APIs</li> * </ul> * Otherwise, the future will fail. */ protected final Future<Void> isConnected() { final Future<Void> tenantCheck = Optional.ofNullable(tenantClient).map(client -> client.isConnected()) .orElse(Future.failedFuture(new IllegalStateException("Tenant service client is not set"))); final Future<Void> messagingCheck = Optional.ofNullable(messagingClient).map(client -> client.isConnected()) .orElse(Future.failedFuture(new IllegalStateException("Messaging client is not set"))); final Future<Void> registrationCheck = Optional.ofNullable(registrationClient) .map(client -> client.isConnected()).orElse(Future .failedFuture(new IllegalStateException("Device Registration service client is not set"))); final Future<Void> credentialsCheck = Optional.ofNullable(credentialsServiceClient) .map(client -> client.isConnected()) .orElse(Future.failedFuture(new IllegalStateException("Credentials service client is not set"))); return CompositeFuture.all(tenantCheck, messagingCheck, registrationCheck, credentialsCheck).compose(ok -> { return Future.succeededFuture(); }); } /** * Create a command consumer for a specific device. * * @param tenantId The tenant of the command receiver. * @param deviceId The device of the command receiver. * @param messageConsumer Handler will be called for each command to the device. * @param closeHandler Called when the peer detaches the link. * @return Result of the receiver creation. */ public final Future<MessageConsumer> createCommandConsumer(final String tenantId, final String deviceId, final BiConsumer<ProtonDelivery, Message> messageConsumer, final Handler<Void> closeHandler) { return commandConnection.createCommandConsumer(tenantId, deviceId, messageConsumer, closeHandler); } /** * Create a command response sender for a specific device. * * @param tenantId The tenant of the command receiver. * @param deviceId The device of the command receiver. * @param replyId The replyId to from the command to use for the response. * @return Result of the response sender creation. */ public final Future<CommandResponseSender> createCommandResponseSender(final String tenantId, final String deviceId, final String replyId) { return commandConnection.getOrCreateCommandResponseSender(tenantId, deviceId, replyId); } /** * Validate that the <em>reply-to</em> and the <em>correlationId</em> of a command message are correctly built. * If the validation is successful, a combined String <em>command request id</em> is returned. * @param tenantId The tenant to be used for the validation. * @param deviceId The device to be used for the validation. * @param commandMessage The message containing a command. * @return Optional An Optional with the combined String, or an empty Optional if the validation failed. * @throws NullPointerException If any of the parameters are null. */ protected final Optional<String> validateAndGenerateCommandRequestId(final String tenantId, final String deviceId, final Message commandMessage) { Objects.requireNonNull(tenantId); Objects.requireNonNull(deviceId); Objects.requireNonNull(commandMessage); // use everything after the prefix as reply-to-id final Optional<String> replyToId = getReplyToIdFromCommand(tenantId, deviceId, commandMessage); if (replyToId.isPresent()) { return Optional .of(Constants.combineTwoStrings(getCorrelationIdFromMessage(commandMessage), replyToId.get())); } else { return Optional.empty(); } } /** * Get the <em>correlationId</em> and the <em>reply-to-id</em> of a command-request-id. * * @param commandRequestId The command-request-id (typically sent by a device in a response message to a command). * @return Optional An Optional with a two element array that contains the correlationId as first element and the replyTo * address as second element. If the command-request-id is not valid, an empty Optional is returned. * @throws NullPointerException If commandRequestId is null. */ protected final Optional<String[]> getCorrelationIdAndReplyToFromCommandRequestId( final String commandRequestId) { Objects.requireNonNull(commandRequestId); return Optional.ofNullable(Constants.splitTwoStrings(commandRequestId)); } /** * Get the <em>reply-to-id</em> of a command message (being the last part of an endpoint). * * @param tenantId The tenant to be used for the validation. * @param deviceId The device to be used for the validation. * @param commandMessage The message containing a command. * @return Optional An Optional with the contained reply-to-id, or an empty Optional if no reply-to-id could be determined. * @throws NullPointerException If commandMessage is {@code null}. * @throws IllegalArgumentException If tenantId or deviceId are {@code null}. */ protected final Optional<String> getReplyToIdFromCommand(final String tenantId, final String deviceId, final Message commandMessage) { // example of commandReplyId: control/DEFAULT_TENANT/4711/33fe70fd-5a2e-4095-83db-00101bf74a07 final String commandReplyId = commandMessage.getReplyTo(); if (commandReplyId == null) { return Optional.empty(); } final String commandReplyResourceForValidation = ResourceIdentifier .from(CommandConstants.COMMAND_ENDPOINT, tenantId, deviceId).toString(); if (!commandReplyId.startsWith(commandReplyResourceForValidation)) { return Optional.empty(); } if (commandReplyId.length() == commandReplyResourceForValidation.length()) { return Optional.empty(); } else { // use everything after the prefix as reply-to-id return Optional.of(commandReplyId.substring(commandReplyResourceForValidation.length() + 1)); } } /** * Get the correlationId from a message. If the correlationId is not explicitly set, the messageId is returned instead. * * @param message The message to determine the correlationId for. * @return String The correlationId. * @throws NullPointerException If the message is {@code null}. */ protected final String getCorrelationIdFromMessage(final Message message) { return Optional.ofNullable(message.getCorrelationId()).orElse(message.getMessageId()).toString(); } /** * Gets a client for sending telemetry data for a tenant. * * @param tenantId The tenant to send the telemetry data for. * @return The client. */ protected final Future<MessageSender> getTelemetrySender(final String tenantId) { return getHonoMessagingClient().getOrCreateTelemetrySender(tenantId); } /** * Gets a client for sending events for a tenant. * * @param tenantId The tenant to send the events for. * @return The client. */ protected final Future<MessageSender> getEventSender(final String tenantId) { return getHonoMessagingClient().getOrCreateEventSender(tenantId); } /** * Gets a client for interacting with the Device Registration service. * * @param tenantId The tenant that the client is scoped to. * @return The client. */ protected final Future<RegistrationClient> getRegistrationClient(final String tenantId) { return getRegistrationServiceClient().getOrCreateRegistrationClient(tenantId); } /** * Gets an assertion for a device's registration status. * <p> * The returned JSON object contains the assertion for the device under property * {@link RegistrationConstants#FIELD_ASSERTION}. * <p> * In addition to the assertion the returned object may include <em>default</em> values for properties to set on * messages published by the device under property {@link RegistrationConstants#FIELD_DEFAULTS}. * * @param tenantId The tenant that the device belongs to. * @param deviceId The device to get the assertion for. * @param authenticatedDevice The device that has authenticated to this protocol adapter. * <p> * If not {@code null} then the authenticated device is compared to the given tenant and device ID. If * they differ in the device identifier, then the authenticated device is considered to be a gateway * acting on behalf of the device. * @return A future indicating the outcome of the operation. * <p> * The future will fail if the assertion cannot be retrieved. The cause will be a * {@link ServiceInvocationException} containing a corresponding error code. * <p> * Otherwise the future will contain the assertion. * @throws NullPointerException if tenant ID or device ID are {@code null}. */ protected final Future<JsonObject> getRegistrationAssertion(final String tenantId, final String deviceId, final Device authenticatedDevice) { Objects.requireNonNull(tenantId); Objects.requireNonNull(deviceId); final Future<String> gatewayId = getGatewayId(tenantId, deviceId, authenticatedDevice); return gatewayId.compose(gwId -> getRegistrationClient(tenantId)) .compose(client -> client.assertRegistration(deviceId, gatewayId.result())); } private Future<String> getGatewayId(final String tenantId, final String deviceId, final Device authenticatedDevice) { final Future<String> result = Future.future(); if (authenticatedDevice == null) { result.complete(null); } else if (tenantId.equals(authenticatedDevice.getTenantId())) { if (deviceId.equals(authenticatedDevice.getDeviceId())) { result.complete(null); } else { result.complete(authenticatedDevice.getDeviceId()); } } else { result.fail(new ClientErrorException(HttpURLConnection.HTTP_FORBIDDEN, "cannot publish data for device of other tenant")); } return result; } /** * Gets configuration information for a tenant. * <p> * The returned JSON object contains information as defined by Hono's * <a href="https://www.eclipse.org/hono/api/tenant-api/#response-payload">Tenant API</a>. * * @param tenantId The tenant to retrieve information for. * @return A future indicating the outcome of the operation. * <p> * The future will fail if the information cannot be retrieved. The cause will be a * {@link ServiceInvocationException} containing a corresponding error code. * <p> * Otherwise the future will contain the configuration information. * @throws NullPointerException if tenant ID is {@code null}. */ protected final Future<TenantObject> getTenantConfiguration(final String tenantId) { Objects.requireNonNull(tenantId); return getTenantClient().compose(client -> client.get(tenantId)); } /** * Adds message properties based on a device's registration information. * <p> * This methods simply invokes {@link #addProperties(Message, JsonObject, boolean)} with * with {@code true} as the value for the regAssertionRequired parameter. * * @param message The message to set the properties on. * @param registrationInfo The values to set. * @throws NullPointerException if any of the parameters is {@code null}. */ protected final void addProperties(final Message message, final JsonObject registrationInfo) { addProperties(message, registrationInfo, true); } /** * Adds message properties based on a device's registration information. * <p> * Sets the following properties on the message: * <ul> * <li>Adds the registration assertion found in the {@link RegistrationConstants#FIELD_ASSERTION} property of the * given registration information (if required by downstream peer).</li> * <li>Adds {@linkplain #getTypeName() the adapter's name} to the message in application property * {@link MessageHelper#APP_PROPERTY_ORIG_ADAPTER}</li> * <li>Augments the message with missing (application) properties corresponding to the * {@link RegistrationConstants#FIELD_DEFAULTS} contained in the registration information.</li> * <li>Adds JMS vendor properties if configuration property <em>jmsVendorPropertiesEnabled</em> is set to * {@code true}.</li> * </ul> * * @param message The message to set the properties on. * @param registrationInfo The values to set. * @param regAssertionRequired {@code true} if the downstream peer requires the registration assertion to * be included in the message. * @throws NullPointerException if any of the parameters is {@code null}. */ protected final void addProperties(final Message message, final JsonObject registrationInfo, final boolean regAssertionRequired) { if (regAssertionRequired) { final String registrationAssertion = registrationInfo.getString(RegistrationConstants.FIELD_ASSERTION); MessageHelper.addRegistrationAssertion(message, registrationAssertion); } MessageHelper.addProperty(message, MessageHelper.APP_PROPERTY_ORIG_ADAPTER, getTypeName()); if (getConfig().isDefaultsEnabled()) { final JsonObject defaults = registrationInfo.getJsonObject(RegistrationConstants.FIELD_DEFAULTS); if (defaults != null) { addDefaults(message, defaults); } } if (Strings.isNullOrEmpty(message.getContentType())) { // set default content type if none has been specified when creating the // message nor a default content type is available message.setContentType(CONTENT_TYPE_OCTET_STREAM); } if (getConfig().isJmsVendorPropsEnabled()) { MessageHelper.addJmsVendorProperties(message); } } private void addDefaults(final Message message, final JsonObject defaults) { defaults.forEach(prop -> { switch (prop.getKey()) { case MessageHelper.SYS_PROPERTY_CONTENT_TYPE: if (Strings.isNullOrEmpty(message.getContentType()) && String.class.isInstance(prop.getValue())) { // set to default type registered for device or fall back to default content type message.setContentType((String) prop.getValue()); } break; case MessageHelper.SYS_PROPERTY_CONTENT_ENCODING: if (Strings.isNullOrEmpty(message.getContentEncoding()) && String.class.isInstance(prop.getValue())) { message.setContentEncoding((String) prop.getValue()); } break; case MessageHelper.SYS_PROPERTY_ABSOLUTE_EXPIRY_TIME: case MessageHelper.SYS_PROPERTY_CORRELATION_ID: case MessageHelper.SYS_PROPERTY_CREATION_TIME: case MessageHelper.SYS_PROPERTY_GROUP_ID: case MessageHelper.SYS_PROPERTY_GROUP_SEQUENCE: case MessageHelper.SYS_PROPERTY_MESSAGE_ID: case MessageHelper.SYS_PROPERTY_REPLY_TO: case MessageHelper.SYS_PROPERTY_REPLY_TO_GROUP_ID: case MessageHelper.SYS_PROPERTY_SUBJECT: case MessageHelper.SYS_PROPERTY_TO: case MessageHelper.SYS_PROPERTY_USER_ID: // these standard properties cannot be set using defaults LOG.debug("ignoring default property [{}] registered for device", prop.getKey()); break; default: // add all other defaults as application properties MessageHelper.addProperty(message, prop.getKey(), prop.getValue()); } }); } /** * Registers a check that succeeds if this component is connected to Hono Messaging, the Tenant Service, the Device * Registration and the Credentials service. */ @Override public void registerReadinessChecks(final HealthCheckHandler handler) { handler.register("connection-to-services", status -> { isConnected().map(connected -> { status.tryComplete(Status.OK()); return null; }).otherwise(t -> { status.tryComplete(Status.KO()); return null; }); }); } /** * Does not register any checks. */ @Override public void registerLivenessChecks(final HealthCheckHandler handler) { } /** * Creates a new AMQP 1.0 message. * <p> * Subclasses are encouraged to use this method for creating {@code Message} instances to be sent downstream in * order to have the following properties set on the message automatically: * <ul> * <li><em>to</em> will be set to the address consisting of the target's endpoint and tenant</li> * <li><em>content-type</em> will be set to content type</li> * <li><em>creation-time</em> will be set to the current number of milliseconds since 1970-01-01T00:00:00Z</li> * <li>application property <em>device_id</em> will be set to the target's resourceId property</li> * <li>application property <em>orig_address</em> will be set to the given publish address</li> * <li>application property <em>ttd</em> will be set to the given time til disconnect</li> * <li>additional properties set by {@link #addProperties(Message, JsonObject, boolean)}</li> * </ul> * This method also sets the message's payload as an AMQP <em>Data</em> section. * * @param target The resource that the message is targeted at. * @param regAssertionRequired {@code true} if the downstream peer requires the registration assertion to * be included in the message. * @param publishAddress The address that the message has been published to originally by the device. (may be * {@code null}). * <p> * This address will be transport protocol specific, e.g. an HTTP based adapter will probably use URIs * here whereas an MQTT based adapter might use the MQTT message's topic. * @param contentType The content type describing the message's payload (may be {@code null}). * @param payload The message payload. * @param registrationInfo The device's registration information as retrieved by the <em>Device Registration</em> * service's <em>assert Device Registration</em> operation. * @param timeUntilDisconnect The number of milliseconds until the device that has published the message * will disconnect from the protocol adapter (may be {@code null}). * @return The message. * @throws NullPointerException if target or registration info are {@code null}. */ protected final Message newMessage(final ResourceIdentifier target, final boolean regAssertionRequired, final String publishAddress, final String contentType, final Buffer payload, final JsonObject registrationInfo, final Integer timeUntilDisconnect) { Objects.requireNonNull(target); Objects.requireNonNull(registrationInfo); final Message msg = ProtonHelper.message(); msg.setAddress(target.getBasePath()); MessageHelper.addDeviceId(msg, target.getResourceId()); if (!regAssertionRequired) { // this adapter is not connected to Hono Messaging // so we need to add the annotations for tenant and // device ID MessageHelper.annotate(msg, target); } if (publishAddress != null) { MessageHelper.addProperty(msg, MessageHelper.APP_PROPERTY_ORIG_ADDRESS, publishAddress); } if (contentType != null) { msg.setContentType(contentType); } if (payload != null) { msg.setBody(new Data(new Binary(payload.getBytes()))); } if (timeUntilDisconnect != null) { MessageHelper.addTimeUntilDisconnect(msg, timeUntilDisconnect); } MessageHelper.setCreationTime(msg); addProperties(msg, registrationInfo, regAssertionRequired); return msg; } /** * Trigger the creation of a <em>connected</em> event. * @param remoteId The remote ID. * @param authenticatedDevice The (optional) authenticated device. * @return A failed future if an event producer is set but the event * could not be published. Otherwise, a succeeded event. * @see ConnectionEventProducer * @see ConnectionEventProducer#connected(String, String, Device, JsonObject) */ protected Future<?> sendConnectedEvent(final String remoteId, final Device authenticatedDevice) { if (this.connectionEventProducer != null) { return this.connectionEventProducer.connected(remoteId, getTypeName(), authenticatedDevice, null); } else { return Future.succeededFuture(); } } /** * Trigger the creation of a <em>disconnected</em> event. * * @param remoteId The remote ID. * @param authenticatedDevice The (optional) authenticated device. * @return A failed future if an event producer is set but the event * could not be published. Otherwise, a succeeded event. * @see ConnectionEventProducer * @see ConnectionEventProducer#disconnected(String, String, Device, JsonObject) */ protected Future<?> sendDisconnectedEvent(final String remoteId, final Device authenticatedDevice) { if (this.connectionEventProducer != null) { return this.connectionEventProducer.disconnected(remoteId, getTypeName(), authenticatedDevice, null); } else { return Future.succeededFuture(); } } /** * Check if the payload a protocol adapter received matches the indicated Content-Type. * * @param contentType The Content-Type of the payload, as received by the protocol adapter. * @param payload The payload received by the protocol adapter. * @return {@code true} if the payload matches the Content-Type, {@code false} otherwise. */ protected boolean isPayloadOfIndicatedType(final Buffer payload, final String contentType) { if (payload == null || payload.length() == 0) { return EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION.equals(contentType); } else { return !EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION.equals(contentType); } } /** * This method may be set as the close handler of the {@link org.eclipse.hono.service.command.CommandConsumer}. * <p> * The implementation only logs that the link was closed and does not try to reopen it. Any other functionality must be * implemented by overwriting the method in a subclass. * * @param tenant The tenant of the device for that a command may be received. * @param deviceId The id of the device for that a command may be received. * @param commandMessageConsumer The Handler that will be called for each command to the device. */ protected void onCloseCommandConsumer(final String tenant, final String deviceId, final BiConsumer<ProtonDelivery, Message> commandMessageConsumer) { LOG.debug( "Command consumer was closed for [tenantId: {}, deviceId: {}] - no command will be received for this request anymore.", tenant, deviceId); } }