Issue Details (XML | Word | Printable)

Key: GLASSFISH-20371
Type: Bug Bug
Status: Resolved Resolved
Resolution: Duplicate
Priority: Critical Critical
Assignee: jjsnyder83
Reporter: Bruno Borges
Votes: 0
Watchers: 4
Operations

If you were logged in you would be able to see more operations.
glassfish

Can't listen to JMS Queue from a WebSocket

Created: 22/Apr/13 05:59 PM   Updated: 22/May/13 03:04 PM   Resolved: 22/May/13 03:04 PM
Component/s: cdi
Affects Version/s: 4.0_b83
Fix Version/s: future release

Time Tracking:
Not Specified

Tags:
Participants: arjavdesai, Bruno Borges, dannycoward, David Zhao, jjsnyder83 and Nigel Deakin


 Description  « Hide

It should be possible to connect a WebSocket and a JMS queue using standards. The usecase is based on the idea where each websocket session will be a subscriber to a Topic. In this example specifically, code is based on a WebSocket session consuming a Queue.

It is possible to inject a JMSContext inside a WebSocket using @Inject at constructor [1], but whenever I try to inject a Queue/Topic, it does not get injected using @Resource [2].

Also tried to use JMS 1.1 code (creating a InitialContext, doing lookup on ConnectionFactory, etc), it dit not work.

Most of the time, I get this error [3].

MyWebSocket.java
@Named // with @Named or without, nothing from CDI works as it should
@ServerEndpoint("/path")
public class MyWebSocket implements MessageListener {
  // @Inject at field level does not work
  private JMSContext jmsContext;

  @Resource(mappedName = "jms/myQueue") // [2] jms queue/topic does not get injected
  private Queue queue;

  @Inject // [1] at constructor, it works
  public MyWebSocket(JMSContext jmsc) {
    this.jmsContext = jmsc;
    ... // code to create a MessageListener
   jmsContext.createConsumer(queue).setMessageListener(this);
  }

  public void onMessage(Message m) {
    webSocketSession.getAsyncRemote().sendText(m.getBody(String.class));
  }

}


David Zhao added a comment - 23/Apr/13 02:19 AM - edited

Regarding to the injection into WebSocket, I observed the following behavior. Forwarding to CDI team to take a look.

1. If JMSContext is injected as a field of class, it is not available in constructor(is that expected?). But it is available in @OnMessage method, which is correct. It seems that @OnMessage method of WebSocket doesn't have a vaild transaction or request scope, so the behavior of JMSContext injection will be undefined in JMS 2.0 spec.

2. If JMSContext is injected at constructor, then it is available inside constructor.

3. The queue injected by @Resource is null in both constructor and @OnMessage method.

How does CDI sped define the scope of injection for WebSocket?


Bruno Borges added a comment - 23/Apr/13 03:35 AM

David, some answers and questions to your comment

1.1 It cannot be available at constructor when injection is at field level. That's fine, and expected.

1.2 What would be the transaction/request scope of JMSContext when it is injected into a HttpServlet?

a. Should WebSockets support @ApplicationScoped/@SessionScoped annotations?

2. Works as it should

3. Most problematic issue so far


Bruno Borges added a comment - 23/Apr/13 03:37 AM

Issue https://java.net/jira/browse/GLASSFISH-20255 might be related to this.


David Zhao added a comment - 23/Apr/13 09:07 AM

Bruno,

Please refer to the urls for the scopes of JMSContext injection.

https://java.net/projects/jms-spec/pages/JMSContextScopeProposalsv4p2
https://java.net/projects/jms-spec/pages/JMSContextScopeProposalsv4p3

I shall leave other CDI questions to CDI team.


jjsnyder83 added a comment - 23/Apr/13 04:26 PM

IIRC only @ApplicationScoped and @Dependent is available for WebSocket. @SessionScoped and @RequestScoped injection is not available as http sessions and http requests aren't valid in the web socket world.

I'm not sure about @Resource injection...Can you do a jndi lookup successfully for the queue "jms/myQueue" from within the web socket?


Bruno Borges added a comment - 23/Apr/13 04:49 PM

Traditional lookup of Queue works, but creating a valid consumer using the injected JMSContext does not.

GF throws a SEVERE message error saying that there is no valid EE environment.


arjavdesai added a comment - 23/Apr/13 05:28 PM

Can you please provide us with a test app or netbeans project, if available?


jjsnyder83 added a comment - 23/Apr/13 06:20 PM

The "...no valid EE environment" means that the GF cdi code couldn't access JNDI because the JNDI environment has not been set correctly for the thread on which the code is running. Something similar to how the web container sets up the invocation manager must also be done by the WebSocket code on which the thread is running or the injection of resources will fail. See WebContainerListener.preInvoke and .postInvoke for an example.


dannycoward added a comment - 23/Apr/13 11:01 PM

This we think relates to https://java.net/jira/browse/GLASSFISH-20375

I'll post more information while we test with the fix for that issue.


arjavdesai added a comment - 25/Apr/13 07:54 PM

https://java.net/jira/browse/GLASSFISH-20375 is marked as resolved with 61589. Can you retry your scenario and update this JIRA?


Bruno Borges added a comment - 25/Apr/13 10:03 PM - edited

No, it did not work.

Got this warning, and worse: the WebSocket didn't even initialize.

WARNING: WELD-001473 javax.enterprise.inject.spi.Bean implementation org.glassfish.jms.injection.JMSCDIExtension$LocalBean@55118a0 
declared a normal scope but does not implement javax.enterprise.inject.spi.PassivationCapable. 
It won't be possible to inject this bean into a bean with passivating scope (@SessionScoped, @ConversationScoped). 
This can be fixed by assigning the Bean implementation a unique id by implementing the PassivationCapable interface.

jjsnyder83 added a comment - 26/Apr/13 01:16 PM

You can ignore the warning for now. It does not affect this particular issue.


Bruno Borges added a comment - 26/Apr/13 01:49 PM

The message wasn't appearing before, and now appears, and the WebSocket does not work when there's @Resource for queue + @Inject for JMSContext.


jjsnyder83 added a comment - 26/Apr/13 01:54 PM

I have contacted JMS team about the warning. The problem that the warning is about would only happen during passivation which I don't believe is happening here.

Can you be more specific in your comment that "the WebSocket didn't even initialize?" Are you still seeing the "...no valid EE environment" message?


Bruno Borges added a comment - 26/Apr/13 02:22 PM

That message does not appear, but websocket does not work either, without any error message


dannycoward added a comment - 29/Apr/13 04:57 PM

Can we get the complete code for this failure Bruno ? The sample code above is neither complete nor a well-formed websocket. Thanks.


Bruno Borges added a comment - 30/Apr/13 06:55 PM

Nigel Deakin added a comment - 02/May/13 03:37 PM

I think your code is throwing exceptions which are not being logged. Please update all the callbacks in your code to add a catch block for RuntimeException, and write the exception and stack trace to the server log. This will make it easier to understand what is going wrong.


Bruno Borges added a comment - 02/May/13 04:04 PM - edited

Code changed to print any type of Exception.

Exception found:

SEVERE:   javax.jms.JMSRuntimeException: [C4306]: This method may not be called in a Java EE web or EJB container
	at com.sun.messaging.jmq.jmsclient.JMSConsumerImpl.setMessageListener(JMSConsumerImpl.java:261)
	at org.glassfish.javaee7wsjms.SampleWebSocket.onOpen(SampleWebSocket.java:45)
	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.onOpen(AnnotatedEndpoint.java:468)
	at org.glassfish.tyrus.core.EndpointWrapper.onConnect(EndpointWrapper.java:446)
	at org.glassfish.tyrus.server.TyrusEndpoint.onConnect(TyrusEndpoint.java:146)
	at org.glassfish.tyrus.websockets.DefaultWebSocket.onConnect(DefaultWebSocket.java:122)
	at org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler.init(TyrusHttpUpgradeHandler.java:98)
	at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:777)

Exception thrown at SampleWebSocket.java:45


Bruno Borges added a comment - 02/May/13 04:20 PM

I understand the message as part of

12.2. Restrictions on the use of JMS API in the Java EE web or EJB
container

