org.jspare.forvertx.web.handler.DefaultHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.jspare.forvertx.web.handler.DefaultHandler.java

Source

/*
 * Copyright 2016 JSpare.org.
 *
 * 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.
 */
package org.jspare.forvertx.web.handler;

import static org.jspare.core.container.Environment.my;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Collection;

import org.apache.commons.lang.StringUtils;
import org.jspare.core.exception.SerializationException;
import org.jspare.core.serializer.Json;
import org.jspare.forvertx.web.collector.HandlerData;
import org.jspare.forvertx.web.handling.Handling;
import org.jspare.forvertx.web.handling.HandlingFactory;
import org.jspare.forvertx.web.mapping.handling.ArrayModel;
import org.jspare.forvertx.web.mapping.handling.ArrayModelParser;
import org.jspare.forvertx.web.mapping.handling.MapModel;
import org.jspare.forvertx.web.mapping.handling.MapModelParser;
import org.jspare.forvertx.web.mapping.handling.Model;

import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.RoutingContext;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/** The Constant log. */
@Slf4j

/**
 * Instantiates a new default handler.
 *
 * @param handlerData
 *            the handler data
 */
@AllArgsConstructor
public class DefaultHandler implements Handler<RoutingContext> {

    /** The handler data. */
    protected final HandlerData handlerData;

    /*
     * (non-Javadoc)
     * 
     * @see io.vertx.core.Handler#handle(java.lang.Object)
     */
    @Override
    public void handle(RoutingContext ctx) {

        try {

            Object newInstance = instantiateHandler();

            setHandlingParameters(ctx, newInstance);

            // Wrap bodyEndHandler to share routingContext
            handlerData.bodyEndHandler().forEach(h -> ctx.addBodyEndHandler(event -> {

                h.handle(ctx);
            }));

            Object[] parameters = collectParameters(ctx);

            invokeHandler(ctx, newInstance, parameters);

        } catch (Throwable t) {

            catchInvoke(ctx, t);
        }
    }

    /**
     * Collect parameters. This method is responsible to collect all parameters
     * to send on handler method, resolving parameters and dependencies.
     * 
     * @param routingContext
     *            the routing context
     * @return the object[]
     */
    protected Object[] collectParameters(RoutingContext routingContext) {
        // Prepare parameters to call method of route
        Object[] parameters = new Object[handlerData.method().getParameterCount()];
        int i = 0;
        for (Parameter parameter : handlerData.method().getParameters()) {

            parameters[i] = resolveParameter(parameter, routingContext);
            i++;
        }
        return parameters;
    }

    /**
     * Invoke handler. This method is responsible to invoke mapped route
     * handler.
     * 
     * @param routingContext
     *            the routing context
     * @param newInstance
     *            the new instance
     * @param parameters
     *            the parameters
     * @throws IllegalAccessException
     *             the illegal access exception
     * @throws InvocationTargetException
     *             the invocation target exception
     */
    protected void invokeHandler(RoutingContext routingContext, Object newInstance, Object[] parameters)
            throws IllegalAccessException, InvocationTargetException {
        // Check Authentication
        if (handlerData.auth() && handlerData.authProvider() != null) {

            handleAuthentication(routingContext, newInstance, parameters);
            return;
        }

        if (handlerData.auth() && handlerData.authProvider() == null) {

            log.warn("AuthProvider is null, ignoring Authentication and Authorization");
        }

        // Call method of handler data
        handlerData.method().invoke(newInstance, parameters);
    }

    protected void setHandlingParameters(RoutingContext routingContext, Object newInstance) {
        // If Route is handling by abstract Handling inject some resources
        if (newInstance instanceof Handling) {

            ((Handling) newInstance).setReq(routingContext.request());
            ((Handling) newInstance).setRes(routingContext.response());
            ((Handling) newInstance).setCtx(routingContext);
        }
    }

    protected Object instantiateHandler() {
        // Inject Request and Response if is Available
        Object newInstance = my(HandlingFactory.class).instantiate(handlerData.clazz());
        return newInstance;
    }

    /**
     * Catch invoke.
     *
     * @param routingContext
     *            the routing context
     * @param t
     *            the t
     */
    protected void catchInvoke(RoutingContext routingContext, Throwable t) {
        // Any server error return internal server error
        while (t.getCause() != null) {

            t = t.getCause();
        }
        log.info("Error: {}", handlerData.toStringLine());
        log.error(t.getMessage(), t);
        routingContext.response().setStatusCode(500).end(t.toString());
    }

