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); } } } } }