This method is not applicable inside the app server when trying to set a listener for a JMS queue

1.8k views Asked by At

I am trying JMS 2.0 so I can decide if it is worth applying in my project. I could successfully create a send/receive application.

Now I would like to have listeners that will receive the message as soon as it is available on the queue (my final goal is to have different listeners to the same queue, each with a different message selector.

Currently I have this class:

package learning.jms;


import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;

@Named(value="senderBean")
@SessionScoped
public class SenderBean implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Resource(mappedName="queues/myQueue")
    private transient Queue myQueue;

    @Inject
    @JMSConnectionFactory("java:/DefaultJMSConnectionFactory")
    private transient JMSContext context;

    private String messageText;

    private int nextType = 3;
    private transient JMSConsumer consumer;
    private transient JMSConsumer consumer2;
    private transient JMSConsumer consumer3;

    public SenderBean() {
    }

    @PostConstruct
    public void setUp(){

    }

    public String getMessageText() {
        return messageText;
    }

    public void setMessageText(String messageText) {
        this.messageText = messageText;
    }

    public void sendJMSMessageToMyQueue() {
        try {

            consumer = context.createConsumer(myQueue, "type=1");
            consumer.setMessageListener(new ListenerTypeOne());

//          consumer2 = context.createConsumer(myQueue, "type=2");
//          consumer2.setMessageListener(new ListenerTypeTwo());
//          
//          consumer3 = context.createConsumer(myQueue, "type=3");
//          consumer3.setMessageListener(new ListenerTypeThree());

           String text = "Message from producer: " + messageText;
           Message m1 = context.createTextMessage(text);
           m1.setIntProperty("type", nextType);

           System.out.println("producer sending msg type " + nextType + "value: " + text);

           nextType = (nextType++%3)+1;


           context.createProducer().send(myQueue, m1);

           FacesMessage facesMessage =
                   new FacesMessage("Sent message: " + text);
           FacesContext.getCurrentInstance().addMessage(null, facesMessage);
       } catch (JMSRuntimeException | JMSException t) {
            System.out.println(t.toString());
       }
   }

    private class ListenerTypeOne implements MessageListener{

        @Override
        public void onMessage(Message msg) {
            try {
                System.out.println("Msg received by typeOne:" + msg.getBody(String.class));
            } catch (JMSException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    private class ListenerTypeTwo implements MessageListener{

        @Override
        public void onMessage(Message msg) {
            try {
                System.out.println("Msg received by typeTwo:" + msg.getBody(String.class));
            } catch (JMSException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }

    private class ListenerTypeThree implements MessageListener{

        @Override
        public void onMessage(Message msg) {
            try {
                System.out.println("Msg received by typeThree:" + msg.getBody(String.class));
            } catch (JMSException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }
    }
}

I commented out two consumers, so I could focus on making one work. I keep getting the following exception on the setMessageListener line:

 javax.jms.IllegalStateException: This method is not applicable inside the application server. See the J2EE spec, e.g. J2EE1.4 Section 6.6
    at org.hornetq.ra.HornetQRASession.checkStrict(HornetQRASession.java:1647)
    at org.hornetq.ra.HornetQRAMessageConsumer.setMessageListener(HornetQRAMessageConsumer.java:124)
    at org.hornetq.jms.client.HornetQJMSConsumer.setMessageListener(HornetQJMSConsumer.java:68)

I have no idea what could be causing this and my searches are not giving me any extra information. I guess it could be something related to the fact that one componen should have no more than one active session. In this case, how could I create multiple listeners to listen to the queue?

(if important: I am using Wildfly 8)

EDIT I've extracted the listener creation to a separate bean and still teh same error:

package learning.jms;


import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;


@ApplicationScoped
public class ListenerOne {
    @Inject
    @JMSConnectionFactory("java:/DefaultJMSConnectionFactory")
    private JMSContext context;

    @Resource(mappedName="queues/myQueue")
    private Queue myQueue;


    private JMSConsumer consumer;

    public void setUp() {
        consumer = context.createConsumer(myQueue, "type=1");
        consumer.setMessageListener(new ListenerTypeOne());


        System.out.println("working");
    }

    private class ListenerTypeOne implements MessageListener{

        @Override
        public void onMessage(Message msg) {
            try {
                System.out.println("Msg received by typeOne:" + msg.getBody(String.class));
            } catch (JMSException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
3

There are 3 answers

0
JSBach On BEST ANSWER

So, looking for MDBs solved the issue. I cleaned the senderBean classfrom any traces of the consumers I was trying to create:

package learning.jms;


import java.io.Serializable;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.context.SessionScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.jms.JMSConnectionFactory;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.Message;
import javax.jms.Queue;

@Named(value="senderBean")
@SessionScoped
public class SenderBean implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    @Resource(mappedName="queues/myQueue")
    private transient Queue myQueue;

    @Inject
    @JMSConnectionFactory("java:/DefaultJMSConnectionFactory")
    private transient JMSContext context;

    private String messageText;

    private int nextType;

    public SenderBean() {
        // TODO Auto-generated constructor stub
    }

    @PostConstruct
    public void init(){
        nextType=2;
    }

    public String getMessageText() {
        return messageText;
    }

    public void setMessageText(String messageText) {
        this.messageText = messageText;
    }

    public void sendJMSMessageToMyQueue() {
        try {
           String text = "Message from producer: " + messageText;
           Message m1 = context.createTextMessage(text);
           m1.setIntProperty("type", nextType);

           nextType = (nextType++%3)+1;

           context.createProducer().send(myQueue, m1);

           FacesMessage facesMessage =
                   new FacesMessage("Sent message: " + text);
           FacesContext.getCurrentInstance().addMessage(null, facesMessage);
       } catch (JMSRuntimeException | JMSException t) {
            System.out.println(t.toString());
       }
   }
}

(notice it is just session scope so I can iterate over the message types")

And created 3 MDBs

package learning.jms;


import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;



@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationLookup",
            propertyValue = "queues/myQueue"),
        @ActivationConfigProperty(propertyName = "destinationType",
            propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "messageSelector",propertyValue = "type=1")
    })
public class ListenerOne implements MessageListener {

    @Override
    public void onMessage(Message msg) {
        try {
            System.out.println("Msg received by typeOne: " + msg.getBody(String.class) + " type: " + msg.getIntProperty("type"));
        } catch (JMSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

two:

package learning.jms;


import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;



@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationLookup",
            propertyValue = "queues/myQueue"),
        @ActivationConfigProperty(propertyName = "destinationType",
            propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "messageSelector",propertyValue = "type=2")
    })
public class ListenerTwo implements MessageListener {

    @Override
    public void onMessage(Message msg) {
        try {
            System.out.println("Msg received by typeTwo: " + msg.getBody(String.class) + " type: " + msg.getIntProperty("type"));
        } catch (JMSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

three:

package learning.jms;


import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;



@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationLookup",
            propertyValue = "queues/myQueue"),
        @ActivationConfigProperty(propertyName = "destinationType",
            propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "messageSelector",propertyValue = "type=3")
    })
public class ListenerThree implements MessageListener {

    @Override
    public void onMessage(Message msg) {
        try {
            System.out.println("Msg received by typeThree: " + msg.getBody(String.class) + " type: " + msg.getIntProperty("type"));
        } catch (JMSException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

Now they listen automatically for messages in the queue that match their selector.

Thanks @prabugp for the help :)

0
MAY On

The above error could be because you are trying to use the client inside the same container and getting connection factory from JCA based connection factory.

Case 1: if client are remote to jms server then using jms/RemoteConnectionFactory is recommended and above problem will not be reproduced.

Case 2: if client resides in same container then connections from JCA based connection factory java/JmsXA is preferred. Since there is a limitation in JEE 7 specification under section 6:7 that JEE server does not permit EJB/Web application to have more than one active session i.e you cannot have legacy JMS application.

E.g: In a method:

public void startConnection() {
        try {
            TopicConnectionFactory connectionFactory = (TopicConnectionFactory)    getConnectionFactory();
            topicConnection = connectionFactory.createTopicConnection();
            topicSession = topicConnection.createTopicSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE);           
            subscriber = topicSession.createSubscriber(messageTopic, selector, true);
            MessageListener messageListener = new MessageListener(this);
            // Code to set message listener.
            subscriber.setMessageListener(messageListener);
            topicConnection.start();
        } catch (Exception e) {
            LOG.error(e, e);
            closeConnection();
            throw new RuntimeException(e);
        }
    }

if connection factory on above code is from @Resource(mappedName = "java:jboss/exported/jms/RemoteConnectionFactory")

then above code will work. But if we change the connection factory to //@Resource(mappedName = "java:/JmsXA")

then above error will be thrown.

So if your client is in the same container then MDB should be used. Since container wants control of connection objects to support two phase commit protocol.

0
Jose Manuel Gomez Alvarez On

My view on this, you can not have a message listener in a JSF bean, since the bean lifecycle is controlled by the web container.

MDBs are the only components that are driven by messages, but JSF MBs, like EJBs or servlets, cannot listen for messages, they "live" in the context of a request, and instances are created, activated, passivated or destroyed by the container.

But instead, you can use receive(), in the context of a request, and set up some autorefresh on client side, to implement a flow that is server-side driven.