package com.atlassian.stash.internal.web.filters;

import org.springframework.web.context.request.async.WebAsyncUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;

/**
 * This filter exists to work around an issue with plugins that use SpringMVC. Spring's {@code OncePerRequestFilter}
 * calls {@code WebAsyncUtils.getAsyncManager(ServletRequest)} to get a {@code WebAsyncManager} to determine if the
 * request is asynchronous. {@code getAsyncManager} caches the {@code WebAsyncManager} it creates as an attribute on
 * the {@code ServletRequest}.
 * <p>
 * Since the host application and the plugin framework are using Spring classes from different {@code ClassLoader}s,
 * that cached {@code WebAsyncManager} causes {@code ClassCastException}s for plugins which use SpringMVC.
 * <p>
 * To avoid those exceptions, this filter detects when Spring attempts to cache its {@code WebAsyncManager} and
 * ignores the call, ensuring it's never added to the request. Each call to {@code getAsyncManager(ServletRequest}
 * will just create a new one, which is then immediately thrown away. But that means the part of the request that
 * is handled in the host application creates instances from its {@code ClassLoader}, and the part that is handled
 * in the plugin framework creates instances from the OSGi {@code ClassLoader}.
 */
public class IgnoreWebAsyncManagerFilter implements Filter {

    @Override
    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        if (servletRequest instanceof HttpServletRequest) {
            servletRequest = new HttpServletRequestWrapper((HttpServletRequest) servletRequest) {

                @Override
                public void setAttribute(String name, Object o) {
                    if (!WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE.equals(name)) {
                        super.setAttribute(name, o);
                    }
                }
            };
        }

        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
}
