silentbalanceyh/vertx-zero

View on GitHub
vertx-gaia/vertx-up/src/main/web/io/vertx/up/backbone/hunt/Answer.java

Summary

Maintainability
A
25 mins
Test Coverage
package io.vertx.up.backbone.hunt;

import io.horizon.eon.em.web.HttpStatusCode;
import io.horizon.exception.web._500InternalServerException;
import io.horizon.uca.log.Annal;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
import io.vertx.up.annotations.Off;
import io.vertx.up.annotations.SessionData;
import io.vertx.up.atom.agent.Event;
import io.vertx.up.commune.Envelop;
import io.vertx.up.eon.KName;
import io.vertx.up.eon.KWeb;
import io.vertx.up.extension.dot.PluginExtension;
import io.vertx.up.util.Ut;
import jakarta.ws.rs.core.MediaType;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;

/**
 * Response process to normalize the response request.
 * 1. Media definition
 * 2. Operation based on event, envelop, context
 */
public final class Answer {
    private static final Annal LOGGER = Annal.get(Answer.class);

    public static Envelop previous(final RoutingContext context) {
        Envelop envelop = context.get(KWeb.ARGS.REQUEST_BODY);
        if (Objects.isNull(envelop)) {
            envelop = Envelop.failure(new _500InternalServerException(Answer.class, "Previous Error of " + KWeb.ARGS.REQUEST_BODY));
        }
        return envelop;
    }

    public static void next(final RoutingContext context, final Envelop envelop) {
        if (envelop.valid()) {
            /*
             * Next step here
             */
            context.put(KWeb.ARGS.REQUEST_BODY, envelop);
            context.next();
        } else {
            reply(context, envelop);
        }
    }

    public static void normalize(final RoutingContext context, final Envelop envelop) {
        if (envelop.valid()) {
            /*
             * Updated here
             */
            envelop.bind(context);
            context.put(KWeb.ARGS.REQUEST_BODY, envelop);
            context.next();
        } else {
            reply(context, envelop);
        }
    }

    public static void reply(final RoutingContext context, final Envelop envelop) {
        reply(context, envelop, new HashSet<>());
    }

    public static void reply(final RoutingContext context, final Envelop envelop, final Supplier<Set<MediaType>> supplier) {
        Set<MediaType> produces = Objects.isNull(supplier) ? new HashSet<>() : supplier.get();
        if (Objects.isNull(produces)) {
            produces = new HashSet<>();
        }
        reply(context, envelop, produces);
    }

    public static void reply(final RoutingContext context, final Envelop envelop, final Event event) {
        Set<MediaType> produces;
        if (Objects.isNull(event)) {
            produces = new HashSet<>();
        } else {
            produces = event.getProduces();
            if (Objects.isNull(produces)) {
                produces = new HashSet<>();
            }
        }
        reply(context, envelop, produces, Objects.isNull(event) ? null : event.getAction());
    }

    private static void reply(final RoutingContext context, final Envelop envelop, final Set<MediaType> mediaTypes) {
        reply(context, envelop, mediaTypes, null);
    }

    private static void reply(final RoutingContext context, final Envelop envelop,
                              final Set<MediaType> mediaTypes, final Method sessionAction) {
        final HttpServerResponse response = context.response();
        /*
         * FIX: java.lang.IllegalStateException: Response is closed
         * The response has been sent here
         */
        if (!response.closed()) {
            /*
             * Set http status information on response, all information came from `Envelop`
             * 1) Status Code
             * 2) Status Message
             * Old code
             *
             *
             *
             * Above code will be put into
             * Outcome.out(response, processed, mediaTypes);
             *
             * It's not needed for current position to set or here will cause following bug:
             *   java.lang.IllegalStateException: Response head already sent
             */
            final HttpStatusCode code = envelop.status();
            response.setStatusCode(code.code());
            response.setStatusMessage(code.message());
            /*
             * Bind Data
             */
            envelop.bind(context);
            /*
             * Media processing
             */
            Outcome.media(response, mediaTypes);
            /*
             * Security for response ( Successful Only )
             */
            if (envelop.valid()) {
                Outcome.security(response);
                /*
                 * SessionData Stored
                 */
                dataSession(context, envelop.data(), sessionAction);
            }
            /*
             * Infusion Extension for response replying here ( Plug-in )
             */
            PluginExtension.Answer.reply(context, envelop).compose(processed -> {
                /*
                 * Output of current situation,
                 * Here has been replaced by DataRegion.
                 * Fix BUG:
                 * In old workflow, below code is not in compose of `PluginExtension`,
                 * The async will impact response data here, it could let response keep the original
                 * and ACL workflow won't be OK for response data serialization.
                 */
                Outcome.out(response, processed, mediaTypes);

                /*
                 * New Feature to publish data into address of @Off
                 */
                dataPublish(context, processed, sessionAction);
                return Future.succeededFuture();
            });
        }
    }

    private static void dataPublish(final RoutingContext context, final Envelop envelop,
                                    final Method method) {
        if (Objects.isNull(method) || !method.isAnnotationPresent(Off.class)) {
            return;
        }
        final Annotation annotation = method.getAnnotation(Off.class);
        final String address = Ut.invoke(annotation, "address");
        if (Ut.isNil(address)) {
            return;
        }

        final Vertx vertx = context.vertx();
        final EventBus publish = vertx.eventBus();
        LOGGER.info("[ Publish ] The response will be published to {0}", address);
        publish.publish(address, envelop);
    }

    private static <T> void dataSession(final RoutingContext context,
                                        final T data,
                                        final Method method) {
        final Session session = context.session();
        if (Objects.isNull(session) || Objects.isNull(data)) {
            return;
        }
        if (Objects.isNull(method) || !method.isAnnotationPresent(SessionData.class)) {
            return;
        }

        final Annotation annotation = method.getAnnotation(SessionData.class);
        final String key = Ut.invoke(annotation, KName.VALUE);
        final String field = Ut.invoke(annotation, KName.FIELD);
        // Data Storage
        Object reference = data;
        if (Ut.isJObject(data) && Ut.isNotNil(field)) {
            final JsonObject target = (JsonObject) data;
            reference = target.getValue(field);
        }
        // Session Put / Include Session ID
        session.put(key, reference);
    }
}