org.eclipse.hono.adapter.rest.VertxBasedRestProtocolAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.hono.adapter.rest.VertxBasedRestProtocolAdapter.java

Source

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

import static java.net.HttpURLConnection.HTTP_CREATED;
import static java.net.HttpURLConnection.HTTP_OK;

import java.util.Map.Entry;
import java.util.function.BiConsumer;

import org.eclipse.hono.adapter.http.AbstractVertxBasedHttpProtocolAdapter;
import org.eclipse.hono.client.RegistrationClient;
import org.eclipse.hono.config.ServiceConfigProperties;
import org.eclipse.hono.util.RegistrationResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

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;

/**
 * A Vert.x based Hono protocol adapter for accessing Hono's Telemetry & Registration API using REST.
 */
@Component
@Scope("prototype")
public class VertxBasedRestProtocolAdapter extends AbstractVertxBasedHttpProtocolAdapter<ServiceConfigProperties> {

    private static final Logger LOG = LoggerFactory.getLogger(VertxBasedRestProtocolAdapter.class);

    private static final String PARAM_TENANT = "tenant";
    private static final String PARAM_DEVICE_ID = "device_id";

    @Override
    protected void addRoutes(final Router router) {
        addTelemetryApiRoutes(router);
        addEventApiRoutes(router);
        addRegistrationApiRoutes(router);
    }

