org.eclipse.hono.service.amqp.BaseEndpoint.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.service.amqp.BaseEndpoint.java

Source

/**
 * Copyright (c) 2016, 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.amqp;

import static java.net.HttpURLConnection.HTTP_OK;
import static org.eclipse.hono.util.MessageHelper.encodeIdToJson;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import io.vertx.core.json.JsonObject;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.message.Message;
import org.eclipse.hono.config.ServiceConfigProperties;
import org.eclipse.hono.util.MessageHelper;
import org.eclipse.hono.util.ResourceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.proton.ProtonHelper;
import io.vertx.proton.ProtonReceiver;
import io.vertx.proton.ProtonSender;

/**
 * Base class for Hono endpoints.
 * 
 * @param <T> The type of configuration properties this endpoint understands.
 */
public abstract class BaseEndpoint<T extends ServiceConfigProperties> implements Endpoint {

    protected final Vertx vertx;
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    protected T config = (T) new ServiceConfigProperties();
    private static final String STATUS_OK = String.valueOf(HTTP_OK);
    private Map<String, UpstreamReceiverImpl> activeClients = new HashMap<>();

    /**
     * Creates an endpoint for a Vertx instance.
     * 
     * @param vertx The Vertx instance to use.
     * @throws NullPointerException if vertx is {@code null};
     */
    protected BaseEndpoint(final Vertx vertx) {
        this.vertx = Objects.requireNonNull(vertx);
    }

    /**
     * Sets configuration properties.
     * 
     * @param props The properties.
     * @throws NullPointerException if props is {@code null}.
     */
    @Autowired(required = false)
    public final void setConfiguration(final T props) {
        this.config = Objects.requireNonNull(props);
    }

    @Override
    public final void start(final Future<Void> startFuture) {
        if (vertx == null) {
            startFuture.fail("Vert.x instance must be set");
        } else {
            doStart(startFuture);
        }
    }

    /**
     * Subclasses should override this method to create required resources
     * during startup.
     * <p>
     * This implementation always completes the start future.
     * 
     * @param startFuture Completes if startup succeeded.
     */
    protected void doStart(final Future<Void> startFuture) {
        startFuture.complete();
    }

    @Override
    public final void stop(final Future<Void> stopFuture) {
        doStop(stopFuture);
    }

    /**
     * Subclasses should override this method to release resources
     * during shutdown.
     * <p>
     * This implementation always completes the stop future.
     * 
     * @param stopFuture Completes if shutdown succeeded.
     */
    protected void doStop(final Future<Void> stopFuture) {
        stopFuture.complete();
    }

    /**
     * Closes the link to an upstream client and removes all state kept for it.
     * 
     * @param client The client to detach.
     */
    protected final void onLinkDetach(final UpstreamReceiver client) {
        onLinkDetach(client, null);
    }

    /**
     * Closes the link to an upstream client and removes all state kept for it.
     * 
     * @param client The client to detach.
     * @param error The error condition to convey to the client when closing the link.
     */
    protected final void onLinkDetach(final UpstreamReceiver client, final ErrorCondition error) {
        if (error == null) {
            logger.debug("closing receiver for client [{}]", client.getLinkId());
        } else {
            logger.debug("closing receiver for client [{}]: {}", client.getLinkId(), error.getDescription());
        }
        client.close(error);
        removeClientLink(client.getLinkId());
    }

    /**
     * Closes the link to a proton based receiver client.
     *
     * @param client The client to detach.
     */
    protected void onLinkDetach(final ProtonReceiver client) {
        onLinkDetach(client, null);
    }

    /**
     * Closes the link to a proton based receiver client.
     *
     * @param client The client to detach.
     * @param error The error condition to convey to the client when closing the link.
     */
    protected void onLinkDetach(final ProtonReceiver client, final ErrorCondition error) {
        if (error == null) {
            logger.debug("closing proton receiver for client [{}]", MessageHelper.getLinkName(client));
        } else {
            logger.debug("closing proton receiver for client [{}]: {}", MessageHelper.getLinkName(client),
                    error.getDescription());
        }
        client.close();
    }

    /**
     * Registers a link with an upstream client.
     * 
     * @param link The link to register.
     */
    protected final void registerClientLink(final UpstreamReceiverImpl link) {
        activeClients.put(link.getLinkId(), link);
    }

    /**
     * Looks up a link with an upstream client based on its identifier.
     * 
     * @param linkId The identifier of the client.
     * @return The link object representing the client or {@code null} if no link with the given identifier exists.
     * @throws NullPointerException if the link id is {@code null}.
     */
    protected final UpstreamReceiver getClientLink(final String linkId) {
        return activeClients.get(Objects.requireNonNull(linkId));
    }

    /**
     * Deregisters a link with an upstream client.
     * 
     * @param linkId The identifier of the link to deregister.
     */
    protected final void removeClientLink(final String linkId) {
        activeClients.remove(linkId);
    }

    @Override
    public void onLinkAttach(final ProtonReceiver receiver, final ResourceIdentifier targetResource) {
        logger.info("Endpoint [{}] does not support data upload, closing link.", getName());
        receiver.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be written to"));
        receiver.close();
    }

    @Override
    public void onLinkAttach(final ProtonSender sender, final ResourceIdentifier targetResource) {
        logger.info("Endpoint [{}] does not support data retrieval, closing link.", getName());
        sender.setCondition(ProtonHelper.condition(AmqpError.NOT_IMPLEMENTED, "resource cannot be read from"));
        sender.close();
    }

    protected final void addHeadersToResponse(final Message request, final JsonObject message) {
        final boolean isApplicationCorrelationId = MessageHelper.getXOptAppCorrelationId(request);
        logger.debug("registration request [{}] uses application specific correlation ID: {}",
                request.getMessageId(), isApplicationCorrelationId);
        if (isApplicationCorrelationId) {
            message.put(MessageHelper.ANNOTATION_X_OPT_APP_CORRELATION_ID, isApplicationCorrelationId);
        }
        final JsonObject correlationIdJson = encodeIdToJson(getCorrelationId(request));
        message.put(MessageHelper.SYS_PROPERTY_CORRELATION_ID, correlationIdJson);
    }

    /**
     * @param request the request message from which to extract the correlationId
     * @return The ID used to correlate the given request message. This can either be the provided correlationId
     * (Correlation ID Pattern) or the messageId of the request (Message ID Pattern, if no correlationId is provided).
     */
    protected final Object getCorrelationId(final Message request) {
        /* if a correlationId is provided, we use it to correlate the response -> Correlation ID Pattern */
        if (request.getCorrelationId() != null) {
            return request.getCorrelationId();
        } else {
            /* otherwise we use the message id -> Message ID Pattern */
            return request.getMessageId();
        }
    }

}