But then I wonder how can WebSockets proactively send data to clients, triggered by asynchronous events.

Is it possible for example, to plug a JMS MessageListener with a WebSocket using CDI? Let's say an MDB producing CDI Events and the WebSocket server endpoint consuming that?


Bruno Borges added a comment - 02/May/13 05:01 PM - edited

I've coded a version using CDI Events.

This version demonstrates a specific bug: Can't send messages to a Queue from a WebSocket

SEVERE:   org.jboss.weld.context.ContextNotActiveException: WELD-001303 No active contexts for scope type javax.enterprise.context.RequestScoped
	at org.jboss.weld.manager.BeanManagerImpl.getContext(BeanManagerImpl.java:667)
	at org.jboss.weld.bean.proxy.ContextBeanInstance.getInstance(ContextBeanInstance.java:74)
	at org.jboss.weld.bean.proxy.ProxyMethodHandler.invoke(ProxyMethodHandler.java:79)
	at org.glassfish.jms.injection.RequestedJMSContextManager$Proxy$_$$_WeldClientProxy.getType(Unknown Source)
	at org.glassfish.jms.injection.InjectableJMSContext.delegate(InjectableJMSContext.java:126)
	at org.glassfish.jms.injection.ForwardingJMSContext.createProducer(ForwardingJMSContext.java:61)
	at org.glassfish.javaee7wsjms.SampleWebSocket.onMessage(SampleWebSocket.java:62)

Branch published on GitHub using-cdi


jjsnyder83 added a comment - 02/May/13 05:27 PM

The ContextNotActiveException you are seeing means that an @RequestScoped CDI-injected object was accessed outside of an active http request. More specifically the JMSContext's @RequestScoped delegate is being accessed in SampleWebSocket.onMessage() but there is no active http request. The CDI-injected JMSContext can only be accessed when there's an active http request or active global transaction. If it is accessed outside of these scopes (http request or global transaction) you will see this type of message.


Nigel Deakin added a comment - 02/May/13 05:31 PM - edited

That matches my own experiments. Your call to context.createConsumer was successful, which suggests that an injected JMSContext can be used in the @OnOpen callback. However you should be aware that as an injected JMSContext has @RequestScoped (when there is no transaction) any object created from it will become invalid when the request ends. So the JMSConsumer you create from it won't be usable after then.

However when I invoked your @OnMessage callback (modified to catch exceptions) I got a Weld error:

SEVERE:   org.jboss.weld.context.ContextNotActiveException: 
WELD-001303 No active contexts for scope type javax.enterprise.context.RequestScoped 

which suggests there is a restriction in using an injected JMSContext in the @OnMessage callback. That's an open issue.

However, as you saw, you also hit the restriction that you can't set a MessageListener in a Java EE environment (except the ACC). This restriction is not new and was in Java EE 6.

There is no restriction on consuming messages synchronously (using JMSConsumer.receive()), or in sending messages. But if you want to consume messages asynchronously you're expected to use a MDB.

I suggest moving discussion of your application design to email.


Bruno Borges added a comment - 02/May/13 07:38 PM - edited

I managed to make this work using CDI events. Still, some things had to be changed:

  1. It is impossible, as I understand from the spec, to connect WebSockets and JMS directly.
  2. To send an incoming message from a WebSocket client to a JMS queue, I used a @Stateless session bean
    There's still a bug in here, because it was not possible to @Inject the SB the normal way. Had to use constructor-injected parameter.
  3. SessionBean does use JMS 2 the way it is specified, @Inject JMSContext and @Resource Queue.
  4. @MessageDriven bean produces a CDI event with the JMS payload
  5. The WebSocket server endpoint @Observes for CDI event with the payload. Then payload is sent to all WebSocket sessions

The code is available here


Bruno Borges added a comment - 02/May/13 07:53 PM

Although I'm happy that such integration is possible, I wonder if this could be simplified by JMS_SPEC-100


Bruno Borges added a comment - 05/May/13 04:43 AM

Created two separate bugs that were found due to this issue:


jjsnyder83 added a comment - 22/May/13 03:04 PM