Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grails 6.2.2: ClassCastException for inheritance in Commands #13945

Closed
agre1981 opened this issue Dec 26, 2024 · 22 comments
Closed

Grails 6.2.2: ClassCastException for inheritance in Commands #13945

agre1981 opened this issue Dec 26, 2024 · 22 comments

Comments

@agre1981
Copy link

agre1981 commented Dec 26, 2024

Expected Behavior

Grails throws exception ClassCastException when we have 2 endpoints with inherited commands. It worked fine on 6.1.2.

Looks like this is a critical issue.

class UrlMappings {
        "/my/$patientId/procedure"(controller: "my", action: "createLedgerProcedure", method: "POST")
        "/my/$patientId/procedure/insuranceEstimates"(controller: "my", action: "getInsuranceEstimates", method: "POST")
}

class MyBaseCommand implements Validateable {

    int testId
}

class MyCommand extends MyBaseCommand {

    int myId
}

class MyController {
    def getInsuranceEstimates(MyCommand command, long patientId) {
        log.info('test2 method')
        render([response: "test2 method"] as JSON)
    }

    def createLedgerProcedure(MyBaseCommand command, long patientId) {
        log.info('test1 method')
        render([response: "test1 method"] as JSON)
    }

}

Actual Behaviour

Exception:

2024-12-26 18:13:10.639 ERROR --- [nio-8080-exec-2] StackTrace                               : Full Stack Trace:

java.lang.reflect.InvocationTargetException: null
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
	at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
	at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
	at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.ClassCastException: class com.example.MyBaseCommand cannot be cast to class com.example.MyCommand (com.example.MyBaseCommand and com.example.MyCommand are in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @8f4db8)
	... 13 common frames omitted

2024-12-26 18:13:10.690 ERROR --- [nio-8080-exec-2] o.g.web.errors.GrailsExceptionResolver   : ClassCastException occurred when processing request: [POST] /my/123/procedure
class com.example.MyBaseCommand cannot be cast to class com.example.MyCommand (com.example.MyBaseCommand and com.example.MyCommand are in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @8f4db8). Stacktrace follows:

java.lang.reflect.InvocationTargetException: null
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:211)
	at org.grails.core.DefaultGrailsControllerClass.invoke(DefaultGrailsControllerClass.java:188)
	at org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:90)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:77)
	at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
	at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.ClassCastException: class com.example.MyBaseCommand cannot be cast to class com.example.MyCommand (com.example.MyBaseCommand and com.example.MyCommand are in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @8f4db8)
	... 13 common frames omitted

Steps To Reproduce

Reproduces on Grails 6.2.2. It worked fine on 6.1.2.

grails_6_2_2.zip

An example of the application has been attached:

  • run the attached application: ./gradlew bootRun
  • Send the request:

curl --location --request POST 'http://localhost:8080/my/123/procedure' \
--header 'Content-Type: application/json' \
--data '{
    "testId": 321
}'

Environment Information

  • Windows 10
  • Java:
openjdk version "11.0.22" 2024-01-16
OpenJDK Runtime Environment Temurin-11.0.22+7 (build 11.0.22+7)
OpenJDK 64-Bit Server VM Temurin-11.0.22+7 (build 11.0.22+7, mixed mode)

Example Application

No response

Version

6.2.2

@agre1981
Copy link
Author

Maybe relates to #13486

@agre1981
Copy link
Author

Class file:
MyController.zip

@jdaugherty
Copy link
Contributor

I added 2 tests for this issue under my 6.2.x branch & 7.x:

@Issue('https://github.com/grails/grails-core/issues/13945')
void "calling actions involving inherited command objects - parent command"() {
    given:
    params.name = 'Douglas'

    when:
    def model = controller.methodTakingParent()
    def commandObject = model.commandObject

    then:
    commandObject.name == 'Douglas'
}

@Issue('https://github.com/grails/grails-core/issues/13945')
void "calling actions involving inherited command objects - child command"() {
    given:
    params.name = 'Douglas'
    params.anotherName = 'Fir'

    when:
    def model = controller.methodTakingChild()
    def commandObject = model.commandObject

    then:
    commandObject.name == 'Douglas'
    commandObject.anotherName == 'Fir'
}

And the associated Commands:

class ParentCommandObject {
    String name
}