    private void addRegistrationApiRoutes(final Router router) {

        // ADD device registration
        router.route(HttpMethod.POST, String.format("/registration/:%s", PARAM_TENANT)).consumes(CONTENT_TYPE_JSON)
                .handler(this::doRegisterDeviceJson);
        router.route(HttpMethod.POST, String.format("/registration/:%s", PARAM_TENANT))
                .consumes(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .handler(this::doRegisterDeviceForm);
        router.route(HttpMethod.POST, "/registration/*/*").handler(ctx -> {
            badRequest(ctx.response(), "missing or unsupported content-type");
        });

        // GET or FIND device registration
        router.route(HttpMethod.GET, String.format("/registration/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .handler(this::doGetDevice);
        router.route(HttpMethod.POST, String.format("/registration/:%s/find", PARAM_TENANT))
                .consumes(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString()).handler(this::doFindDevice);

        // UPDATE existing registration
        router.route(HttpMethod.PUT, String.format("/registration/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .consumes(CONTENT_TYPE_JSON).handler(this::doUpdateRegistrationJson);
        router.route(HttpMethod.PUT, String.format("/registration/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .consumes(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString())
                .handler(this::doUpdateRegistrationForm);
        router.route(HttpMethod.PUT, "/registration/*/*").handler(ctx -> {
            badRequest(ctx.response(), "missing or unsupported content-type");
        });

        // REMOVE registration
        router.route(HttpMethod.DELETE, String.format("/registration/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .handler(this::doUnregisterDevice);
    }

    private void addTelemetryApiRoutes(final Router router) {

        // route for uploading telemetry data
        router.route(HttpMethod.PUT, String.format("/telemetry/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .handler(ctx -> uploadTelemetryMessage(ctx, getTenantParam(ctx), getDeviceIdParam(ctx)));
    }

    private void addEventApiRoutes(final Router router) {

        // route for sending event messages
        router.route(HttpMethod.PUT, String.format("/event/:%s/:%s", PARAM_TENANT, PARAM_DEVICE_ID))
                .handler(ctx -> uploadEventMessage(ctx, getTenantParam(ctx), getDeviceIdParam(ctx)));
    }

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

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

    private static JsonObject getPayloadForParams(final HttpServerRequest request) {
        JsonObject payload = new JsonObject();
        for (Entry<String, String> param : request.params()) {
            // filter out tenant param captured from URI path
            if (!PARAM_TENANT.equalsIgnoreCase(param.getKey())) {
                payload.put(param.getKey(), param.getValue());
            }
        }
        return payload;
    }

    private void doRegisterDeviceJson(final RoutingContext ctx) {

        try {
            JsonObject payload = null;
            if (ctx.getBody().length() > 0) {
                payload = ctx.getBodyAsJson();
            }
            registerDevice(ctx, payload);
        } catch (DecodeException e) {
            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) {
            badRequest(ctx.response(), "missing body");
        } else {
            Object deviceId = payload.remove(PARAM_DEVICE_ID);
            if (deviceId == null) {
                badRequest(ctx.response(), String.format("'%s' param is required", PARAM_DEVICE_ID));
            } else if (!(deviceId instanceof String)) {
                badRequest(ctx.response(), String.format("'%s' must be a string", PARAM_DEVICE_ID));
            } else {
                LOG.debug("registering data for device: {}", payload);
                doRegistrationAction(ctx, (client, response) -> {
                    client.register((String) deviceId, payload, result -> {
                        if (result.failed()) {
                            internalServerError(response, "could not register device");
                        } else {
                            RegistrationResult registerResult = result.result();
                            response.setStatusCode(registerResult.getStatus());
                            switch (registerResult.getStatus()) {
                            case HTTP_CREATED:
                                response.putHeader(HttpHeaders.LOCATION,
                                        String.format("/registration/%s/%s", getTenantParam(ctx), 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) {
            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(PARAM_DEVICE_ID);
        }
        doRegistrationAction(ctx, (client, response) -> {
            client.update(deviceId, payload, updateRequest -> {
                if (updateRequest.failed()) {
                    internalServerError(response, "could not update device registration");
                } else {
                    RegistrationResult updateResult = updateRequest.result();
                    response.setStatusCode(updateResult.getStatus());
                    switch (updateResult.getStatus()) {
                    case HTTP_OK:
                        String msg = updateResult.getPayload().encodePrettily();
                        response.putHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON_UFT8)
                                .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
                    default:
                        response.end();
                    }
                }
            });
        });
    }

    private void doUnregisterDevice(final RoutingContext ctx) {
        String deviceId = getDeviceIdParam(ctx);
        doRegistrationAction(ctx, (client, response) -> {
            client.deregister(deviceId, deregisterRequest -> {
                if (deregisterRequest.failed()) {
                    internalServerError(response, "could not unregister device");
                } else {
                    RegistrationResult deregisterResult = deregisterRequest.result();
                    response.setStatusCode(deregisterResult.getStatus());
                    switch (deregisterResult.getStatus()) {
                    case HTTP_OK:
                        String msg = deregisterResult.getPayload().encodePrettily();
                        response.putHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON_UFT8)
                                .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
                    default:
                        response.end();
                    }
                }
            });
        });
    }

    private void doGetDevice(final RoutingContext ctx) {
        String deviceId = getDeviceIdParam(ctx);
        doRegistrationAction(ctx, (client, response) -> {
            client.get(deviceId, getRequest -> {
                if (getRequest.failed()) {
                    internalServerError(response, "could not get device");
                } else {
                    RegistrationResult getResult = getRequest.result();
                    response.setStatusCode(getResult.getStatus());
                    switch (getResult.getStatus()) {
                    case HTTP_OK:
                        String msg = getResult.getPayload().encodePrettily();
                        response.putHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON_UFT8)
                                .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
                    default:
                        response.end();
                    }
                }
            });
        });
    }

    private void doFindDevice(final RoutingContext ctx) {
        String key = null;
        String value = null;
        for (Entry<String, String> param : ctx.request().params()) {
            if (!PARAM_TENANT.equals(param.getKey())) {
                key = param.getKey();
                value = param.getValue();
            }
        }
        findDevice(key, value, ctx);
    }

    private void findDevice(final String key, final String value, final RoutingContext ctx) {
        if (key == null || value == null) {
            badRequest(ctx.response(), "query param is missing");
        } else {
            doRegistrationAction(ctx, (client, response) -> {
                client.find(key, value, getRequest -> {
                    if (getRequest.failed()) {
                        internalServerError(response, "could not get device");
                    } else {
                        RegistrationResult getResult = getRequest.result();
                        response.setStatusCode(getResult.getStatus());
                        switch (getResult.getStatus()) {
                        case HTTP_OK:
                            String msg = getResult.getPayload().encodePrettily();
                            response.putHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON_UFT8)
                                    .putHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(msg.length())).write(msg);
                        default:
                            response.end();
                        }
                    }
                });
            });
        }
    }

    private void doRegistrationAction(final RoutingContext ctx,
            final BiConsumer<RegistrationClient, HttpServerResponse> action) {
        final String tenant = getTenantParam(ctx);
        final HttpServerResponse resp = ctx.response();
        getRegistrationServiceClient().getOrCreateRegistrationClient(tenant, done -> {
            if (done.succeeded()) {
                action.accept(done.result(), resp);
            } else {
                // we don't have a connection to Hono
                serviceUnavailable(resp, 2);
            }
        });
    }
}