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

Jakarta Faces 4.0 websocket jakarta.faces.push.PushContext fails to serialize objects in certain cases #26854

Open hrsto opened 10 months ago

hrsto commented 10 months ago

Describe the bug jakarta.faces.push.PushContext#send(Object) fails in these cases:

  1. When it is given a collection of java record objects. The javascript websocket message handler will return an array of empty json objects. The array size is equal to the size java record given to send(Object)
  2. When it is given a class generated by apache avro, regardless if it's just one class or collection of such classes. Nothing is being sent back to javascript handler.

Steps to Reproduce
Follow javadoc of jakarta.faces.push.Push to setup basic code.

  1. Add to some Faces page <f:websocket channel="myStream" onmessage="myStreamListener" />
  2. Add the javascript listener somewhere:
    function myStreamListener(message, channel, event) {
     console.log(message);
    }
  3. In a java @Session CDI bean:
    
    @Inject @Push PushContext myStream;

public void pushData(List data) { // SomeRecord can be a java record or apache avro generated class (via its maven plugin) // Fixed by changing it to a POJO var messagesSendStatuses = myStream.send(data); }


**Expected behavior** 
Should receive a properly serialized object. Should support java record classes.

**Diagnostic information:**  
 - OpenLiberty Version: 21.0.0.10
 - Affected feature(s): faces-4.0
 - Java Version: 

openjdk 17.0.2 2022-01-18 OpenJDK Runtime Environment (build 17.0.2+8-86) OpenJDK 64-Bit Server VM (build 17.0.2+8-86, mixed mode, sharing)

 - server.xml configuration:

<?xml version="1.0" encoding="UTF-8"?>

localConnector-1.0 faces-4.0 cdi-4.0 websocket-2.1 expressionLanguage-5.0 servlet-6.0 restfulWS-3.1 appSecurity-5.0 mpConfig-3.0
<httpEndpoint id="defaultHttpEndpoint" httpPort="8080" />
<applicationMonitor updateTrigger="polled"/>
<ssl id="defaultSSLConfig" trustDefaultCerts="true" />

<mpMetrics authentication="false" />

<basicRegistry></basicRegistry>


 - `$WLP_OUTPUT_DIR/messages.log`: no error messages, or any message at all is being outputted at the time of sending websocket messages.

**Additional context**  
Above issues disappear as soon as i start using POJOs.
volosied commented 10 months ago

Hi, thanks for bringing this to our attention. If you have a WAR file you could also provide, that would be great.

I'll investigate and see what I can do. Thanks!

volosied commented 10 months ago

I created an issue here: https://issues.apache.org/jira/browse/MYFACES-4637.

I'm not too sure how much I can help out with Records since they are a Java 14+ feature, and faces-4.0 is built with Java 11. The problem is within our Json encoding logic. We check object type to encode it properly, but since it's a Record, nothing matches. It's ultimately treated as a bean, and the handing is therefore incorrect -- hence empty json data is returned.

The only workaround I can provide is to call toString() on the collection, which works in my testing. Nevertheless, I'll collaborate with the open source community via the JIRA above. Perhaps there may be something else we can do here.

I'm curious how the Apache Arvo classes are generated? I would expect classes to work? Can you provide an example?

hrsto commented 10 months ago

Providing minimal example on github: https://github.com/hrsto/jakarta-faces-bug-report

Run it locally via liberty maven plugin mvn liberty:dev.

Avro class generated with avro plugin: mvn avro:schema.

Open page http://localhost:8080/bug/ and click Send push message via websocket to trigger the 3 cases - java record, avro, POJO.

volosied commented 9 months ago

Thanks for providing an app. I see that in the avro case, the JSON encoding fails on the line value = property.getReadMethod().invoke(bean);

property.getReadMethod() returns public org.apache.avro.Schema org.apache.avro.Schema.getElementType()

The issue is that getElementType throws an exception:

org.apache.avro.AvroRuntimeException: Not an array: {"type":"record","name":"SomeSample","namespace":"com.bug.report","fields":[{"name":"iso","type":"string"},{"name":"continent","type":["null","string"],"default":null},{"name":"country","type":"string"}],"connect.doc":"Sample for bug report.","connect.version":1,"connect.name":"SomeSample"}

MyFaces' JSON.java class looks at all the properties of the object it's encoding and tries to call all the read methods.

As for why the Schema class is getting encoded -- Your object SomeSample contains the mention getSchema, so the JSON encoder tries to encode the Schema$RecordSchema, too. That's where it encounters the getElementType method.

I'm not sure what exactly could be done here in MyFaces. You might need to use toString here as well.

SomeSample#toString returns: [{"iso": "AVRO iso", "continent": "AVRO continent", "country": "AVRO country"}]

volosied commented 7 months ago

Jakarta Faces 4.1 (EE11) will be built with Java 17 -- this means we can support Java Records.

We only have the 4.1-RC1 out right now, and we haven't added record encoding just yet. I'll aim to have this in 4.1.0-RC2 (or whichever release is next). When we beta faces, you you be able to send records.

volosied commented 4 months ago

I've fixed the record encoding in MyFaces 4.1. It will be in the next release ( RC3 currently).

As for avro encoding, I'm not sure what I can do as I mentioned before. We could have a exception for Avro code, but that means we'd need to use it as a dependency, and that's not ideal.

I'll keep this issue open until we pull the record into Liberty. Note that the faces-4.1 is still in development - we hope to get it into beta soon. Thanks!