jdaugherty opened a new pull request, #16693: URL: https://github.com/apache/grails-core/pull/16693
### Summary Fixes #16692. A data service method returning an `Observable` of a scalar property projection, such as `Observable<String> findBookAuthor(String title)`, failed at runtime with `GroovyCastException: Cannot cast object 'Stephen King' with class 'java.lang.String' to class 'java.lang.Iterable'` from the generated transactional body. ### Root cause The RxJava adapters in `grails-datamapping-rx` wrap every synchronous implementer: `SingleResultAdapter` for the single-result ones and `ObservableResultAdapter` for the iterable ones. For a projection implementer, both adapters asked the implementer whether the *projected* type is compatible and neither checked that the method's return type is its own reactive type: - `SingleResultAdapter` accepted `Observable<String> findBookAuthor` for `FindOnePropertyProjectionImplementer`, so the body found one value while `RxScheduleIOTransformation` typed the transactional delegate as returning `Iterable<String>`. That is the cast in the issue. - `ObservableResultAdapter` accepted `Single<BookInfo> findByTitle` for `FindAllByInterfaceProjectionImplementer`, so the body built a list that then failed to cast to `BookInfo`. - `ObservableResultAdapter.implement` also took the Observable's generic type as the domain class of a property projection, so the one spelling only it could implement, `Observable<String> listBookAuthor(String title)`, failed at compile time with `No property exists on domain class [java.lang.String]`. ### Fix Each adapter now requires its own reactive return type before delegating to a projection implementer, and `ObservableResultAdapter` leaves the domain class alone for a projection. An `Observable` of a projection therefore lists every projected value and completes (empty when nothing matches), and a `Single` of a projection finds one, in line with how `Observable<Book>` and `Single<Book>` already behave. ### Tests - `RxServiceImplSpec` (`grails-datamapping-core-test`, against `SimpleMapDatastore`): `Observable<String> findBookAuthor` emits every matching author and completes empty without a match; `Single<BookInfo> findByTitle` returns the interface projection. Both failed before the fix with the cast exceptions above. - `ObservableServiceTransformSpec`: `Observable<String> findBookAuthor` and `listBookAuthor` are implemented by `FindAllPropertyProjectionImplementer`, `Single<IFoo> findByNameLike` by `FindOneByInterfaceProjectionImplementer`. - `SingleResultAdapterSpec` and `ObservableResultAdapterSpec`: each adapter declines a projection whose return type belongs to the other adapter, and the Observable adapter keeps the domain class for a property projection. Run locally on JDK 21: the four specs above (67 tests, 9 of them new), `:grails-datamapping-rx:codeStyle`, and the `asciidoctor` task of both Hibernate guides. The full suites are left to CI. ### Docs The RxJava section of the data services chapter in the Hibernate 5 and Hibernate 7 guides gains a paragraph on projections. ### Branches `grails-datamapping-rx` is commented out of `settings.gradle` on 7.0.x, 7.2.x and 8.0.x, so 8.1.x is the earliest branch that ships the module and this fix; it merges up to 9.0.x and later. The RxJava 3 port in #16619 (9.0.x) touches a javadoc line in `ObservableResultAdapter` and will want a merge of 9.0.x after this lands. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
