org.debux.webmotion.server.handler.ExecutorMethodInvokerHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of toolkit-server Show documentation
Show all versions of toolkit-server Show documentation
ObServe Toolkit Server module
/*
* #%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;
}
}
}