All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nannoq.tools.web.controllers.RestController Maven / Gradle / Ivy

/*
 * MIT License
 *
 * Copyright (c) 2017 Anders Mikkelsen
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package com.nannoq.tools.web.controllers;

import com.nannoq.tools.repository.models.ETagable;
import com.nannoq.tools.repository.models.Model;
import com.nannoq.tools.repository.models.ModelUtils;
import com.nannoq.tools.repository.models.ValidationError;
import com.nannoq.tools.repository.utils.*;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.lang3.ArrayUtils;

import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.function.Function;

import static com.nannoq.tools.repository.models.Model.buildValidationErrorObject;
import static com.nannoq.tools.web.RoutingHelper.*;
import static com.nannoq.tools.web.requestHandlers.RequestLogHandler.REQUEST_PROCESS_TIME_TAG;
import static com.nannoq.tools.web.requestHandlers.RequestLogHandler.addLogMessageToRequestLog;
import static com.nannoq.tools.web.responsehandlers.ResponseLogHandler.BODY_CONTENT_TAG;

/**
 * This interface defines the RestController. It defines a chain of operations for CRUD and Index operations. Overriding
 * functions must remember to call the next element in the chain.
 *
 * @author Anders Mikkelsen
 * @version 17.11.2017
 */
public interface RestController {
    default void show(RoutingContext routingContext) {
        try {
            preShow(routingContext);
        } catch (Exception e) {
            addLogMessageToRequestLog(routingContext, "Error in Show!", e);

            routingContext.fail(e);
        }
    }

    default void preShow(RoutingContext routingContext) {
        performShow(routingContext);
    }

    void performShow(RoutingContext routingContext);

