org.eclipse.hono.service.credentials.CredentialsEndpoint.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.service.credentials.CredentialsEndpoint.java

Source

/**
 * Copyright (c) 2017 Bosch Software Innovations GmbH.
 *
 * 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
 */
package org.eclipse.hono.service.credentials;

import io.vertx.core.Vertx;
import io.vertx.core.eventbus.MessageConsumer;
import io.vertx.core.json.JsonObject;
import io.vertx.proton.ProtonQoS;
import io.vertx.proton.ProtonReceiver;
import io.vertx.proton.ProtonSender;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.codec.DecodeException;
import org.apache.qpid.proton.message.Message;
import org.eclipse.hono.service.amqp.BaseEndpoint;
import org.eclipse.hono.util.CredentialsConstants;
import org.eclipse.hono.util.MessageHelper;
import org.eclipse.hono.util.ResourceIdentifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.net.HttpURLConnection;
import java.util.Objects;

import static io.vertx.proton.ProtonHelper.condition;
import static org.eclipse.hono.util.CredentialsConstants.EVENT_BUS_ADDRESS_CREDENTIALS_IN;

/**
 * An {@code Endpoint} for managing device credential information.
 * <p>
 * This endpoint implements Hono's <a href="https://www.eclipse.org/hono/api/Credentials-API/">Credentials API</a>.
 * It receives AMQP 1.0 messages representing requests and sends them to an address on the vertx
 * event bus for processing. The outcome is then returned to the peer in a response message.
 */
@Component
@Qualifier("credentials")
public final class CredentialsEndpoint extends BaseEndpoint {

    /**
     * Creates a new credentials endpoint for a vertx instance.
     *
     * @param vertx The vertx instance to use.
     */
    @Autowired
    public CredentialsEndpoint(final Vertx vertx) {
        super(Objects.requireNonNull(vertx));
    }

    @Override
    public String getName() {
        return CredentialsConstants.CREDENTIALS_ENDPOINT;
    }

    @Override
    public void onLinkAttach(final ProtonReceiver receiver, final ResourceIdentifier targetAddress) {
        if (ProtonQoS.AT_MOST_ONCE.equals(receiver.getRemoteQoS())) {
            logger.debug(
                    "client wants to use AT MOST ONCE delivery mode for credentials endpoint, this is not supported.");
            receiver.setCondition(
                    condition(AmqpError.PRECONDITION_FAILED.toString(), "endpoint requires AT_LEAST_ONCE QoS"));
            receiver.close();
        } else {

            logger.debug("establishing link for receiving credentials messages from client [{}]",
                    MessageHelper.getLinkName(receiver));
            receiver.setQoS(ProtonQoS.AT_LEAST_ONCE).setAutoAccept(true) // settle received messages if the handler succeeds
                    .setPrefetch(20).handler((delivery, message) -> {
                        if (CredentialsMessageFilter.verify(targetAddress, message)) {
                            try {
                                processRequest(message);
                            } catch (DecodeException e) {
                                MessageHelper.rejected(delivery, AmqpError.DECODE_ERROR.toString(),
                                        "malformed payload");
                            }
                        } else {
                            MessageHelper.rejected(delivery, AmqpError.DECODE_ERROR.toString(),
                                    "malformed credentials message");
                            // we close the link if the client sends a message that does not comply with the API spec
                            onLinkDetach(receiver,
                                    condition(AmqpError.DECODE_ERROR.toString(), "invalid message received"));
                        }
                    }).closeHandler(clientDetached -> onLinkDetach(clientDetached.result())).open();
        }
    }

    @Override
    public void onLinkAttach(final ProtonSender sender, final ResourceIdentifier targetResource) {
        if (targetResource.getResourceId() == null) {
            logger.debug(
                    "link target provided in client's link ATTACH must not be null, but must match pattern \"credentials/<tenant>/<reply-address>\" instead");
            sender.setCondition(condition(AmqpError.INVALID_FIELD.toString(),
                    "link target must not be null but must have the following format credentials/<tenant>/<reply-address>"));
            sender.close();
        } else {
            logger.debug("establishing sender link with client [{}]", MessageHelper.getLinkName(sender));
            final MessageConsumer<JsonObject> replyConsumer = vertx.eventBus().consumer(targetResource.toString(),
                    message -> {
                        // TODO check for correct session here...?
                        logger.trace("forwarding reply to client: {}", message.body());
                        final Message amqpReply = CredentialsConstants.getAmqpReply(message);
                        sender.send(amqpReply);
                    });

            sender.closeHandler(senderClosed -> {
                replyConsumer.unregister();
                senderClosed.result().close();
                final String linkName = MessageHelper.getLinkName(sender);
                logger.debug("receiver closed link [{}], removing associated event bus consumer [{}]", linkName,
                        replyConsumer.address());
            });

            sender.setQoS(ProtonQoS.AT_LEAST_ONCE).open();
        }
    }

    private void processRequest(final Message msg) {

        final JsonObject credentialsMsg = CredentialsConstants.getCredentialsMsg(msg);

        vertx.eventBus().send(EVENT_BUS_ADDRESS_CREDENTIALS_IN, credentialsMsg, result -> {
            JsonObject response = null;
            if (result.succeeded()) {
                // TODO check for correct session here...?
                response = (JsonObject) result.result().body();
            } else {
                logger.debug("failed to process credentials request [msg ID: {}] due to {}", msg.getMessageId(),
                        result.cause());
                // we need to inform client about failure
                response = CredentialsConstants.getReply(HttpURLConnection.HTTP_INTERNAL_ERROR,
                        MessageHelper.getTenantIdAnnotation(msg), null, null);
            }
            addHeadersToResponse(msg, response);
            vertx.eventBus().send(msg.getReplyTo(), response);
        });
    }
}