OpenLiberty / open-liberty

Open Liberty is a highly composable, fast to start, dynamic application server runtime environment
https://openliberty.io
Eclipse Public License 2.0
1.14k stars 587 forks source link

ClassCastException raised if ServletContext provided to jspInit() is wrapped #29089

Closed rlubke closed 3 weeks ago

rlubke commented 1 month ago

Describe the bug

We have an application that extends JSP pages with a custom JspServlet implementation.

<%@ page extends="app.api50.JspServlet" %>

This JspServlet implements the HttpJspPage interface. An aspect of this custom page implementation is getServletConfig() is overridden to return a wrapped ServletConfig which returns a wrapped ServletContext.

When a request is made to our extended JSPs, the following error is raised:

Caused by: java.lang.ClassCastException: class app.api60.ServletContextWrapper cannot be cast to class com.ibm.wsspi.webcontainer.servlet.IServletContext (app.api60.ServletContextWrapper is in unnamed module of loader com.ibm.ws.classloading.internal.AppClassLoader @33458bd0; com.ibm.wsspi.webcontainer.servlet.IServletContext is in unnamed module of loader org.eclipse.osgi.internal.loader.EquinoxClassLoader @69ac0557)
    at com.ibm.wsspi.webcontainer.annotation.AnnotationHelperManager.unwrapServletContext(AnnotationHelperManager.java:124)
    at [internal classes]
    at com.ibm._jsp._index._jspInit(_index.java:134)
    at app.api50.JspServlet.init(JspServlet.java:57)
    at com.ibm.ws.webcontainer.servlet.ServletWrapper.init(ServletWrapper.java:301)

This wrapping worked fine when using <feature>jsp-2.3</feature>, but fails when moving to <feature>pages-3.1</feature>

Steps to Reproduce
Extend a JSP page with a custom servlet/page implementation that wraps the default ServletConfig returned from getServletConfig(). This custom ServletConfig also returns a custom ServletContext that wraps the default ServletContext

Expected behavior
The JSP page to be rendered.

Diagnostic information:

jhanders34 commented 1 month ago

Could you provide some details about what the class structure is for app.api60.ServletContextWrapper, i.e. what does it extends and implements?

epj commented 1 month ago

It would also be very helpful to see the trace output at the time of this exception. Please set your trace specification to: *=info:com.ibm.ws.webcontainer*=all:com.ibm.wsspi.webcontainer*=all:HTTPChannel=all:GenericBNF=all:com.ibm.ws.jsp=all

rlubke commented 1 month ago

Could you provide some details about what the class structure is for app.api60.ServletContextWrapper, i.e. what does it extends and implements?

The wrapper implements ServletContext (Jakarta Servlet 6.0) and extends our internal base class where the actual functionality exists so we can support both Jakarta 5 and 6 (removal of classes in 6 muddied things a bit hence this approach)

It would also be very helpful to see the trace output at the time of this exception. Please set your trace specification to: *=info:com.ibm.ws.webcontainer*=all:com.ibm.wsspi.webcontainer*=all:HTTPChannel=all:GenericBNF=all:com.ibm.ws.jsp=all

Sure, I'll work on that today.

rlubke commented 1 month ago

Here's the console and trace logs with the requested trace specification. logs.tar.gz

epj commented 1 month ago

The file trace_24.07.18_08.00.41.0.log ends before the exception which happens at 08:00:42:126. Was there a later trace file that includes the time of the exception?

rlubke commented 1 month ago

My mistake - here you go. trace.log.gz

volosied commented 1 month ago

@rlubke

Thanks for you patience. I've looked into this issue, and was able to reproduce the problem on my end, regardless of which jsp / pages feature is enabled.

You had said: This wrapping worked fine when using jsp-2.3, but fails when moving to pages-3.1

By wrapping mention above, are are referring to the wrapped ServletConfig, the wrapped ServletContext, or both?

I don't see how it would work on one feature, but fail on another. The liberty implementation code around area is all the same. If the earlier features don't use a wrapped ServletContext, then I would expect it to work.

The only way I've managed to avoid this so far, is by returning the original servlet context in the ServletConfigWrapper. However, based on what you said, I don't think this approach will work for you since your trying to have a custom ServletContext object.

public class ServletConfigWrapper implements ServletConfig {

....

private ServletContextWrapper context = null;

@Override
public ServletContext getServletContext() {
    return (ServletContext) context;   // fails -- returns wrapped 
    // return context.getServletContext(); // works  -- returns original servlet context 
}

public void setServletContext(ServletContextWrapper context) {

    this.context = context;
}

With the way I see the flow of things, I don't think there's a way for us to avoid this ClassClassException with the current code. There's not a way we (liberty) can unwrap the ServletContext without some Liberty SPI method (Servlet API does not have any wrappers).

I think if I create a ServletContextWrapper interface in the SPI, you could use it in your code and we can call unwrap in our implementation.

Let me see if I can get this proposed approach to work. Thanks!

Edit: IServletContext and ServletContextFacade are not publicly exposed, but so removed their mention.

rlubke commented 1 month ago

@volosied Thank you for looking into this!

You had said: This wrapping worked fine when using jsp-2.3, but fails when moving to pages-3.1 By wrapping mention above, are are referring to the wrapped ServletConfig, the wrapped ServletContext, or both?

Yes, both. We test our "wrapped" application against multiple container providers. In our working job for Open Liberty, we use version 23.0.0.5 with the following configuration:

     <featureManager>
        <feature>appSecurity-3.0</feature>
        <feature>jsp-2.3</feature>
    </featureManager>

And no CCE is raised and all tests pass.

As to the unwrapping on your side, I understand. Our use case is fairly unique; provide custom session integration in a portable manner without having to understand container internals and providing SPI implementations as we test on so many different containers. Given this, do you have pointers on how one would implement custom session integration with OpenLiberty (docs or otherwise)? For example, Tomcat allows an application to deploy with a custom Manager implementation to deal with sessions.

volosied commented 1 month ago

Is there any chance you could create a sample app reproducing the problem? I'm able to reproduce this problem across all JSP features with my app, so I may not have exact same scenario (even though I encounter the same ClassCastException).

I'll create a git repo showing what my app looks like -- perhaps you can point out any differences or use that as a base for the reproducer.

I'll have it up tomorrow.


As for the proposed solution, I mentioned, it won't work, unfortunately, as I was mistaken. SPIs are for user features, not the applications. Your use-case is an application, not a feature, correct?

The best step forward is understand how it worked in jsp-2.3 and not in the pages-3.1 feature.

volosied commented 1 month ago

Here's my app: https://github.com/volosied/OLGH29089/tree/main

@rlubke

rlubke commented 1 month ago

Thanks, I'll take a look and follow up as soon as possible.

rlubke commented 3 weeks ago

Yes, you are correct. This was an error on my part. After taking a closer look at the older code, there was an exception for OpenLiberty where entity that was responsible for wrapping the ServletContext actually doesn't wrap it and instead returns the underlying container context. Sorry for the noise!