    default void postShow(RoutingContext routingContext, E item, @Nonnull String[] projections) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);
        String requestEtag = routingContext.request().getHeader("If-None-Match");

        if (requestEtag != null && requestEtag.equals(item.getEtag())) {
            unChangedShow(routingContext);
        } else {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, item.toJsonString(projections));

            setStatusCodeAndContinue(200, routingContext, initialNanoTime);
        }
    }

    default void unChangedShow(RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        setStatusCodeAndContinue(304, routingContext, initialNanoTime);
    }

    default void notFoundShow(RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        setStatusCodeAndAbort(404, routingContext, initialNanoTime);
    }

    default void failedShow(RoutingContext routingContext, JsonObject debugInformation) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        routingContext.put(BODY_CONTENT_TAG, debugInformation.encode());
        setStatusCodeAndAbort(500, routingContext, initialNanoTime);
    }

    default void index(RoutingContext routingContext) {
        try {
            preIndex(routingContext, null);
        } catch (Exception e) {
            addLogMessageToRequestLog(routingContext, "Error in Index!", e);

            routingContext.fail(e);
        }
    }

    default void index(RoutingContext routingContext, String customQuery) {
        try {
            preIndex(routingContext, customQuery);
        } catch (Exception e) {
            addLogMessageToRequestLog(routingContext, "Error in Index!", e);

            routingContext.fail(e);
        }
    }

    default void preIndex(RoutingContext routingContext, String customQuery) {
        prepareQuery(routingContext, customQuery);
    }

    void prepareQuery(RoutingContext routingContext, String customQuery);

    default void preProcessQuery(RoutingContext routingContext, Map> queryMap) {
        processQuery(routingContext, queryMap);
    }

    void processQuery(RoutingContext routingContext, Map> queryMap);

    default void postProcessQuery(RoutingContext routingContext, AggregateFunction aggregateFunction,
                                  Queue orderByQueue, Map> params,
                                  @Nonnull String[] projections, String indexName, Integer limit) {
        postPrepareQuery(routingContext, aggregateFunction, orderByQueue, params, projections, indexName, limit);
    }

    default void postPrepareQuery(RoutingContext routingContext, AggregateFunction aggregateFunction,
                                  Queue orderByQueue, Map> params,
                                  String[] projections, String indexName, Integer limit) {
        createIdObjectForIndex(routingContext, aggregateFunction, orderByQueue, params, projections, indexName, limit);
    }

    void createIdObjectForIndex(RoutingContext routingContext, AggregateFunction aggregateFunction,
                                Queue orderByQueue, Map> params,
                                String[] projections, String indexName, Integer limit);

    void performIndex(RoutingContext routingContext, JsonObject identifiers, AggregateFunction aggregateFunction,
                      Queue orderByQueue, Map> params,
                      String[] projections, String indexName, Integer limit);

    void proceedWithPagedIndex(JsonObject id, String pageToken,
                               QueryPack queryPack, String[] projections, RoutingContext routingContext);

    void proceedWithAggregationIndex(RoutingContext routingContext, String etag, JsonObject id,
                                     QueryPack queryPack, String[] projections);

    default void postIndex(RoutingContext routingContext, @Nonnull ItemList items, @Nonnull String[] projections) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);
        String requestEtag = routingContext.request().getHeader("If-None-Match");

        if (requestEtag != null && requestEtag.equals(items.getEtag())) {
            unChangedIndex(routingContext);
        } else {
            String content = items.toJsonString(projections);

            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, content);

            setStatusCodeAndContinue(200, routingContext, initialNanoTime);
        }
    }

    default void postAggregation(RoutingContext routingContext, @Nonnull String content) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);
        String requestEtag = routingContext.request().getHeader("If-None-Match");

        if (requestEtag != null && requestEtag.equals(ModelUtils.returnNewEtag(content.hashCode()))) {
            unChangedIndex(routingContext);
        } else {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, content);

            setStatusCodeAndContinue(200, routingContext, initialNanoTime);
        }
    }

    default void unChangedIndex(RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        setStatusCodeAndContinue(304, routingContext, initialNanoTime);
    }

    default void failedIndex(RoutingContext routingContext, JsonObject debugInformation) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        routingContext.put(BODY_CONTENT_TAG, debugInformation.encode());
        setStatusCodeAndAbort(500, routingContext, initialNanoTime);
    }

    default void create(RoutingContext routingContext) {
        try {
            preCreate(routingContext);
        } catch (Exception e) {
            addLogMessageToRequestLog(routingContext, "Error in Create!", e);

            routingContext.fail(e);
        }
    }

    default void preCreate(RoutingContext routingContext) {
        if (denyQuery(routingContext)) return;

        parseBodyForCreate(routingContext);
    }

    void parseBodyForCreate(RoutingContext routingContext);

    default void preVerifyNotExists(E newRecord, RoutingContext routingContext) {
        verifyNotExists(newRecord, routingContext);
    }

    void verifyNotExists(E newRecord, RoutingContext routingContext);

    default void postVerifyNotExists(E newRecord, RoutingContext routingContext) {
        preSetIdentifiers(newRecord, routingContext);
    }

    default void preSetIdentifiers(E newRecord, RoutingContext routingContext) {
        setIdentifiers(newRecord, routingContext);
    }

    void setIdentifiers(E newRecord, RoutingContext routingContext);

    default void preSanitizeForCreate(E record, RoutingContext routingContext) {
        performSanitizeForCreate(record, routingContext);
    }

    default void performSanitizeForCreate(E record, RoutingContext routingContext) {
        record.sanitize();

        postSanitizeForCreate(record, routingContext);
    }

    default void postSanitizeForCreate(E record, RoutingContext routingContext) {
        preValidateForCreate(record, routingContext);
    }

    default void preValidateForCreate(E record, RoutingContext routingContext) {
        performValidateForCreate(record, routingContext);
    }

    default void performValidateForCreate(E record, RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);
        List errors = record.validateCreate();

        if (errors.size() > 0) {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, Json.encodePrettily(buildValidationErrorObject(errors)));
            setStatusCodeAndAbort(422, routingContext, initialNanoTime);
        } else {
            postValidateForCreate(record, routingContext);
        }
    }

    default void postValidateForCreate(E record, RoutingContext routingContext) {
        performCreate(record, routingContext);
    }

    void performCreate(E newRecord, RoutingContext routingContext);

    default void postCreate(@Nonnull E createdRecord, RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
        routingContext.put(BODY_CONTENT_TAG, createdRecord.toJsonString());
        setStatusCodeAndContinue(201, routingContext, initialNanoTime);
    }

    default void failedCreate(RoutingContext routingContext, JsonObject userFeedBack) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        if (userFeedBack != null) {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, userFeedBack.encode());
        }

        setStatusCodeAndContinue(500, routingContext, initialNanoTime);
    }

    default void update(RoutingContext routingContext) {
        try {
            preUpdate(routingContext);
        } catch (Exception e) {
            addLogMessageToRequestLog(routingContext, "Error in Update!", e);

            routingContext.fail(e);
        }
    }

    default void preUpdate(RoutingContext routingContext) {
        if (denyQuery(routingContext)) return;

        parseBodyForUpdate(routingContext);
    }

    void parseBodyForUpdate(RoutingContext routingContext);

    default void preVerifyExistsForUpdate(E newRecord, RoutingContext routingContext) {
        verifyExistsForUpdate(newRecord, routingContext);
    }

    void verifyExistsForUpdate(E newRecord, RoutingContext routingContext);

    default void postVerifyExistsForUpdate(E oldRecord, E newRecord, RoutingContext routingContext) {
        preSanitizeForUpdate(oldRecord, newRecord, routingContext);
    }

    default void preSanitizeForUpdate(E record, E newRecord, RoutingContext routingContext) {
        performSanitizeForUpdate(record, newRecord, routingContext);
    }

    default void performSanitizeForUpdate(E record, E newRecord, RoutingContext routingContext) {
        Function setNewValues = rec -> {
            rec.setModifiables(newRecord);
            rec.sanitize();

            return rec;
        };

        postSanitizeForUpdate(setNewValues.apply(record), setNewValues, routingContext);
    }

    default void postSanitizeForUpdate(E record, Function setNewValues, RoutingContext routingContext) {
        preValidateForUpdate(record, setNewValues, routingContext);
    }

    default void preValidateForUpdate(E record, Function setNewValues, RoutingContext routingContext) {
        performValidateForUpdate(record, setNewValues, routingContext);
    }

    default void performValidateForUpdate(E record, Function setNewValues, RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);
        record.setUpdatedAt(new Date());
        List errors = record.validateUpdate();

        if (errors.size() > 0) {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, Json.encodePrettily(buildValidationErrorObject(errors)));
            setStatusCodeAndAbort(422, routingContext, initialNanoTime);
        } else {
            postValidateForUpdate(record, setNewValues, routingContext);
        }
    }

    default void postValidateForUpdate(E record, Function setNewValues, RoutingContext routingContext) {
        performUpdate(record, setNewValues, routingContext);
    }

    void performUpdate(E updatedRecord, Function setNewValues, RoutingContext routingContext);

    default void postUpdate(@Nonnull E updatedRecord, RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
        routingContext.put(BODY_CONTENT_TAG, updatedRecord.toJsonString());
        setStatusCodeAndContinue(200, routingContext, initialNanoTime);
    }

    default void failedUpdate(RoutingContext routingContext, JsonObject userFeedBack) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        if (userFeedBack != null) {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, userFeedBack.encode());
        }

        setStatusCodeAndContinue(500, routingContext, initialNanoTime);
    }

    default void destroy(RoutingContext routingContext) {
        try {
            preDestroy(routingContext);
        } catch (Exception e) {
            addLogMessageToRequestLog(routingContext, "Error in Destroy!", e);

            routingContext.fail(e);
        }
    }

    default void preDestroy(RoutingContext routingContext) {
        if (denyQuery(routingContext)) return;

        verifyExistsForDestroy(routingContext);
    }

    void verifyExistsForDestroy(RoutingContext routingContext);

    default void postVerifyExistsForDestroy(E recordForDestroy, RoutingContext routingContext) {
        performDestroy(recordForDestroy, routingContext);
    }

    void performDestroy(E recordForDestroy, RoutingContext routingContext);

    default void postDestroy(@Nonnull E destroyedRecord, RoutingContext routingContext) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        setStatusCodeAndContinue(204, routingContext, initialNanoTime);
    }

    default void failedDestroy(RoutingContext routingContext, JsonObject userFeedBack) {
        long initialNanoTime = routingContext.get(REQUEST_PROCESS_TIME_TAG);

        if (userFeedBack != null) {
            routingContext.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
            routingContext.put(BODY_CONTENT_TAG, userFeedBack.encode());
        }

        setStatusCodeAndContinue(500, routingContext, initialNanoTime);
    }

    default Field[] getAllFieldsOnType(Class klazz) {
        Field[] fields = klazz.getDeclaredFields();

        if (klazz.getSuperclass() != null && klazz.getSuperclass() != Object.class) {
            return ArrayUtils.addAll(fields, getAllFieldsOnType(klazz.getSuperclass()));
        }

        return fields;
    }

    default Method[] getAllMethodsOnType(Class klazz) {
        Method[] methods = klazz.getDeclaredMethods();

        if (klazz.getSuperclass() != null && klazz.getSuperclass() != Object.class) {
            return ArrayUtils.addAll(methods, getAllMethodsOnType(klazz.getSuperclass()));
        }

        return methods;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy