package com.github.aesteve.vertx.nubes.reflections.visitors;
import com.github.aesteve.vertx.nubes.Config;
import com.github.aesteve.vertx.nubes.annotations.auth.Auth;
import com.github.aesteve.vertx.nubes.annotations.filters.After;
import com.github.aesteve.vertx.nubes.annotations.filters.Before;
import com.github.aesteve.vertx.nubes.annotations.routing.Disabled;
import com.github.aesteve.vertx.nubes.annotations.routing.Forward;
import com.github.aesteve.vertx.nubes.auth.AuthMethod;
import com.github.aesteve.vertx.nubes.handlers.AnnotationProcessor;
import com.github.aesteve.vertx.nubes.handlers.Processor;
import com.github.aesteve.vertx.nubes.reflections.RouteRegistry;
import com.github.aesteve.vertx.nubes.reflections.factories.AuthenticationFactory;
import com.github.aesteve.vertx.nubes.routing.HttpMethodFactory;
import com.github.aesteve.vertx.nubes.routing.MVCRoute;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.Session;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
class MethodVisitor<T> {
private static final Logger LOG = LoggerFactory.getLogger(MethodVisitor.class);
private final Class<T> controller;
private final Method method;
private final Config config;
private final T instance;
private final String basePath;
private final Auth auth;
private final AuthenticationFactory authFactory;
private final RouteRegistry routeRegistry;
private final Map<Class<? extends Annotation>, BiConsumer<RoutingContext, ?>> returnHandlers;
private final Set<Processor> processors;
private final Set<Handler<RoutingContext>> paramsHandlers;
private final List<MVCRoute> routes;
private boolean usesSession;
MethodVisitor(ControllerVisitor<T> parent, Method method) {
this.method = method;
controller = parent.clazz;
config = parent.config;
instance = parent.instance;
basePath = parent.basePath;
auth = method.getAnnotation(Auth.class) == null ? controller.getAnnotation(Auth.class) : method.getAnnotation(Auth.class);
authFactory = parent.authFactory;
routeRegistry = parent.routeRegistry;
returnHandlers = parent.returnHandlers;
processors = parent.processors;
paramsHandlers = new LinkedHashSet<>();
routes = new ArrayList<>();
}
List<MVCRoute> visit() {
if (!HttpMethodFactory.isRouteMethod(method)) {
return routes;
}
createParamsHandlers();
Map<HttpMethod, String> httpMethods = HttpMethodFactory.fromAnnotatedMethod(method);
routes.addAll(httpMethods.entrySet().stream().map(this::createHandlers).collect(Collectors.toList()));
return routes;
}
private MVCRoute createHandlers(Map.Entry<HttpMethod, String> entry) {
HttpMethod httpMethod = entry.getKey();
String path = entry.getValue();
MVCRoute route = createRoute(httpMethod, path);
handleMethodAnnotations(route);
createAopProcessors(route);
route.addProcessors(processors);
route.attachHandlers(paramsHandlers);
route.setMainHandler(method);
routeRegistry.register(controller, method, route);
if (method.isAnnotationPresent(Forward.class)) {
Forward redirect = method.getAnnotation(Forward.class);
routeRegistry.bindRedirect(route, redirect);
}
return route;
}
private void createAopProcessors(MVCRoute route) {
Before before = method.getAnnotation(Before.class);
After after = method.getAnnotation(After.class);
if (before != null) {
Handler<RoutingContext> beforeHandler = config.getAopHandler(before.name());
if (beforeHandler == null) {
LOG.warn("The interceptor with name" + (before.name()) + " could not be found");
} else {
route.attachInterceptor(beforeHandler, true);
}
}
if (after != null) {
Handler<RoutingContext> afterHandler = config.getAopHandler(after.name());
if (afterHandler == null) {
LOG.warn("The interceptor with name" + (after.name()) + " could not be found");
} else {
route.attachInterceptor(afterHandler, false);
}
}
}
private void handleMethodAnnotations(MVCRoute route) {
for (Annotation methodAnnotation : method.getDeclaredAnnotations()) {
Class<? extends Annotation> annotClass = methodAnnotation.annotationType();
Set<Handler<RoutingContext>> handler = config.getAnnotationHandler(annotClass);
if (handler != null) {
route.attachHandlers(handler);
}
AnnotationProcessor<?> annProcessor = config.getAnnotationProcessor(methodAnnotation);
if (annProcessor != null) {
route.addProcessor(annProcessor);
}
BiConsumer<RoutingContext, ?> returnHandler = returnHandlers.get(annotClass);
if (returnHandler != null) {
route.attachReturnHandler(returnHandler);
}
}
}
private MVCRoute createRoute(HttpMethod httpMethod, String path) {
Handler<RoutingContext> authHandler = null;
String redirectURL = null;
if (auth != null) {
authHandler = authFactory.create(auth);
if (AuthMethod.REDIRECT.equals(auth.method())) {
redirectURL = auth.redirectURL();
}
}
boolean disabled = method.isAnnotationPresent(Disabled.class) || controller.isAnnotationPresent(Disabled.class);
MVCRoute route = new MVCRoute(instance, basePath + path, httpMethod, config, authHandler, disabled, usesSession);
route.setLoginRedirect(redirectURL);
return route;
}
private void createParamsHandlers() {
for (Parameter p : method.getParameters()) {
Class<?> parameterClass = p.getType();
if (Session.class.isAssignableFrom(parameterClass)) {
usesSession = true;
}
Processor typeProcessor = config.getTypeProcessor(parameterClass);
if (typeProcessor != null) {
processors.add(typeProcessor);
}
Handler<RoutingContext> handler = config.getParamHandler(parameterClass);
if (handler != null) {
paramsHandlers.add(handler);
}
createParamAnnotationHandlers(p);
}
}
private void createParamAnnotationHandlers(Parameter p) {
Annotation[] paramAnnotations = p.getAnnotations();
if (paramAnnotations != null) {
for (Annotation annotation : paramAnnotations) {
Set<Handler<RoutingContext>> paramHandler = config.getAnnotationHandler(annotation.annotationType());
if (paramHandler != null) {
paramsHandlers.addAll(paramHandler);
}
}
}
}
}