package io.mangoo.routing.handlers; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.google.inject.Inject; import io.mangoo.annotations.FilterWith; import io.mangoo.configuration.Config; import io.mangoo.core.Application; import io.mangoo.crypto.Crypto; import io.mangoo.enums.Required; import io.mangoo.helpers.RequestHelper; import io.mangoo.i18n.Messages; import io.mangoo.interfaces.MangooRequestFilter; import io.mangoo.routing.Attachment; import io.mangoo.routing.listeners.MetricsListener; import io.mangoo.templating.TemplateEngine; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; /** * Main class for dispatching a request to the request chain. * The request chain contains the following handlers in order: * * DispatcherHandler * LimitHandler * LocalHandler * InboundCookiesHandler * FormHandler * RequestHandler * OutboundCookiesHandler * ResponseHandler * * @author svenkubiak * */ public class DispatcherHandler implements HttpHandler { private static final Logger LOG = LogManager.getLogger(DispatcherHandler.class); private Config config; private Method method; private List<Annotation> methodAnnotations = new ArrayList<>(); private List<Annotation> classAnnotations = new ArrayList<>(); private Messages messages; private Crypto crypto; private Map<String, Class<?>> methodParameters; private Class<?> controllerClass; private String controllerClassName; private String controllerMethodName; private boolean hasRequestFilter; private TemplateEngine templateEngine = Application.getInstance(TemplateEngine.class); private String username; private String password; private int limit; private int methodParametersCount; private boolean blocking; private boolean timer; private final RequestHelper requestHelper; @Inject public DispatcherHandler(Config config, RequestHelper requestHelper) { this.config = Objects.requireNonNull(config, Required.CONFIG.toString()); this.requestHelper = Objects.requireNonNull(requestHelper, Required.REQUEST_HELPER.toString()); } public DispatcherHandler dispatch(Class<?> controllerClass, String controllerMethodName) { Objects.requireNonNull(controllerClass, Required.CONTROLLER_CLASS.toString()); Objects.requireNonNull(controllerMethodName, Required.CONTROLLER_METHOD.toString()); this.messages = Application.getInstance(Messages.class); this.crypto = Application.getInstance(Crypto.class); this.controllerClass = controllerClass; this.controllerMethodName = controllerMethodName; this.controllerClassName = controllerClass.getSimpleName(); this.methodParameters = getMethodParameters(); this.methodParametersCount = this.methodParameters.size(); this.hasRequestFilter = Application.getInjector().getAllBindings().containsKey(com.google.inject.Key.get(MangooRequestFilter.class)); try { this.method = Application.getInstance(this.controllerClass) .getClass() .getMethod(this.controllerMethodName, this.methodParameters.values().toArray(new Class[0])); for (Annotation annotation : this.method.getAnnotations()) { if (annotation.annotationType().equals(FilterWith.class)) { this.methodAnnotations.add(annotation); } } } catch (NoSuchMethodException | SecurityException e) { LOG.error("Failed to create DispatcherHandler", e); } for (Annotation annotation : controllerClass.getAnnotations()) { if (annotation.annotationType().equals(FilterWith.class)) { this.classAnnotations.add(annotation); } } return this; } public DispatcherHandler isBlocking(boolean blocking) { this.blocking = blocking; return this; } public DispatcherHandler withInternalTemplateEngine() { this.templateEngine = Application.getInternalTemplateEngine(); return this; } public DispatcherHandler withTimer(boolean timer) { this.timer = timer; return this; } public DispatcherHandler withUsername(String username) { this.username = username; return this; } public DispatcherHandler withPassword(String password) { this.password = password; return this; } public DispatcherHandler withLimit(int limit) { this.limit = limit; return this; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { if ( (this.requestHelper.isPostPutPatch(exchange) || this.blocking) && exchange.isInIoThread()) { exchange.dispatch(this); return; } if (this.config.isAdminEnabled()) { exchange.addExchangeCompleteListener(new MetricsListener(System.currentTimeMillis())); } final Attachment attachment = Attachment.build() .withControllerInstance(Application.getInstance(this.controllerClass)) .withControllerClass(this.controllerClass) .withControllerClassName(this.controllerClassName) .withControllerMethodName(this.controllerMethodName) .withClassAnnotations(this.classAnnotations) .withMethodAnnotations(this.methodAnnotations) .withMethodParameters(this.methodParameters) .withMethod(this.method) .withMethodParameterCount(this.methodParametersCount) .withRequestFilter(this.hasRequestFilter) .withRequestParameter(this.requestHelper.getRequestParameters(exchange)) .withMessages(this.messages) .withTimer(this.timer) .withLimit(this.limit) .withUsername(this.username) .withPassword(this.password) .withTemplateEngine(this.templateEngine) .withCrypto(this.crypto); exchange.putAttachment(RequestHelper.ATTACHMENT_KEY, attachment); nextHandler(exchange); } /** * Converts the method parameter of a mapped controller method to a map * * @return A Map containing the declared methods of the method parameters and their class type */ private Map<String, Class<?>> getMethodParameters() { final Map<String, Class<?>> parameters = new LinkedHashMap<>(); //NOSONAR for (final Method declaredMethod : this.controllerClass.getDeclaredMethods()) { if (declaredMethod.getName().equals(this.controllerMethodName) && declaredMethod.getParameterCount() > 0) { Arrays.asList(declaredMethod.getParameters()).forEach(parameter -> parameters.put(parameter.getName(), parameter.getType())); //NOSONAR break; } } return parameters; } /** * Handles the next request in the handler chain * * @param exchange The HttpServerExchange * @throws Exception Thrown when an exception occurs */ @SuppressWarnings("all") private void nextHandler(HttpServerExchange exchange) throws Exception { Application.getInstance(LimitHandler.class).handleRequest(exchange); } }