users@tyrus.java.net

Re: WebSocket+EJB+BV integration bug?

From: Reza Rahman <Reza.Rahman_at_oracle.com>
Date: Fri, 09 Aug 2013 10:13:25 -0400

OK, sounds good.

On 8/9/2013 9:52 AM, Pavel Bucek wrote:
> Thanks for suggestion, I filed an issue against Tyrus right after
> reading previous mail - https://java.net/jira/browse/TYRUS-231
>
> Anyway, I don't see how we could improve error reporting for this case
> - Server-side WebSocket Endpoint does not know much about connected
> client, so there is no information about format of messages client can
> handle. This is very different than JAX-RS and other HTTP or generally
> request/response schemes, there is no "per-message negotiation" like
> HTTP MediaType for example. WebSocket Message does not contain any
> metadata and there is no defined way how to send errors.. well, other
> than in Close Reason; but application might not want to close the
> connection right away.
>
> What we might be able to provide is some identification of thrown
> exception, so you might be able to skip the instanceof check, but we
> will need to think it through a little bit..
>
> Thanks!
> Pavel
>
> On 8/9/13 2:57 PM, Reza Rahman wrote:
>> Great - this would scale better than singleton. When you get a chance
>> I would look deeper into BV to see if a better programming model
>> while integrating with WebSocket is possible.
>>
>> Generally speaking with BV + JSF/JPA/JAX-RS, etc the runtime takes
>> care of the error handling/reporting when validation fails.
>>
>> Sent from my iPhone
>>
>> On Aug 9, 2013, at 7:49 AM, Pavel Bucek <pavel.bucek_at_oracle.com> wrote:
>>
>>> Not sure, I'm not very familiar with bean validation as such. (but
>>> it does not look that bad..)
>>>
>>> Anyway - I noticed you should be able to simplify your ChatServer
>>> class. I haven't tried it this, but it might look as simple as:
>>>
>>> @ServerEndpoint(value = "/chat",
>>> encoders = {ChatMessage.class}, decoders = {ChatMessage.class})
>>> @Stateless
>>> public class ChatServer {
>>>
>>> @OnMessage
>>> public void onMessage(Session session, @Valid ChatMessage
>>> message) {
>>> for (Session peer : session.getOpenSessions()) {
>>> try {
>>> peer.getBasicRemote().sendObject(message);
>>> } catch (IOException | EncodeException ex) {
>>> Logger.getLogger(ChatServer.class.getName()).log(
>>> Level.SEVERE, "Error sending message", ex);
>>> }
>>> }
>>> }
>>>
>>> @OnError
>>> public void onError(Session session, Throwable error) {
>>> try {
>>> if (error.getCause() instanceof
>>> ConstraintViolationException) {
>>> // Just report the first validation problem.
>>> JsonObject jsonObject = Json.createObjectBuilder()
>>> .add("error",
>>> ((ConstraintViolationException)
>>> error.getCause())
>>> .getConstraintViolations().iterator().next()
>>> .getMessage())
>>> .build();
>>> session.getBasicRemote().sendText(jsonObject.toString());
>>> } else {
>>> Logger.getLogger(ChatServer.class.getName()).log(
>>> Level.SEVERE, null, error);
>>> }
>>> } catch (IOException ex) {
>>> Logger.getLogger(ChatServer.class.getName()).log(
>>> Level.SEVERE, null, ex);
>>> }
>>> }
>>> }
>>>
>>> I removed peers Set, since it should be maintained by Session
>>> itself. Also I changed lifecycle from Singleton to Stateless (for
>>> the same reason). The annotation is there only because bean
>>> validation stuff - Tyrus does not have any Bean Validation yet and
>>> @Stateless should pass the responsibility for calling BV runtime to
>>> EJB container.
>>>
>>> Regards,
>>> Pavel
>>>
>>>
>>>
>>> On 8/9/13 3:24 AM, Reza Rahman wrote:
>>>> OK, that works. BTW, the updates code is here:
>>>> https://github.com/m-reza-rahman/javaee-mobile-server. It's a bit
>>>> of awkward code. Maybe there's better semantics one could come up
>>>> with to handle BV integration?
>>>>
>>>> On 8/8/2013 5:41 PM, Pavel Bucek wrote:
>>>>> Hi Reza,
>>>>>
>>>>> not really. Client is _not_ (and should not be) automatically
>>>>> notified about error on server side (when it is not violation of
>>>>> WebSocket protocol and/or "critical" error which will cause
>>>>> closing current connection.
>>>>>
>>>>> Error should be propagated to @OnError annotated method on
>>>>> *server* endpoint and it is up to application logic to notify
>>>>> client about malformed message.
>>>>>
>>>>> Anyway, I think your sample demonstrates usecase we actually don't
>>>>> have in our test so we will add something similar leveraging
>>>>> BeanValidation.
>>>>>
>>>>> Thanks and regards,
>>>>> Pavel
>>>>>
>>>>> On 8/8/13 11:06 PM, Reza Rahman wrote:
>>>>>> I have a pretty simple chat server that uses Bean Validation 1.1
>>>>>> to validate an inbound WebSocket message like so:
>>>>>>
>>>>>> @OnMessage
>>>>>> public void message(@Valid ChatMessage message) {
>>>>>>
>>>>>> The validation part is working correctly (i.e. I can make the
>>>>>> validation fail). However, the part that I'm finding surprising
>>>>>> is the fact that when the validation fails, the websocket client
>>>>>> does not receive an error message? I just see the below error
>>>>>> message on the console and the @OnMessage processing is blocked
>>>>>> (which is desirable). Is this correct? Should it be fixed such
>>>>>> that an error is sent back to the client when validation fails (a
>>>>>> la JAX-RS 2)? My code is attached in the zip.
>>>>>>
>>>>>> WARNING: EJB5184:A system exception occurred during an
>>>>>> invocation on EJB ChatServer, method: public void
>>>>>> org.example.chat.server.ChatServer.message(org.example.chat.server.ChatMessage)
>>>>>> WARNING: javax.ejb.EJBException
>>>>>> at
>>>>>> com.sun.ejb.containers.EJBContainerTransactionManager.processSystemException(EJBContainerTransactionManager.java:748)
>>>>>> at
>>>>>> com.sun.ejb.containers.EJBContainerTransactionManager.completeNewTx(EJBContainerTransactionManager.java:698)
>>>>>> at
>>>>>> com.sun.ejb.containers.EJBContainerTransactionManager.postInvokeTx(EJBContainerTransactionManager.java:503)
>>>>>> at
>>>>>> com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4475)
>>>>>> at
>>>>>> com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:2009)
>>>>>>
>>>>>> at
>>>>>> com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1979)
>>>>>>
>>>>>> at
>>>>>> com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:220)
>>>>>> at
>>>>>> com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:88)
>>>>>> at com.sun.proxy.$Proxy305.message(Unknown Source)
>>>>>> at
>>>>>> org.example.chat.server.__EJB31_Generated__ChatServer__Intf____Bean__.message(Unknown
>>>>>> Source)
>>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>>> at
>>>>>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
>>>>>> at
>>>>>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
>>>>>> at java.lang.reflect.Method.invoke(Method.java:601)
>>>>>> at
>>>>>> org.jboss.weld.util.reflection.Reflections.invokeAndUnwrap(Reflections.java:396)
>>>>>> at
>>>>>> org.jboss.weld.bean.proxy.EnterpriseBeanProxyMethodHandler.invoke(EnterpriseBeanProxyMethodHandler.java:108)
>>>>>> at
>>>>>> org.jboss.weld.bean.proxy.EnterpriseTargetBeanInstance.invoke(EnterpriseTargetBeanInstance.java:56)
>>>>>> at
>>>>>> org.jboss.weld.bean.proxy.InjectionPointPropagatingEnterpriseTargetBeanInstance.invoke(InjectionPointPropagatingEnterpriseTargetBeanInstance.java:63)
>>>>>> at
>>>>>> org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:101)
>>>>>> at
>>>>>> org.example.chat.server.ChatServer$Proxy$_$$_Weld$EnterpriseProxy$.message(Unknown
>>>>>> Source)
>>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>>> at
>>>>>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
>>>>>> at
>>>>>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
>>>>>> at java.lang.reflect.Method.invoke(Method.java:601)
>>>>>> at
>>>>>> org.glassfish.tyrus.core.AnnotatedEndpoint.callMethod(AnnotatedEndpoint.java:431)
>>>>>> at
>>>>>> org.glassfish.tyrus.core.AnnotatedEndpoint.access$100(AnnotatedEndpoint.java:83)
>>>>>> at
>>>>>> org.glassfish.tyrus.core.AnnotatedEndpoint$WholeHandler$1.onMessage(AnnotatedEndpoint.java:518)
>>>>>> at
>>>>>> org.glassfish.tyrus.core.SessionImpl.notifyMessageHandlers(SessionImpl.java:389)
>>>>>> at
>>>>>> org.glassfish.tyrus.core.EndpointWrapper.onMessage(EndpointWrapper.java:495)
>>>>>> at
>>>>>> org.glassfish.tyrus.server.TyrusEndpoint.onMessage(TyrusEndpoint.java:174)
>>>>>> at
>>>>>> org.glassfish.tyrus.websockets.DefaultWebSocket.onMessage(DefaultWebSocket.java:156)
>>>>>> at
>>>>>> org.glassfish.tyrus.websockets.frametypes.TextFrameType.respond(TextFrameType.java:66)
>>>>>> at
>>>>>> org.glassfish.tyrus.websockets.DataFrame.respond(DataFrame.java:102)
>>>>>> at
>>>>>> org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler.onDataAvailable(TyrusHttpUpgradeHandler.java:113)
>>>>>> at
>>>>>> org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.processDataAvailable(InputBuffer.java:488)
>>>>>> at
>>>>>> org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.onDataAvailable(InputBuffer.java:453)
>>>>>> at
>>>>>> org.glassfish.grizzly.http.io.InputBuffer.append(InputBuffer.java:855)
>>>>>>
>>>>>> at
>>>>>> org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:222)
>>>>>> at
>>>>>> org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
>>>>>> at
>>>>>> org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288)
>>>>>> at
>>>>>> org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206)
>>>>>> at
>>>>>> org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136)
>>>>>> at
>>>>>> org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114)
>>>>>> at
>>>>>> org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
>>>>>> at
>>>>>> org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838)
>>>>>> at
>>>>>> org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113)
>>>>>> at
>>>>>> org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115)
>>>>>> at
>>>>>> org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55)
>>>>>> at
>>>>>> org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135)
>>>>>> at
>>>>>> org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564)
>>>>>> at
>>>>>> org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544)
>>>>>> at java.lang.Thread.run(Thread.java:722)
>>>>>> Caused by: javax.validation.ConstraintViolationException: 1
>>>>>> constraint violation(s) occurred during method validation.
>>>>>> Constructor or Method: public void
>>>>>> org.example.chat.server.ChatServer.message(org.example.chat.server.ChatMessage)
>>>>>> Argument values: [org.example.chat.server.ChatMessage_at_3197e3fa]
>>>>>> Constraint violations:
>>>>>> (1) Kind: PROPERTY
>>>>>> message: size must be between 1 and 255
>>>>>> root bean: org.example.chat.server.ChatServer_at_2eedd120
>>>>>> property path: message.arg0.message
>>>>>> constraint:
>>>>>> @javax.validation.constraints.Size(message={javax.validation.constraints.Size.message},
>>>>>> min=1, max=255, payload=[], groups=[])
>>>>>> at
>>>>>> org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor.validateMethodInvocation(ValidationInterceptor.java:81)
>>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>>> at
>>>>>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
>>>>>> at
>>>>>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
>>>>>> at java.lang.reflect.Method.invoke(Method.java:601)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
>>>>>> at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
>>>>>> at
>>>>>> org.jboss.weld.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:55)
>>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>>> at
>>>>>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
>>>>>> at
>>>>>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
>>>>>> at java.lang.reflect.Method.invoke(Method.java:601)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
>>>>>> at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:582)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.SystemInterceptorProxy.doCall(SystemInterceptorProxy.java:163)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.SystemInterceptorProxy.aroundInvoke(SystemInterceptorProxy.java:140)
>>>>>> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
>>>>>> at
>>>>>> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
>>>>>> at
>>>>>> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
>>>>>> at java.lang.reflect.Method.invoke(Method.java:601)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:883)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:822)
>>>>>> at
>>>>>> com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:369)
>>>>>> at
>>>>>> com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:4667)
>>>>>> at
>>>>>> com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4655)
>>>>>>
>>>>>> at
>>>>>> com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:212)
>>>>>> ... 45 more
>>
>