Java tutorial
/** * Copyright (c) 2017, 2018 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 1.0 which is available at * https://www.eclipse.org/legal/epl-v10.html * * SPDX-License-Identifier: EPL-1.0 */ package org.eclipse.hono.vertx.example.base; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.proton.ProtonConnection; import org.apache.qpid.proton.amqp.messaging.Data; import org.apache.qpid.proton.message.Message; import org.eclipse.hono.client.CommandClient; import org.eclipse.hono.client.HonoClient; import org.eclipse.hono.client.MessageConsumer; import org.eclipse.hono.client.impl.HonoClientImpl; import org.eclipse.hono.config.ClientConfigProperties; import org.eclipse.hono.util.MessageTap; import org.eclipse.hono.util.MessageHelper; import org.eclipse.hono.util.TimeUntilDisconnectNotification; import io.vertx.core.Future; import io.vertx.core.Vertx; /** * Example base class for consuming data from Hono. * <p> * This class implements all necessary code to get Hono's messaging consumer client running. * The code consumes data until it receives * any input on it's console (which finishes it and closes vertx). * <p> * By default, this class consumes telemetry data. This can be changed to event data by setting * {@link HonoConsumerBase#setMode(MODE)} to {@link MODE#EVENT}. */ public class HonoConsumerBase { public static final String HONO_CLIENT_USER = "consumer@HONO"; public static final String HONO_CLIENT_PASSWORD = "verysecret"; protected final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 1000; private final Vertx vertx = Vertx.vertx(); private final HonoClient honoClient; /** * Enum to define from which source messages are consumed. * <p> * Instead of using a {@code Boolean} this enum allows for more modes in future extensions (e.g. to receive messages * from both channels). */ public enum MODE { TELEMETRY, EVENT } private MODE mode = MODE.TELEMETRY; /** * The consumer needs one connection to the AMQP 1.0 messaging network from which it can consume data. * <p> * The client for receiving data is instantiated here. * <p> * NB: if you want to integrate this code with your own software, it might be necessary to copy the truststore to * your project as well and adopt the file path. */ public HonoConsumerBase() { final ClientConfigProperties props = new ClientConfigProperties(); props.setHost(HonoExampleConstants.HONO_AMQP_CONSUMER_HOST); props.setPort(HonoExampleConstants.HONO_AMQP_CONSUMER_PORT); props.setUsername(HONO_CLIENT_USER); props.setPassword(HONO_CLIENT_PASSWORD); props.setTrustStorePath("target/config/hono-demo-certs-jar/trusted-certs.pem"); props.setHostnameVerificationRequired(false); honoClient = new HonoClientImpl(vertx, props); } /** * Initiate the connection and set the message handling method to treat data that is received. * * @throws Exception Thrown if the latch is interrupted during waiting or if the read from System.in throws an IOException. */ protected void consumeData() throws Exception { final CountDownLatch latch = new CountDownLatch(1); final Future<MessageConsumer> consumerFuture = Future.future(); consumerFuture.setHandler(result -> { if (!result.succeeded()) { System.err.println("honoClient could not create telemetry consumer for " + HonoExampleConstants.HONO_AMQP_CONSUMER_HOST + ":" + HonoExampleConstants.HONO_AMQP_CONSUMER_PORT + " : " + result.cause()); } latch.countDown(); }); honoClient.connect(this::onDisconnect).compose(connectedClient -> createConsumer()) .setHandler(consumerFuture.completer()); latch.await(); if (consumerFuture.succeeded()) { System.in.read(); } vertx.close(); } /** * Create the message consumer that handles the downstream messages and invokes the notification callback * {@link #handleCommandReadinessNotification(TimeUntilDisconnectNotification)} if the message indicates that it * stays connected for a specified time. Supported are telemetry or event MessageConsumer. * * @return Future A succeeded future that contains the MessageConsumer if the creation was successful, a failed * Future otherwise. */ private Future<MessageConsumer> createConsumer() { switch (mode) { case EVENT: // create the eventHandler by using the helper functionality for demultiplexing messages to callbacks final Consumer<Message> eventHandler = MessageTap.getConsumer(this::handleEventMessage, this::handleCommandReadinessNotification); return honoClient.createEventConsumer(HonoExampleConstants.TENANT_ID, eventHandler, closeHook -> System.err.println("remotely detached consumer link")); case TELEMETRY: // create the telemetryHandler by using the helper functionality for demultiplexing messages to callbacks final Consumer<Message> telemetryHandler = MessageTap.getConsumer(this::handleTelemetryMessage, this::handleCommandReadinessNotification); return honoClient.createTelemetryConsumer(HonoExampleConstants.TENANT_ID, telemetryHandler, closeHook -> System.err.println("remotely detached consumer link")); default: return Future.failedFuture("No valid mode set for consumer."); } } /** * Method to act as a disconnect handler. If called, it tries to reconnect to Hono by creating the MessageConsumer * again and continue to wait for incoming downstream messages. * <p> * The reconnection attempt is delayed by {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} milliseconds and set this method * as disconnect handler again (for potential future disconnect handling). * * @param con The ProtonConnection for that the connection was lost. */ private void onDisconnect(final ProtonConnection con) { // give Vert.x some time to clean up NetClient vertx.setTimer(DEFAULT_CONNECT_TIMEOUT_MILLIS, reconnect -> { System.out.println("attempting to re-connect to Hono ..."); honoClient.connect(this::onDisconnect).compose(connectedClient -> createConsumer()) .map(messageConsumer -> { System.out.println("Reconnected to Hono."); return null; }); }); } private void printMessage(final String tenantId, final Message msg, final String messageType) { final String content = ((Data) msg.getBody()).getValue().toString(); final String deviceId = MessageHelper.getDeviceId(msg); final StringBuilder sb = new StringBuilder("received ").append(messageType).append(" [tenant: ") .append(tenantId).append(", device: ").append(deviceId).append(", content-type: ") .append(msg.getContentType()).append(" ]: ").append(content); System.out.println(sb.toString()); } /** * Handler method for a <em>device ready for command</em> notification (by an explicit event or contained implicit in * another message). * <p> * The code creates a simple command in JSON * * @param notification The notification containing the tenantId, deviceId and the Instant (that * defines until when this notification is valid). See {@link TimeUntilDisconnectNotification}. */ private void handleCommandReadinessNotification(final TimeUntilDisconnectNotification notification) { System.out.println(String.format("Device is ready to receive a command : <%s>.", notification.toString())); final String tenantId = notification.getTenantId(); final String deviceId = notification.getDeviceId(); honoClient.getOrCreateCommandClient(tenantId, deviceId).map(commandClient -> { final JsonObject jsonCmd = new JsonObject().put("brightness", (int) (Math.random() * 100)); final Buffer commandBuffer = Buffer.buffer(jsonCmd.encodePrettily()); // let the commandClient timeout when the notification expires commandClient.setRequestTimeout(notification.getMillisecondsUntilExpiry()); // send the command upstream to the device sendCommandToAdapter(commandClient, commandBuffer); return commandClient; }).otherwise(t -> { System.err.println(String.format("Could not create command client : %s", t.getMessage())); return null; }); } private void sendCommandToAdapter(final CommandClient commandClient, final Buffer commandBuffer) { commandClient.sendCommand("setBrightness", commandBuffer).map(result -> { System.out.println(String.format("Successfully sent command and received response: %s", Optional.ofNullable(result).orElse(Buffer.buffer()).toString())); commandClient.close(v -> { }); return result; }).otherwise(t -> { System.out.println( String.format("Could not send command or did not receive a response : %s", t.getMessage())); commandClient.close(v -> { }); return (Buffer) null; }); } /** * Handler method for a Message from Hono that was received as telemetry data. * <p> * The tenant, the device, the payload, the content-type, the creation-time and the application properties will be printed to stdout. * * @param msg The message that was received. */ private void handleTelemetryMessage(final Message msg) { printMessage(HonoExampleConstants.TENANT_ID, msg, "telemetry"); } /** * Handler method for a Message from Hono that was received as event data. * <p> * The tenant, the device, the payload, the content-type, the creation-time and the application properties will be printed to stdout. * * @param msg The message that was received. */ private void handleEventMessage(final Message msg) { printMessage(HonoExampleConstants.TENANT_ID, msg, "event"); } /** * Gets the {@link MODE} of this consumer: event data or telemetry data. * * @return The mode of this consumer. */ public MODE getMode() { return mode; } /** * Sets the consumer to consume event data or telemetry data. * * @param value The new {@link MODE} value. */ public void setMode(final MODE value) { this.mode = value; } }