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
>