All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.debux.webmotion.server.handler.ExecutorMethodInvokerHandler Maven / Gradle / Ivy

There is a newer version: 10.0.0
Show newest version
/*
 * #%L
 * Toolkit :: Server
 * %%
 * Copyright (C) 2017 - 2024 Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * .
 * #L%
 */
package org.debux.webmotion.server.handler;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.debux.webmotion.server.WebMotionContextable;
import org.debux.webmotion.server.WebMotionController;
import org.debux.webmotion.server.WebMotionException;
import org.debux.webmotion.server.WebMotionFilter;
import org.debux.webmotion.server.WebMotionHandler;
import org.debux.webmotion.server.call.Call;
import org.debux.webmotion.server.call.Executor;
import org.debux.webmotion.server.tools.upload.FileProgressListener;
import org.debux.webmotion.server.call.HttpContext;
import org.debux.webmotion.server.call.ServerContext;
import org.debux.webmotion.server.mapping.Action;
import org.debux.webmotion.server.mapping.Config;
import org.debux.webmotion.server.mapping.Mapping;
import org.debux.webmotion.server.mapping.Rule;
import org.debux.webmotion.server.render.Render;
import org.debux.webmotion.server.tools.HttpUtils;

import javax.servlet.AsyncContext;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Invokes methods of filters then action method. If a filter returns the
 * render, the render action is avoided. Moreover the class manages async request,
 * you can configure the thread pool.
 *
 * @author julien
 */
public class ExecutorMethodInvokerHandler extends AbstractHandler implements WebMotionHandler {

    private static final Logger log = LogManager.getLogger(ExecutorMethodInvokerHandler.class);
    /**
     * Context in controller
     */
    protected final WebMotionContextable context;
    /**
     * Pool async request
     */
    protected final ExecutorService threadPool;

    public ExecutorMethodInvokerHandler(WebMotionContextable context, ExecutorService threadPool) {
        this.context = context;
        this.threadPool = threadPool;
    }

    public ExecutorMethodInvokerHandler() {
        this(new WebMotionContextable(), Executors.newFixedThreadPool(100));
    }

