es.upv.grycap.opengateway.core.http.BaseRestService.java Source code

Java tutorial

Introduction

Here is the source code for es.upv.grycap.opengateway.core.http.BaseRestService.java

Source

/*
 * Open Gateway - Core Components.
 * Copyright 2015-2016 GRyCAP (Universitat Politecnica de Valencia)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * This product combines work with different licenses. See the "NOTICE" text
 * file for details on the various modules and licenses.
 * 
 * The "NOTICE" text file is part of the distribution. Any derivative works
 * that you distribute must include a readable copy of the "NOTICE" text file.
 */

package es.upv.grycap.opengateway.core.http;

import static io.vertx.core.buffer.Buffer.buffer;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isBlank;

import java.util.Objects;
import java.util.function.Function;

import org.slf4j.Logger;

import es.upv.grycap.opengateway.core.http.RestServiceConfig.ApiConfig;
import es.upv.grycap.opengateway.core.loadbalancer.BalanceableService;
import es.upv.grycap.opengateway.core.loadbalancer.LoadBalancerClient;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.CorsHandler;
import io.vertx.ext.web.handler.StaticHandler;

/**
 * Implements a generic REST API that allows clients to list items, retrieve details for a particular item and to add new items. The API can 
 * be extended to meet the specific needs of an application.
 * @author Erik Torres <etserrano@gmail.com>
 * @since 0.0.1
 */
public abstract class BaseRestService extends AbstractVerticle implements BalanceableService {

    protected Logger logger;
    protected RestServiceConfig serviceConfig;
    protected LoadBalancerClient loadBalancerClient;
    protected Function<JsonObject, JsonObject> converter = null;

    private final static long MAX_BODY_SIZE_MIB = 8; // 8 MiB

    @Override
    public LoadBalancerClient getLoadBalancer() {
        return loadBalancerClient;
    }

    @Override
    public void setLoadBalancer(final LoadBalancerClient loadBalancerClient) {
        this.loadBalancerClient = loadBalancerClient;
    }

    @Override
    public void start() throws Exception {
        requireNonNull(serviceConfig, "A valid service configuration expected");
        requireNonNull(loadBalancerClient, "A valid load balancer client expected");
        // set body limit and create router
        final long maxBodySize = context.config().getLong("http-server.max-body-size", MAX_BODY_SIZE_MIB) * 1024l
                * 1024l;
        final Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create().setBodyLimit(maxBodySize));
        // enable CORS
        router.route().handler(CorsHandler.create("*").allowedMethod(HttpMethod.GET).allowedMethod(HttpMethod.POST)
                .allowedMethod(HttpMethod.PUT).allowedMethod(HttpMethod.DELETE).allowedMethod(HttpMethod.OPTIONS)
                .allowedHeader("Content-Type").allowedHeader("Authorization"));
        // configure index page      
        router.route(serviceConfig.getFrontpage().orElse("/")).handler(StaticHandler.create());
        // serve resources
        serviceConfig.getServices().values().stream().forEach(s -> {
            final String path = requireNonNull(s.getPath(), "A valid path required");
            router.get(String.format("%s/:id", path)).produces("application/json").handler(e -> handleGet(s, e));
            router.get(path).produces("application/json").handler(e -> handleList(s, e));
            router.post(path).handler(e -> handleCreate(s, e));
            router.put(String.format("%s/:id", path)).consumes("application/json").handler(e -> handleModify(s, e));
            router.delete(String.format("%s/:id", path)).handler(e -> handleDelete(s, e));
        });
        // start HTTP server
        final int port = context.config().getInteger("http.port", 8080);
        vertx.createHttpServer().requestHandler(router::accept).listen(port);
        logger.trace("New instance created: [id=" + context.deploymentID() + "].");
    }

    private void handleGet(final ApiConfig api, final RoutingContext routingContext) {
        final String id = routingContext.request().getParam("id");
        final HttpServerResponse response = routingContext.response();
        if (id == null) {
            sendError(400, response);
        } else {
            final String service = loadBalancerClient.getServiceInstance(api.getAppId());
            if (isBlank(service)) {
                sendError(503, response);
            } else {
                new VertxHttp2Client(vertx).asyncGet(String.format("%s/%s", service, id), false, resp -> {
                    if (!resp.succeeded()) {
                        sendError(504, response);
                    } else {
                        convertSend(resp.result(), response);
                    }
                });
            }
        }
    }

    private void handleList(final ApiConfig api, final RoutingContext routingContext) {
        final HttpServerResponse response = routingContext.response();
        final String service = loadBalancerClient.getServiceInstance(api.getAppId());
        if (isBlank(service)) {
            sendError(503, response);
        } else {
            new VertxHttp2Client(vertx).asyncGet(service, false, resp -> {
                if (!resp.succeeded()) {
                    sendError(504, response);
                } else {
                    convertSend(resp.result(), response);
                }
            });
        }
    }

    private void handleCreate(final ApiConfig api, final RoutingContext routingContext) {
        final Buffer buffer = routingContext.getBody();
        final HttpServerResponse response = routingContext.response();
        if (buffer == null) {
            sendError(400, response);
        } else {
            final String service = loadBalancerClient.getServiceInstance(api.getAppId());
            if (isBlank(service)) {
                sendError(503, response);
            } else {
                new VertxHttp2Client(vertx).asyncPostBytes(service, "application/json", () -> buffer.getBytes(),
                        resp -> {
                            if (!resp.succeeded()) {
                                sendError(504, response);
                            } else {
                                ofNullable(resp.result().header("Location")).orElse(emptyList()).stream()
                                        .filter(Objects::nonNull)
                                        .forEach(location -> response.putHeader("Location", location));
                                response.setStatusCode(201).end();
                            }
                        });
            }
        }
    }

    private void handleModify(final ApiConfig api, final RoutingContext routingContext) {
        final String id = routingContext.request().getParam("id");
        final Buffer buffer = routingContext.getBody();
        final HttpServerResponse response = routingContext.response();
        if (id == null || buffer == null) {
            sendError(400, response);
        } else {
            final String service = loadBalancerClient.getServiceInstance(api.getAppId());
            if (isBlank(service)) {
                sendError(503, response);
            } else {
                new VertxHttp2Client(vertx).asyncPutBytes(String.format("%s/%s", service, id), "application/json",
                        () -> buffer.getBytes(), resp -> {
                            if (!resp.succeeded()) {
                                sendError(504, response);
                            } else {
                                response.setStatusCode(204).end();
                            }
                        });
            }
        }
    }

    private void handleDelete(final ApiConfig api, final RoutingContext routingContext) {
        final String id = routingContext.request().getParam("id");
        final HttpServerResponse response = routingContext.response();
        if (id == null) {
            sendError(400, response);
        } else {
            final String service = loadBalancerClient.getServiceInstance(api.getAppId());
            if (isBlank(service)) {
                sendError(503, response);
            } else {
                new VertxHttp2Client(vertx).asyncDelete(String.format("%s/%s", service, id), resp -> {
                    if (!resp.succeeded()) {
                        sendError(504, response);
                    } else {
                        response.setStatusCode(204).end();
                    }
                });
            }
        }
    }

    private void sendError(final int statusCode, final HttpServerResponse response) {
        response.setStatusCode(statusCode).end();
    }

    private void convertSend(final HttpResponse aux, final HttpServerResponse response) {
        if (converter != null) {
            final JsonObject jsonObj = aux.fromString(r -> new JsonObject(r));
            if (jsonObj == null) {
                sendError(404, response);
            } else {
                response.putHeader("content-type", "application/json")
                        .end(ofNullable(converter.apply(jsonObj)).orElse(new JsonObject()).encode());
            }
        } else {
            final Buffer buffer = buffer(aux.readByteArray());
            if (buffer.length() == 0) {
                sendError(404, response);
            } else {
                response.putHeader("content-type", "application/json").end(buffer);
            }
        }
    }

}