4

I want to use the cross context feature in a Spring application so I can import some webapp1 JSP into a webapp2 JSP. I'm using Eclipse STS with the included Tomcat 7.0.42 (vFabric TC Server) and Spring Framework 3.2.8.

I have configured the Tomcat conf/context.xml to have: `

<Context crossContext="true">...</Context>`. 

In the webapp2 JSP I use `

<c:import context="/webapp1" url="/myurl" />`.

When I call the webapp2 JSP I have this error:

HTTP Status 500 - javax.servlet.ServletException: javax.servlet.jsp.JspException: `java.lang.ClassCastException:` `org.springframework.web.context.request.async.WebAsyncManager cannot be cast to` org.springframework.web.context.request.async.WebAsyncManager`

Has anyone else encountered this?

Simmant
  • 1,477
  • 25
  • 39
Marco Gasparetto
  • 463
  • 5
  • 10

4 Answers4

3

Seems like Spring is not ready for cross context request processing (at least not without a bit of hacking).

FrameworkServlet always tries to get WebAsyncManager from the request attributes. And the way it is extracted can not work across different contexts (class loaders).

I see two possibilities how to workaround this:

  • Implement your own include JSP tag, which will wrap the original request so that Spring specific attributes are not visible (usually the ones starting with org.springframework) to the second context.
  • Put Spring JARs in a shared class loader path (that would be probably the easier way).
Pavel Horal
  • 17,782
  • 3
  • 65
  • 89
  • In the past I heard that putting Spring JARs in shared libs is bad (because Spring uses singletons and static vars). Isn't true anymore? – Marco Gasparetto Mar 02 '14 at 16:39
  • I am not aware of any component which would not work. – Pavel Horal Mar 02 '14 at 17:50
  • I've done a quick test with my two webapps loading Spring JARs from the Tomcat common lib directory. Now the cross context request works! Thank you – Marco Gasparetto Mar 02 '14 at 20:59
  • @PavelHoral Can you please elaborate(Implement your own include JSP tag, which will wrap the original request so that Spring specific attributes are not visible (usually the ones starting with org.springframework) to the second context.) you have mentioned here. As i am facing similar issue that has been mentioned here. – Mohit Singh Jul 21 '16 at 10:40
  • @Moitt Are you interested in the request wrapper? – Pavel Horal Jul 22 '16 at 07:21
1

Atlassian seems to have a work-around by using this filter:

https://docs.atlassian.com/atlassian-confluence/latest/com/atlassian/confluence/internal/web/filter/spring/IgnoreWebAsyncManagerFilter.html

According to the documentation:

This filter exists to work around an issue with plugins that use SpringMVC. Spring's OncePerRequestFilter calls WebAsyncUtils.getAsyncManager(ServletRequest) to get a WebAsyncManager to determine if the request is asynchronous. getAsyncManager caches the WebAsyncManager it creates as an attribute on the ServletRequest.

Since the host application and the plugin framework are using Spring classes from different ClassLoaders, that cached WebAsyncManager causes ClassCastExceptions for plugins which use SpringMVC.

To avoid those exceptions, this filter detects when Spring attempts to cache its WebAsyncManager and ignores the call, ensuring it's never added to the request. Each call to 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 ClassLoader, and the part that is handled in the plugin framework creates instances from the OSGi ClassLoader.

Maybe you can try to get the source code, If you do so please reply and send me the class, or you can reproduce the behaviour. Seems to create a new object on each request so it avoids serializing it for cross context.

Sylvain Lecoy
  • 947
  • 6
  • 15
0

I came across this issue today and based on @Sylvain Lecoy's answer I came up with this implementation of a servlet filter that acts in the way the Atlassian filter javadoc describes. Putting it before any filter that calls WebAsyncUtils.getAsyncManager(ServletRequest) seems to fix any classloader/cross-context issues.

public class IgnoreWebAsyncManagerFilter implements Filter {

static class IgnoreWebAsyncManagerCacheServletRequest extends HttpServletRequestWrapper {

    /**
     * Creates a ServletRequest adaptor wrapping the given request object.
     *
     * @param request the {@link ServletRequest} to wrap.
     * @throws IllegalArgumentException if the request is null
     */
    IgnoreWebAsyncManagerCacheServletRequest(HttpServletRequest request) {
        super(request);
    }

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


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

/**
 * {@inheritDoc}
 */
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    chain.doFilter(new IgnoreWebAsyncManagerCacheServletRequest((HttpServletRequest) request), response);
}

/**
 * {@inheritDoc}
 */
@Override
public void destroy() {
}

Note: This assumes all requests are HttpServletRequest instances but could be modified to be more safe.

Woodham
  • 4,053
  • 2
  • 20
  • 15
0

We fought with this exception in our hybrid applications (development: Spring Boot app / production: Liferay portlet; the exception occured on our Liferay servers) for several days.

In our case disabling all (unneeded) servlet filters helped:

Meik Behnfeldt
  • 117
  • 3
  • 9