    @Override
    public void handle(Mapping mapping, Call call) {
        HttpContext context = call.getContext();
        HttpServletRequest request = context.getRequest();
        HttpServletResponse response = context.getResponse();
        ServletContext servletContext = context.getServletContext();

        // Search if the request is execute to sync or async mode
        boolean isSyncRequest = true;
        Rule rule = call.getRule();
        if (rule != null) {
            Action action = rule.getAction();
            Config config = mapping.getConfig();
            Boolean async = action.getAsync();

            isSyncRequest = request.getDispatcherType() == DispatcherType.INCLUDE
                    || async == null && !config.isAsync()
                    || async != null && !async;
        }

        call.setAsync(!isSyncRequest);

        // Create handler to process the request
        RunnableHandler runnableHandler = new RunnableHandler(mapping, call);

        // Execute the request
        log.debug("is Async " + !isSyncRequest + " for url " + context.getUrl());
        if (isSyncRequest) {
            runnableHandler.handle(mapping, call);

        } else {
            // Only the first request is execute at async mode
            AsyncContext asyncContext;
            if (HttpUtils.isTomcatContainer(servletContext)) {
                request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
                // Tomcat patch : force the dispatcher type
                asyncContext = request.startAsync(new HttpServletRequestWrapper(request) {
                    @Override
                    public DispatcherType getDispatcherType() {
                        return DispatcherType.INCLUDE;
                    }
                }, response);

            } else {
                asyncContext = request.startAsync();
            }

            // Set timeout to negative value otherwise no run glassfish server
            asyncContext.setTimeout(-1);
            asyncContext.addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent event) {
                    log.debug("onComplete " + event);
                }

                @Override
                public void onTimeout(AsyncEvent event) {
                    log.warn("onTimeout " + event);
                }

                @Override
                public void onError(AsyncEvent event) {
                    log.error("onError " + event, event.getThrowable());
                }

                @Override
                public void onStartAsync(AsyncEvent event) {
                    log.debug("onStartAsync");
                }

            });

            threadPool.execute(runnableHandler);
        }
    }

    /**
     * Runnable use direct execute or async execute the filters and the action.
     */
    public class RunnableHandler implements Runnable, WebMotionHandler {

        /**
         * Current mapping
         */
        protected Mapping mapping;

        /**
         * Current call
         */
        protected Call call;

        /**
         * Current filters executed.
         */
        protected ListIterator filtersIterator;

        /**
         * Mark if the render is executed
         */
        protected boolean executed;

        public RunnableHandler(Mapping mapping, Call call) {
            this.mapping = mapping;
            this.call = call;

            this.filtersIterator = call.getFilters().listIterator();
            this.executed = false;
        }

        @Override
        public void handlerCreated(Mapping mapping, ServerContext context) {
            throw new UnsupportedOperationException("Not call.");
        }

        @Override
        public void handlerInitialized(Mapping mapping, ServerContext context) {
            throw new UnsupportedOperationException("Not call.");
        }

        @Override
        public void handlerDestroyed(Mapping mapping, ServerContext context) {
            throw new UnsupportedOperationException("Not call.");
        }

        @Override
        public void run() {
            handle(mapping, call);
        }

        @Override
        public void handle(Mapping mapping, Call call) {
            // Process action and filters
            Render render = call.getRender();
            Executor executor = call.getExecutor(); // Search if the call contains a action

            if (render == null || !executed) {
                if (filtersIterator.hasNext()) {
                    processFilter(mapping, call);

                } else if (executor != null) {
                    processAction(mapping, call);

                } else {
                    // The call contains already the render
                    processResponse(mapping, call);
                }
            }
        }

        /**
         * Process the action.
         *
         * @param mapping TODO
         * @param call    TODO
         */
        public void processAction(Mapping mapping, Call call) {
            Executor executor = call.getExecutor();
            call.setCurrent(executor);
            processHandlers(mapping, call);

            try {

                WebMotionController instance = executor.getInstance();
                context.create(this, mapping, call);
                instance.setContextable(context);

                Map parameters = executor.getParameters();
                Object[] toArray = parameters.values().toArray();

                Method actionMethod = executor.getMethod();
                Object returnMethod = actionMethod.invoke(instance, toArray);
                Render render = processRender(mapping, returnMethod);

                // Check if is the last executor is finished to remove the thread local
                List filters = call.getFilters();
                if (filters.isEmpty()) {
                    context.remove();
                }

                call.setRender(render);
                processResponse(mapping, call);

            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                context.remove();
                throw new WebMotionException(String.format("Error during invoke method for action %s on method %s", executor.getClazz().getName(), executor.getMethod().getName()), ex, executor.getRule());

            }
        }

        /**
         * Process one filter.
         *
         * @param mapping TODO
         * @param call    TODO
         */
        public void processFilter(Mapping mapping, Call call) {
            Executor executor = filtersIterator.next();
            call.setCurrent(executor);
            processHandlers(mapping, call);

            try {
                WebMotionFilter filterInstance = (WebMotionFilter) executor.getInstance();
                context.create(this, mapping, call);
                filterInstance.setContextable(context);

                Map parameters = executor.getParameters();
                Object[] toArray = parameters.values().toArray();

                Method filterMethod = executor.getMethod();
                Render render = (Render) filterMethod.invoke(filterInstance, toArray);

                // Check if is the last executor is finished to remove the thread local
                Executor firstFilter = call.getFilters().get(0);
                if (executor == firstFilter) {
                    context.remove();
                }

                if (render != null) {
                    call.setRender(render);
                    processResponse(mapping, call);
                }

            } catch (IllegalAccessException | IllegalArgumentException ex) {
                context.remove();

                throw new WebMotionException(String.format("Error during invoke method for filter %s on method %s", executor.getClazz().getName(), executor.getMethod().getName()), ex, executor.getRule());

            } catch (InvocationTargetException ex) {
                context.remove();

                Throwable cause = ex.getCause();
                if (cause instanceof WebMotionException
                        && ((WebMotionException) cause).getRule() != null) {
                    throw (WebMotionException) cause;

                } else {
                    throw new WebMotionException(String.format("Error during invoke method for filter %s on method %s", executor.getClazz().getName(), executor.getMethod().getName()), ex, executor.getRule());
                }
            }
        }

        /**
         * Insert a filter dynamic from a another filer
         *
         * @param executor pass the
         */
        public void chainFilter(Executor executor) {
            filtersIterator.add(executor);
            filtersIterator.previous();
            this.handle(mapping, call);
        }

        /**
         * Setting up the elements for the response :
         * 
    *
  • save the client session
  • *
  • execute the render
  • *
  • remove attribute in session for file progress
  • *
* * @param mapping TODO * @param call TODO */ public void processResponse(Mapping mapping, Call call) { // Before render, store the client session HttpContext context = call.getContext(); try { Render render = call.getRender(); log.debug("Render = " + render); if (render != null) { render.exec(mapping, call); } executed = true; } catch (IOException ioe) { throw new WebMotionException("Error during write the render in response", ioe, call.getRule()); } catch (ServletException se) { throw new WebMotionException("Error on server when write the render in response", se, call.getRule()); } // After render, remove file progress from session if (call.isFileUploadRequest()) { HttpSession session = context.getSession(); if (session != null) { session.removeAttribute(FileProgressListener.SESSION_ATTRIBUTE_NAME); } } } /** * Execute the handler before each filters and action. * * @param mapping TODO * @param call TODO */ public void processHandlers(Mapping mapping, Call call) { List executorHandlers = call.getExecutorHandlers(); for (WebMotionHandler handler : executorHandlers) { handler.handle(mapping, call); } } /** * Return the render if the returnMethod is a render otherwise return an * instance of defaut render else null. * * @param mapping current mapping * @param returnMethod object return by controller's method * @return render */ protected Render processRender(Mapping mapping, Object returnMethod) { Config config = mapping.getConfig(); String defaultRender = config.getDefaultRender(); if (returnMethod instanceof Render) { return (Render) returnMethod; } else if (defaultRender != null && !"".equals(defaultRender)) { try { Class clazz = Class.forName(defaultRender); Constructor constructor = clazz.getConstructors()[0]; return (Render) constructor.newInstance(returnMethod); } catch (Exception ex) { Executor executor = call.getCurrent(); throw new WebMotionException(String.format("Error during create render %s for %s on method %s", defaultRender, executor.getClazz().getName(), executor.getMethod().getName()), ex, executor.getRule()); } } return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy