co.paralleluniverse.springframework.web.method.support.FiberInvocableHandlerMethod.java Source code

Java tutorial

Introduction

Here is the source code for co.paralleluniverse.springframework.web.method.support.FiberInvocableHandlerMethod.java

Source

/*
 * COMSAT
 * Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 3.0
 * as published by the Free Software Foundation.
 */
/*
 * Based on org.springframework.web.method.support.InvocableHandlerMethod in
 * Spring Framework Web MVC.
 * Copyright the original author Rossen Stoyanchev.
 * Released under the ASF 2.0 license.
 */
package co.paralleluniverse.springframework.web.method.support;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.method.HandlerMethod;

import co.paralleluniverse.fibers.Fiber;
import co.paralleluniverse.fibers.SuspendExecution;
import co.paralleluniverse.fibers.Suspendable;
import co.paralleluniverse.fibers.instrument.SuspendableHelper;
import co.paralleluniverse.strands.SuspendableRunnable;
import java.util.Arrays;
import java.util.concurrent.Callable;
import org.springframework.web.method.support.InvocableHandlerMethod;

/**
 * A fiber-blocking {@link InvocableHandlerMethod}
 *
 * @author circlespainter
 */
public class FiberInvocableHandlerMethod extends InvocableHandlerMethod {
    // TODO need avoiding async error handling due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=454022, remove as sson as it's fixed
    private static final String SPRING_BOOT_ERROR_CONTROLLER_CLASS_NAME = "org.springframework.boot.autoconfigure.web.ErrorController";
    private static Class springBootErrorControllerClass;
    static {
        try {
            springBootErrorControllerClass = Class.forName(SPRING_BOOT_ERROR_CONTROLLER_CLASS_NAME);
        } catch (ClassNotFoundException e) {
        }
    }

    public FiberInvocableHandlerMethod(Object bean, Method method) {
        super(bean, method);
    }

    public FiberInvocableHandlerMethod(HandlerMethod handlerMethod) {
        super(handlerMethod);
    }

    public FiberInvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)
            throws NoSuchMethodException {
        super(bean, methodName, parameterTypes);
    }

    /**
     * @return `true` if the controller is a Spring Boot error controller
     */
    private boolean isBootErrorController() {
        return springBootErrorControllerClass != null
                && springBootErrorControllerClass.isAssignableFrom(getBean().getClass());
    }

    /**
     * @return `true` if the method is not annotated with `@Suspendable` and it doesn't throw `SuspendExecution` nor it is instrumented in any other way.
     */
    private boolean isSpringTraditionalThreadBlockingControllerMethod() {
        final Method m = getMethod();
        return m.getAnnotation(Suspendable.class) == null
                && !Arrays.asList(m.getExceptionTypes()).contains(SuspendExecution.class)
                && !SuspendableHelper.isInstrumented(m);
    }

    /**
     * Invoke the handler method with the given argument values, either traditional thread-blocking or in a new fiber.
     */
    @Override
    protected Object doInvoke(final Object... args) throws Exception {
        if (isBootErrorController() // TODO need avoiding async error handling due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=454022, remove as sson as it's fixed
                || isSpringTraditionalThreadBlockingControllerMethod())
            return threadBlockingInvoke(args);
        else
            return fiberDispatchInvoke(args);
    }

    protected Object threadBlockingInvoke(Object... args) throws IllegalAccessException, Exception {
        return super.doInvoke(args);
    }

    protected Object fiberDispatchInvoke(final Object... args) {
        final Object b = getBean();
        final Method m = getBridgedMethod();
        ReflectionUtils.makeAccessible(m);

        // Returning deferred even for normal return values, Spring return handlers will take care dynamically based on the actual returned value
        final DeferredResult ret = new DeferredResult();

        // The actual method execution and deferred completion is dispatched to a new fiber
        new Fiber(new SuspendableRunnable() {
            private Object deAsync(final Object o) throws SuspendExecution, Exception {
                if (o instanceof Callable)
                    return deAsync(((Callable) o).call());
                else
                    return o;
            }

            @Override
            public void run() throws SuspendExecution, InterruptedException {
                try {
                    Object originalRet = m.invoke(b, args);
                    ret.setResult(deAsync(originalRet));
                } catch (IllegalArgumentException ex) {
                    assertTargetBean(m, b, args);
                    ret.setErrorResult(
                            new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex));
                } catch (InvocationTargetException ex) {
                    // Unwrap for HandlerExceptionResolvers ...
                    Throwable targetException = ex.getTargetException();
                    if (targetException instanceof RuntimeException || targetException instanceof Error
                            || targetException instanceof Exception) {
                        ret.setErrorResult(targetException);
                    } else {
                        String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
                        ret.setErrorResult(new IllegalStateException(msg, targetException));
                    }
                } catch (Exception ex) {
                    ret.setErrorResult(ex);
                }
            }
        }).start();

        return ret;
    }

    /**
     * Assert that the target bean class is an instance of the class where the given
     * method is declared. In some cases the actual controller instance at request-
     * processing time may be a JDK dynamic proxy (lazy initialization, prototype
     * beans, and others). {@code @Controller}'s that require proxying should prefer
     * class-based proxy mechanisms.
     */
    private void assertTargetBean(Method method, Object targetBean, Object[] args) {
        Class<?> methodDeclaringClass = method.getDeclaringClass();
        Class<?> targetBeanClass = targetBean.getClass();
        if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
            String msg = "The mapped controller method class '" + methodDeclaringClass.getName()
                    + "' is not an instance of the actual controller bean instance '" + targetBeanClass.getName()
                    + "'. If the controller requires proxying "
                    + "(e.g. due to @Transactional), please use class-based proxying.";
            throw new IllegalStateException(getInvocationErrorMessage(msg, args));
        }
    }

    private String getInvocationErrorMessage(String message, Object[] resolvedArgs) {
        StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message));
        sb.append("Resolved arguments: \n");
        for (int i = 0; i < resolvedArgs.length; i++) {
            sb.append("[").append(i).append("] ");
            if (resolvedArgs[i] == null) {
                sb.append("[null] \n");
            } else {
                sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] ");
                sb.append("[value=").append(resolvedArgs[i]).append("]\n");
            }
        }
        return sb.toString();
    }
}