org.eclipse.hono.service.registration.RegistrationHttpEndpoint.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.service.registration.RegistrationHttpEndpoint.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.registration;

import static org.eclipse.hono.util.RequestResponseApiConstants.FIELD_DEVICE_ID;

import java.net.HttpURLConnection;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.BiConsumer;

import org.eclipse.hono.config.ServiceConfigProperties;
import org.eclipse.hono.service.http.AbstractHttpEndpoint;
import org.eclipse.hono.service.http.HttpEndpointUtils;
import org.eclipse.hono.util.RegistrationConstants;
import org.springframework.beans.factory.annotation.Autowired;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

/**
 * An {@code HttpEndpoint} for managing device registration information.
 * <p>
 * This endpoint implements Hono's <a href="https://www.eclipse.org/hono/api/Device-Registration-API/">Device Registration API</a>.
 * It receives HTTP requests representing operation invocations and sends them to an address on the vertx
 * event bus for processing. The outcome is then returned to the peer in the HTTP response.
 */
public final class RegistrationHttpEndpoint extends AbstractHttpEndpoint<ServiceConfigProperties> {

    // path parameters for capturing parts of the URI path
    private static final String PARAM_TENANT_ID = "tenant_id";
    private static final String PARAM_DEVICE_ID = "device_id";

    /**
     * Creates an endpoint for a Vertx instance.
     * 
     * @param vertx The Vertx instance to use.
     * @throws NullPointerException if vertx is {@code null};
     */
    @Autowired
    public RegistrationHttpEndpoint(final Vertx vertx) {
        super(Objects.requireNonNull(vertx));
    }

    private static JsonObject getPayloadForParams(final HttpServerRequest request) {
        JsonObject payload = new JsonObject();
        for (Entry<String, String> param : request.params()) {
            if (PARAM_DEVICE_ID.equalsIgnoreCase(param.getKey())) {
                // add device param captured from URI path - use same name as in JSON structures for that
                payload.put(FIELD_DEVICE_ID, param.getValue());
            } else if (!PARAM_TENANT_ID.equalsIgnoreCase(param.getKey())) { // filter out tenant param captured from URI path
                payload.put(param.getKey(), param.getValue());
            }
        }
        return payload;
    }

    @Override
    public String getName() {
        return RegistrationConstants.REGISTRATION_ENDPOINT;
    }

    @Override
    public void addRoutes(final Router router) {

        final String pathWithTenant = String.format("/%s/:%s", RegistrationConstants.REGISTRATION_ENDPOINT,
                PARAM_TENANT_ID);
        // ADD device registration
        router.route(HttpMethod.POST, pathWithTenant).consumes(HttpEndpointUtils.CONTENT_TYPE_JSON)
                .handler(this::doRegisterDeviceJson);
        router.route(HttpMethod.POST, pathWithTenant)
                .consumes(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .handler(this::doRegisterDeviceForm);
        router.route(HttpMethod.POST, pathWithTenant).handler(
                ctx -> HttpEndpointUtils.badRequest(ctx.response(), "missing or unsupported content-type"));

        final String pathWithTenantAndDeviceId = String.format("/%s/:%s/:%s",
                RegistrationConstants.REGISTRATION_ENDPOINT, PARAM_TENANT_ID, PARAM_DEVICE_ID);
        // GET device registration
        router.route(HttpMethod.GET, pathWithTenantAndDeviceId).handler(this::doGetDevice);

        // UPDATE existing registration
        router.route(HttpMethod.PUT, pathWithTenantAndDeviceId).consumes(HttpEndpointUtils.CONTENT_TYPE_JSON)
                .handler(this::doUpdateRegistrationJson);
        router.route(HttpMethod.PUT, pathWithTenantAndDeviceId)
                .consumes(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .handler(this::doUpdateRegistrationForm);
        router.route(HttpMethod.PUT, pathWithTenantAndDeviceId).handler(
                ctx -> HttpEndpointUtils.badRequest(ctx.response(), "missing or unsupported content-type"));

        // REMOVE registration
        router.route(HttpMethod.DELETE, pathWithTenantAndDeviceId).handler(this::doUnregisterDevice);
    }

    private static String getTenantParam(final RoutingContext ctx) {
        return ctx.request().getParam(PARAM_TENANT_ID);
    }

    private static String getDeviceIdParam(final RoutingContext ctx) {
        return ctx.request().getParam(PARAM_DEVICE_ID);
    }

    private void doGetDevice(final RoutingContext ctx) {

        final String deviceId = getDeviceIdParam(ctx);
        final String tenantId = getTenantParam(ctx);
        final HttpServerResponse response = ctx.response();
        final JsonObject requestMsg = RegistrationConstants
                .getServiceRequestAsJson(RegistrationConstants.ACTION_GET, tenantId, deviceId);

        doRegistrationAction(ctx, requestMsg, (status, registrationResult) -> {
            response.setStatusCode(status);
            switch (status) {
            case HttpURLConnection.HTTP_OK:
                final String msg = registrationResult.getJsonObject("payload").encodePrettily();
                response.putHeader(HttpHeaders.CONTENT_TYPE, HttpEndpointUtils.CONTENT_TYPE_JSON_UFT8)
                        .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
            default:
                response.end();
            }
        });
    }

    private void doRegisterDeviceJson(final RoutingContext ctx) {
        try {
            JsonObject payload = null;
            if (ctx.getBody().length() > 0) {
                payload = ctx.getBodyAsJson();
            }
            registerDevice(ctx, payload);
        } catch (DecodeException e) {
            HttpEndpointUtils.badRequest(ctx.response(), "body does not contain a valid JSON object");
        }
    }

    private void doRegisterDeviceForm(final RoutingContext ctx) {

        registerDevice(ctx, getPayloadForParams(ctx.request()));
    }

    private void registerDevice(final RoutingContext ctx, final JsonObject payload) {

        if (payload == null) {
            HttpEndpointUtils.badRequest(ctx.response(), "missing body");
        } else {
            Object deviceId = payload.remove(FIELD_DEVICE_ID);
            if (deviceId == null) {
                HttpEndpointUtils.badRequest(ctx.response(),
                        String.format("'%s' param is required", FIELD_DEVICE_ID));
            } else if (!(deviceId instanceof String)) {
                HttpEndpointUtils.badRequest(ctx.response(),
                        String.format("'%s' must be a string", FIELD_DEVICE_ID));
            } else {
                final String tenantId = getTenantParam(ctx);
                logger.debug("registering data for device [tenant: {}, device: {}, payload: {}]", tenantId,
                        deviceId, payload);
                final HttpServerResponse response = ctx.response();
                final JsonObject requestMsg = RegistrationConstants.getServiceRequestAsJson(
                        RegistrationConstants.ACTION_REGISTER, tenantId, (String) deviceId, payload);
                doRegistrationAction(ctx, requestMsg, (status, registrationResult) -> {
                    response.setStatusCode(status);
                    switch (status) {
                    case HttpURLConnection.HTTP_CREATED:
                        response.putHeader(HttpHeaders.LOCATION, String.format("/%s/%s/%s",
                                RegistrationConstants.REGISTRATION_ENDPOINT, tenantId, deviceId));
                    default:
                        response.end();
                    }
                });
            }
        }
    }

    private void doUpdateRegistrationJson(final RoutingContext ctx) {

        try {
            JsonObject payload = null;
            if (ctx.getBody().length() > 0) {
                payload = ctx.getBodyAsJson();
            }
            updateRegistration(getDeviceIdParam(ctx), payload, ctx);
        } catch (DecodeException e) {
            HttpEndpointUtils.badRequest(ctx.response(), "body does not contain a valid JSON object");
        }
    }

    private void doUpdateRegistrationForm(final RoutingContext ctx) {

        updateRegistration(getDeviceIdParam(ctx), getPayloadForParams(ctx.request()), ctx);
    }

    private void updateRegistration(final String deviceId, final JsonObject payload, final RoutingContext ctx) {

        if (payload != null) {
            payload.remove(FIELD_DEVICE_ID);
        }
        final String tenantId = getTenantParam(ctx);
        logger.debug("updating registration data for device [tenant: {}, device: {}, payload: {}]", tenantId,
                deviceId, payload);
        final HttpServerResponse response = ctx.response();
        final JsonObject requestMsg = RegistrationConstants
                .getServiceRequestAsJson(RegistrationConstants.ACTION_UPDATE, tenantId, deviceId, payload);

        doRegistrationAction(ctx, requestMsg, (status, registrationResult) -> {
            response.setStatusCode(status);
            switch (status) {
            case HttpURLConnection.HTTP_OK:
                String msg = registrationResult.getJsonObject("payload").encodePrettily();
                response.putHeader(HttpHeaders.CONTENT_TYPE, HttpEndpointUtils.CONTENT_TYPE_JSON_UFT8)
                        .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
            default:
                response.end();
            }
        });
    }

    private void doUnregisterDevice(final RoutingContext ctx) {

        final String deviceId = getDeviceIdParam(ctx);
        final String tenantId = getTenantParam(ctx);
        logger.debug("removing registration information for device [tenant: {}, device: {}]", tenantId, deviceId);
        final HttpServerResponse response = ctx.response();
        final JsonObject requestMsg = RegistrationConstants
                .getServiceRequestAsJson(RegistrationConstants.ACTION_DEREGISTER, tenantId, deviceId);
        doRegistrationAction(ctx, requestMsg, (status, registrationResult) -> {
            response.setStatusCode(status);
            switch (status) {
            case HttpURLConnection.HTTP_OK:
                String msg = registrationResult.getJsonObject("payload").encodePrettily();
                response.putHeader(HttpHeaders.CONTENT_TYPE, HttpEndpointUtils.CONTENT_TYPE_JSON_UFT8)
                        .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
            default:
                response.end();
            }
        });
    }

    private void doRegistrationAction(final RoutingContext ctx, final JsonObject requestMsg,
            final BiConsumer<Integer, JsonObject> responseHandler) {

        vertx.eventBus().send(RegistrationConstants.EVENT_BUS_ADDRESS_REGISTRATION_IN, requestMsg, invocation -> {
            HttpServerResponse response = ctx.response();
            if (invocation.failed()) {
                HttpEndpointUtils.serviceUnavailable(response, 2);
            } else {
                final JsonObject registrationResult = (JsonObject) invocation.result().body();
                final Integer status = Integer.valueOf(registrationResult.getString("status"));
                responseHandler.accept(status, registrationResult);
            }
        });
    }
}