    /**
     * Handle authentication.
     *
     * @param routingContext
     *            the routing context
     * @param newInstance
     *            the new instance
     * @param parameters
     *            the parameters
     */
    protected void handleAuthentication(RoutingContext routingContext, Object newInstance, Object[] parameters) {

        JsonObject authData = handlerData.authProvider().provideAuthdata(routingContext);
        handlerData.authProvider().authenticate(authData, authenticationResult -> {

            try {

                if (authenticationResult.succeeded()) {

                    routingContext.setUser(authenticationResult.result());
                    if (!handlerData.skipAuthorities() && StringUtils.isNotEmpty(handlerData.autority())) {

                        handleAuthorization(routingContext, newInstance, parameters);
                        return;
                    }
                    handlerData.method().invoke(newInstance, parameters);
                    return;
                }

                if (!routingContext.response().ended()) {

                    log.debug("AuthValidation failed, returning unauthorized status code");
                    sendStatus(routingContext, HttpResponseStatus.UNAUTHORIZED);
                }

            } catch (Throwable t) {

                catchInvoke(routingContext, t);
            }
        });
    }

    /**
     * Handle authorization.
     *
     * @param routingContext
     *            the routing context
     * @param newInstance
     *            the new instance
     * @param parameters
     *            the parameters
     */
    protected void handleAuthorization(RoutingContext routingContext, Object newInstance, Object[] parameters) {
        routingContext.user().isAuthorised(handlerData.autority(), authorizationResult -> {

            if (authorizationResult.succeeded() && authorizationResult.result()) {

                try {

                    handlerData.method().invoke(newInstance, parameters);
                    return;
                } catch (Throwable t) {

                    catchInvoke(routingContext, t);
                }
            }

            sendStatus(routingContext, HttpResponseStatus.FORBIDDEN);
        });
    }

    /**
     * Resolve parameter.
     *
     * @param parameter
     *            the parameter
     * @param routingContext
     *            the routing context
     * @return the object
     */
    @SuppressWarnings("unchecked")
    protected Object resolveParameter(Parameter parameter, RoutingContext routingContext) {

        if (parameter.getType().equals(RoutingContext.class)) {

            return routingContext;
        }
        if (parameter.getType().equals(HttpServerRequest.class)) {

            return routingContext.request();
        }

        if (parameter.getType().equals(HttpServerResponse.class)) {

            return routingContext.response();
        }
        if (StringUtils.isNotEmpty(routingContext.request().getParam(parameter.getName()))) {

            return routingContext.request().getParam(parameter.getName());
        }

        if (parameter.isAnnotationPresent(ArrayModel.class)) {

            ArrayModel am = parameter.getAnnotation(ArrayModel.class);
            Class<? extends Collection<?>> collection = (Class<? extends Collection<?>>) am.collectionClass();
            Class<?> clazz = am.value();
            return ArrayModelParser.toList(routingContext.getBody().toString(), collection, clazz);
        }

        if (parameter.isAnnotationPresent(MapModel.class)) {

            MapModel mm = parameter.getAnnotation(MapModel.class);
            Class<?> mapClazz = mm.mapClass();
            Class<?> key = mm.key();
            Class<?> value = mm.value();
            return MapModelParser.toMap(routingContext.getBody().toString(), mapClazz, key, value);
        }

        if (parameter.getType().getPackage().getName().endsWith(".model")
                || parameter.getType().isAnnotationPresent(Model.class)
                || parameter.isAnnotationPresent(Model.class)) {

            try {
                if (routingContext.getBody() == null) {

                    return null;
                }
                return my(Json.class).fromJSON(routingContext.getBody().toString(), parameter.getType());
            } catch (SerializationException e) {

                log.debug("Invalid content of body for class [{}] on parameter [{}]", parameter.getClass(),
                        parameter.getName());
                return null;
            }
        }
        if (parameter.isAnnotationPresent(org.jspare.forvertx.web.mapping.handling.Parameter.class)) {

            String parameterName = parameter.getAnnotation(org.jspare.forvertx.web.mapping.handling.Parameter.class)
                    .value();
            // Test types
            Type typeOfParameter = parameter.getType();
            if (typeOfParameter.equals(Integer.class)) {
                return Integer.parseInt(routingContext.request().getParam(parameterName));
            }
            if (typeOfParameter.equals(Double.class)) {
                return Double.parseDouble(routingContext.request().getParam(parameterName));
            }
            if (typeOfParameter.equals(Long.class)) {
                return Long.parseLong(routingContext.request().getParam(parameterName));
            }
            return routingContext.request().getParam(parameterName);
        }
        if (parameter.isAnnotationPresent(org.jspare.forvertx.web.mapping.handling.Header.class)) {

            String headerName = parameter.getAnnotation(org.jspare.forvertx.web.mapping.handling.Header.class)
                    .value();
            return routingContext.request().getHeader(headerName);
        }

        return null;
    }

    /**
     * Send status.
     *
     * @param routingContext
     *            the routing context
     * @param status
     *            the status
     */
    protected void sendStatus(RoutingContext routingContext, HttpResponseStatus status) {
        routingContext.response().setStatusCode(status.code()).setStatusMessage(status.reasonPhrase())
                .end(status.reasonPhrase());
    }
}