package com.github.aesteve.vertx.nubes.routing;
import com.github.aesteve.vertx.nubes.Config;
import com.github.aesteve.vertx.nubes.annotations.Blocking;
import com.github.aesteve.vertx.nubes.handlers.Processor;
import com.github.aesteve.vertx.nubes.handlers.impl.DefaultMethodInvocationHandler;
import com.github.aesteve.vertx.nubes.handlers.impl.PayloadTypeProcessor;
import com.github.aesteve.vertx.nubes.reflections.Filter;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.auth.AuthProvider;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.*;
import io.vertx.ext.web.sstore.LocalSessionStore;
import java.lang.reflect.Method;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.function.BiConsumer;
public class MVCRoute {
private final String path;
private final HttpMethod httpMethod;
private final Object instance;
private final Set<Filter> beforeFilters;
private final Set<Filter> afterFilters;
private Method mainHandler;
private final Set<Handler<RoutingContext>> handlers;
private Set<Processor> processors;
private MVCRoute redirectRoute;
private final Handler<RoutingContext> authHandler;
private String loginRedirect;
private Handler<RoutingContext> preInterceptor;
private Handler<RoutingContext> postInterceptor;
private final Config config;
private final boolean disabled;
private BiConsumer<RoutingContext, ?> returnHandler;
private final boolean usesSession;
public MVCRoute(Object instance, String path, HttpMethod method, Config config, Handler<RoutingContext> authHandler, boolean disabled, final boolean usesSession) {
this.instance = instance;
this.config = config;
this.path = path;
this.httpMethod = method;
this.beforeFilters = new TreeSet<>();
this.afterFilters = new TreeSet<>();
this.handlers = new LinkedHashSet<>();
this.processors = new LinkedHashSet<>();
this.authHandler = authHandler;
this.disabled = disabled;
this.usesSession = usesSession;
}
public boolean isEnabled() {
return !disabled;
}
public void redirectTo(MVCRoute anotherRoute) {
redirectRoute = anotherRoute;
}
public void setLoginRedirect(String loginRedirect) {
this.loginRedirect = loginRedirect;
}
public void addProcessor(Processor processor) {
processors.add(processor);
}
public void addProcessors(Set<Processor> processors) {
this.processors.addAll(processors);
}
public void addProcessorsFirst(Set<Processor> processors) {
Set<Processor> oldProcessors = new LinkedHashSet<>(this.processors);
this.processors = new LinkedHashSet<>(oldProcessors.size() + processors.size());
this.processors.addAll(processors);
this.processors.addAll(oldProcessors);
}
public void attachInterceptor(Handler<RoutingContext> handler, boolean before) {
if (before) {
this.preInterceptor = handler;
} else {
this.postInterceptor = handler;
}
}
public void attachReturnHandler(BiConsumer<RoutingContext, ?> handler) {
returnHandler = handler;
}
public void attachHandlers(Set<Handler<RoutingContext>> newHandlers) {
handlers.addAll(newHandlers);
}
public void setMainHandler(Method mainHandler) {
this.mainHandler = mainHandler;
}
public void addBeforeFilters(Set<Filter> beforeFilters) {
this.beforeFilters.addAll(beforeFilters);
}
public void addAfterFilters(Set<Filter> afterFilters) {
this.afterFilters.addAll(afterFilters);
}
public String path() {
return path;
}
public HttpMethod method() {
return httpMethod;
}
public void attachHandlersToRouter(Router router) {
config.forEachGlobalHandler(handler -> router.route(httpMethod, path).handler(handler));
final Vertx vertx = config.getVertx();
if (authHandler != null) {
attachAuthHandler(router, vertx);
} else if (usesSession) {
router.route(httpMethod, path).handler(SessionHandler.create(LocalSessionStore.create(vertx)));
}
handlers.forEach(handler ->
router.route(httpMethod, path).handler(handler)
);
attachPreProcessingHandlers(router);
boolean hasPostProcessors = redirectRoute != null || postInterceptor != null || !afterFilters.isEmpty()|| !processors.isEmpty();
setHandler(router, mainHandler, hasPostProcessors);
if (redirectRoute != null) {
// intercepted -> redirected => do not call post processing handlers
router.route(httpMethod, path).handler(ctx ->
ctx.reroute(redirectRoute.method(), redirectRoute.path())
);
}
attachPostProcessingHandlers(router);
}
private void attachPreProcessingHandlers(Router router) {
processors.forEach(processor -> router.route(httpMethod, path).handler(processor::preHandle));
int i = 0;
boolean beforeFiltersHaveNext = mainHandler != null;
for (Filter filter : beforeFilters) {
boolean hasNext = beforeFiltersHaveNext || i < beforeFilters.size() - 1;
setHandler(router, filter.method(), hasNext);
i++;
}
if (preInterceptor != null) {
router.route(httpMethod, path).handler(preInterceptor);
}
}
private void attachPostProcessingHandlers(Router router) {
if (postInterceptor != null) {
router.route(httpMethod, path).handler(postInterceptor);
}
int i = 0;
boolean afterFiltersHaveNext = !processors.isEmpty();
for (Filter filter : afterFilters) {
boolean hasNext = afterFiltersHaveNext || i < afterFilters.size() - 1;
setHandler(router, filter.method(), hasNext);
i++;
}
if (!mainHandler.getReturnType().equals(Void.TYPE) && returnHandler == null) { // try to set as payload
processors.add(new PayloadTypeProcessor(config.getMarshallers()));
}
processors.forEach(processor -> router.route(httpMethod, path).handler(processor::postHandle));
processors.forEach(processor -> router.route(httpMethod, path).handler(processor::afterAll));
}
private void attachAuthHandler(Router router, Vertx vertx) {
final AuthProvider authProvider = config.getAuthProvider();
router.route(httpMethod, path).handler(CookieHandler.create());
router.route(httpMethod, path).handler(UserSessionHandler.create(authProvider));
router.route(httpMethod, path).handler(SessionHandler.create(LocalSessionStore.create(vertx)));
router.route(httpMethod, path).handler(authHandler);
if (loginRedirect != null && !"".equals(loginRedirect)) {
router.post(loginRedirect).handler(CookieHandler.create());
router.post(loginRedirect).handler(BodyHandler.create());
router.post(loginRedirect).handler(UserSessionHandler.create(authProvider));
router.post(loginRedirect).handler(SessionHandler.create(LocalSessionStore.create(vertx)));
router.post(loginRedirect).handler(FormLoginHandler.create(authProvider));
}
}
private void setHandler(Router router, Method method, boolean hasNext) {
Handler<RoutingContext> handler = new DefaultMethodInvocationHandler<>(instance, method, config, hasNext, returnHandler);
if (method.isAnnotationPresent(Blocking.class)) {
router.route(httpMethod, path).blockingHandler(handler);
} else {
router.route(httpMethod, path).handler(handler);
}
}
@Override
public String toString() {
return "Route : " + httpMethod.toString() + " " + path();
}
}