class ChildCommandObject extends ParentCommandObject {
    String anotherName
}

And the associated action:

def methodTakingParent(ParentCommandObject co) {
    [commandObject: co]
}

def methodTakingChild(ChildCommandObject co) {
    [commandObject: co]
}

Neither of these tests are failing in either branch. I'm still researching.

@agre1981
Copy link
Author

@jdaugherty could you push the tests? I'll try to help with reproducing of the issue

@jdaugherty
Copy link
Contributor

I figured out the cause of this. I'll push the tests here shortly. The name of the method & the arguments to that method seem to matter. The actual cast exception is happening on the validate() call:

image

jdaugherty added a commit that referenced this issue Dec 30, 2024

Verified

This commit was signed with the committer’s verified signature.
krassowski Michał Krassowski
@jdaugherty
Copy link
Contributor

Commit 268e386 reproduces this issue. I've gone ahead and marked the test with @PendingFeature to not fail the test run until this is fixed.

@agre1981
Copy link
Author

The test doesn't fail without Validatable for me
image

@jdaugherty
Copy link
Contributor

There seems to be some randomness. The test run passed unexpectedly so this leads me to believe there's an ordering issue involved.

@jdaugherty
Copy link
Contributor

I changed the code that calls validate in the action transformer, and this now is generated:
image

Which makes the test pass.

jdaugherty added a commit to jdaugherty/grails-core that referenced this issue Dec 30, 2024
@agre1981
Copy link
Author

agre1981 commented Dec 30, 2024

For me, it constantly fails with Validateable and constantly passes without Validateable. My command:

./gradlew :grails-test-suite-web:clean :grails-test-suite-web:singleTest --tests "org.grails.web.commandobjects.CommandObjectsSpec"

jdaugherty added a commit that referenced this issue Dec 30, 2024
jdaugherty added a commit to jdaugherty/grails-core that referenced this issue Dec 30, 2024
…ing the object""

This reverts commit e8a07aa.
jdaugherty added a commit to jdaugherty/grails-core that referenced this issue Dec 30, 2024
@jdaugherty
Copy link
Contributor

I accidentally pushed the fix for this to the 6.2.x branch, I reverted it there and then opened #13947 to fix this in 6.2.x.

@agre1981
Copy link
Author

It can make sense to add Validatable to the test to cover my "not failing" case

jdaugherty added a commit to jdaugherty/grails-core that referenced this issue Dec 30, 2024
…il prior to code fix
@agre1981
Copy link
Author

@jdaugherty Could you advise the tool for decompiling .class you use?

IntelliJ fails to decompile controller classes
image

@matrei
Copy link
Contributor

matrei commented Dec 30, 2024

Hmm, both tests run successfully for me on 33b3dc1, without the changes to ControllerActionTransformer.

@agre1981
Copy link
Author

agre1981 commented Dec 30, 2024

Could you try running the tests with clean?

./gradlew :grails-test-suite-web:clean :grails-test-suite-web:singleTest --tests "org.grails.web.commandobjects.CommandObjectsSpec"

@jdaugherty
Copy link
Contributor

@jdaugherty Could you advise the tool for decompiling .class you use?

IntelliJ fails to decompile controller classes image

I use http://java-decompiler.github.io/ to decompile groovy code.

@agre1981
Copy link
Author

I checked my original project and it doesn't fail without Validateable and fails with it.

And this looks logical because I don't expect calling validation for non-Validateable commands

@matrei
Copy link
Contributor

matrei commented Dec 30, 2024

Could you try running the tests with clean?

@agre1981 Thanks, I managed to get the tests to fail now.

jdaugherty added a commit that referenced this issue Dec 30, 2024
fix #13945 - use local variable instead of casting the object
@jdaugherty
Copy link
Contributor

The 6.2.3-SNAPSHOT build is now published. @agre1981 can you please set your grails version to this version and let me know if it fixes this issue for you?

@agre1981
Copy link
Author

@jdaugherty Yes, it works fine on Grails 6.2.3-SNAPSHOT.

Great job! thank you!

image

@jdaugherty
Copy link
Contributor

I'm going to close this ticket as fixed. I'm discussing with the other maintainers on when best to release this. Hopefully it's soon.

@jdaugherty
Copy link
Contributor

We have released 6.2.3 to address this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants