1 2 3 4 Previous Next 48 Replies Latest reply on Apr 10, 2011 1:28 PM by blabno Go to original post
      • 15. Re: JMS MessageListener in a web application
        nbhatia.bhatian.comcast.net

        That's a brilliant workaround! Thanks Nikolay.

        • 16. Re: JMS MessageListener in a web application
          lvdberg
          After a sleepless night i've found a simple but working fix.

          The Asynchronous timer support works perfectly together with Seam so I've created a simple MessageConsumer receiver within the timed task, which fires every 5 seconds to see if something has arrived.

          Basically :

          @Asynchronous
          public QuartzTriggerHandle checkTopic(
                          @Expiration Date when,
                          @IntervalDuration Long interval,
                          @FinalExpiration Date endDate
                          ){
                                 
                          try {
                                  Message m = consumer.receiveNoWait();
                                  // Check if we received a message and if yes process it.
                                  if (m != null){
                                  log.info("Received a message");
                                                 
                          }
                                         
                          } catch (JMSException e) {
                                          log.error("Error blah, blah");
                          }
                         
                          return null;
                  }


          The conncetion is opened/whitin @Create and @Destroy methods;

          private void createConnection() {
                  InitialContext iniCtx;
                  try {
                  iniCtx = new InitialContext();
                  Object tmp = iniCtx.lookup("ConnectionFactory");
                  ConnectionFactory qcf = (ConnectionFactory) tmp;
                  conn = (QueueConnection) qcf.createConnection();
                  topic = (Topic) iniCtx.lookup("topic/messageTopic");
                  session = conn.createSession(false, TopicSession.AUTO_ACKNOWLEDGE);
                  consumer = session.createConsumer(topic);
                  conn.start();
                  log.info("JMS connection created and ready.");
                  } catch (Exception e) {
                  log.error("Error while creating the JMS connection: " + e.getMessage());
                  }
          }

          private void closeConnection() {
          try {
                  conn.stop();
                  consumer.close();
                  session.close();
                  conn.close();
                  log.info("JMS connection destroyed");
                  } catch (JMSException e) {
          log.error("Error while closing the JMS connection; " + e.getMessage() + ".");
          }
          }

          Needs some beautifyiing but it works!!


          I would definitely go for a similar solution with the OnMessage Methoud. In other words: it should be possible to have a @Asunchronous annotation there and have Seam do the rest. Would save us the - in my opinion - unnecessary use of receiving JMS in the browser and would make JMS and Seam a much better team.

          Please give a shout if you need more infor.

          Best regards,

          Leo


          • 17. Re: JMS MessageListener in a web application
            nbhatia.bhatian.comcast.net

            Leo, that's a very clever solution - that sleepless night was well worth the effort :-). I will give it a try. Thanks so much.


            Naresh

            • 18. Re: JMS MessageListener in a web application
              nbhatia.bhatian.comcast.net

              Leo, exactly how do you set up the recurring timer? Where is the interval duration specified? It is not very clear from the manual.


              Thanks.

              • 19. Re: JMS MessageListener in a web application
                nbhatia.bhatian.comcast.net

                Actually, I found a much easier way to create a recurring job using the EJB timer service. However it is giving me an exception right now :-(. Here's the code:


                @Name("timerJob")
                public class TimerJob {
                
                    @Observer("org.jboss.seam.postInitialization")
                    public void init() {
                        Events.instance().raiseTimedEvent(
                                "pollQueue",
                                new TimerSchedule(1000*5L, 1000*5L));
                    }
                
                    @Observer("pollQueue")
                    public void pollQueue() {
                        logger.info("pollQueue");
                    }
                }
                



                Here's the exception:


                java.lang.RuntimeException: error while reading /WEB-INF/components.xml


                Caused by: java.lang.RuntimeException: Error loading element TimerServiceDispatcher with component name null and component class null
                     at org.jboss.seam.init.Initialization.installComponentsFromXmlElements(Initialization.java:342)
                     at org.jboss.seam.init.Initialization.initComponentsFromXmlDocument(Initialization.java:217)


                My components.xml has this line as suggested in the manual. THis is the line that is causing the problem:


                <async:timer-service-dispatcher />
                



                Any ideas what's going on?


                Thanks.


                Naresh

                • 20. Re: JMS MessageListener in a web application
                  kapitanpetko

                  Did you add the async namespace in your components.xml?
                  Try to validate it with Eclipse.

                  • 21. Re: JMS MessageListener in a web application
                    nbhatia.bhatian.comcast.net

                    Yes, I do have the async namespace in components.xml and Eclipse is not complaining. Still the exception is there.

                    • 22. Re: JMS MessageListener in a web application
                      lvdberg
                      Naresh,

                      I start the timer in a separate application scoped bean. That bean is responsible for all "kick-starting" of Seam components (such as timers)  The reason I use Quartz is because of its additional features. In this specific case you can make it work also with the EJB timer. The bean is started as follows :

                      @In QuartzJMSHandler quartzJMSHandler;

                      ....
                      Calendar cal = Calendar.getInstance();
                                     cal.set(2010,12, 31); // Now
                                     quartzMonitorHandler.checkMonitors(new Date(), 30000L, cal.getTime());
                      quartzJMSHandler.checkTopic(new Date(), 5000L, cal.getTime());
                      ...

                      Whic basically means: start now, trigger every 5 seconds and stop 31 december 2010 (last just added for fun, but not necessary).

                      I think your bean needs a specific scope, because the way you defined it means it is in the default scope. The bean gets destroyed when not used or referenced and there is nothing left to reference.  You could make it an application scoped bean with an @AutoStart so it is always available.

                      You can see if this occurs with a simple Debug trich I always use during development. Add a @Create and @Destroy annotation on a create and destroy method and put a Logr in there.
                      log.info("I was created./ destroyed.").


                      The other thing is that I always let Seam manage these type of beans, so directly starting the TimerService (ejb) makes it independent of the "supervision" of Seam. Managing it by Seam means that you can manage (start/stop/re-trigger) it from another bean by injecting it.
                      • 23. Re: JMS MessageListener in a web application
                        lvdberg

                        Naresh,


                        My example:




                        |@In QuartzJMSHandler quartzJMSHandler;
                        ...
                        Calendar cal = Calendar.getInstance();
                                                cal.set(2010,12, 31); // Now
                                                quartzMonitorHandler.checkMonitors(new Date(), 30000L, cal.getTime());
                                                quartzJMSHandler.checkTopic(new Date(), 5000L, cal.getTime());
                        log.info("Timers initialized");..|



                        .


                        Which basically means: start now and repeat every 5 seconds. The last parameter (end data was more playing than really necessary...)


                        Using Quarts is more for is aditional features, but not really necessary, you could the Ejb-timer also.


                        Concerning you example: I wouldn't start the timer directly from inside the same bean you're using for polling purposes but that's a question of taste. However you're also using an event with another signature than the Observing method. You should add the  right number of parameters to pollQueue. Also put the bean in a specific scope its now in default scope and I think you need it in Application scope and autocreated. Also have the timer managed by Seam this helps in managing th timer from other beans (starting/stopping/changing tims etc.)


                        Hopefully this helps.


                        Leo

                        • 24. Re: JMS MessageListener in a web application
                          lvdberg

                          Sorry for the repeat, it gave me an error the first time, so I've uploaded a second time.


                          Leo

                          • 25. Re: JMS MessageListener in a web application
                            lvdberg

                            Just want to mention that a comparable discusssion is going on in:


                            http://seamframework.org/Community/SeamJMSActiveMQHibernate


                            Leo

                            • 26. Re: JMS MessageListener in a web application
                              dan.j.allen

                              I think you guys are making this much harder than it has to be. As you have probably figured out, JMS message listeners are not intercepted by Seam because they are instantiated directly by the EJB container. Therefore, you don't get injection. But that doesn't mean you can't get access to Seam components.


                              First, a little background. Seam backs the application scope with a static map. That means that at all times in the same classloader, Seam can access the set of components and any application-scoped instances. The only other interesting context is the event context, which only needs to be created for the request (in this case a message announcement) so that you can use event-scoped components. (As a bonus, Seam can also rev up the business process scope at will, since it's also a non-HTTP scope).


                              Great, so how do you make the scopes active for the duration of the message announcement (i.e. the invocation of onMessage)? Well, peeking at the code used by the asynchronous dispatcher infrastructure, we see the following:


                              Lifecycle.beginCall();
                              // message handling goes here
                              Lifecycle.endCall();



                              Between the Lifecycle methods, you can ask Seam for any component by name.


                              MessageProcessor processor = (MessageProcessor) Component.getInstance("messageProcessor");



                              or by class, which resolves the name from the @Name annotation and looks up by name:


                              MessageProcessor processor = (MessageProcessor) Component.getInstance(MessageProcessor.class);



                              The only downside is that you can't use injection. But you can use injection within the component you are looking up, so this is sort of like the service-locator or JNDI lookup pattern.


                              Here's a complete example:


                              MessagePublisher.java

                              @Name("messagePublisher")
                              public class MessagePublisher {
                                   @In QueueSender testQueueSender;
                              
                                   @In QueueSession queueSession;
                              
                                   private String message;
                              
                                   public void publish() throws JMSException {
                                        if (message != null && message.length() > 0) {
                                             System.out.println("Publishing message: " + message);
                                             testQueueSender.send(queueSession.createTextMessage(message));
                                        }
                                   }
                              
                                   public void setMessage(String message) {
                                        this.message = message;
                                   }
                              
                                   public String getMessage() {
                                        return message;
                                   }
                              
                              }



                              MessageReceiver.java

                              @MessageDriven(activationConfig = {
                                   @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
                                   @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/testQueue")
                              })
                              public class MessageReceiver implements MessageListener {
                              
                                   @Override
                                   public void onMessage(Message message) {
                                        try {
                                             Lifecycle.beginCall();
                                             MessageProcessor processor = (MessageProcessor) Component.getInstance("messageProcessor");
                                             processor.process(((TextMessage) message).getText());
                                        } catch (JMSException e) {
                                             e.printStackTrace();
                                        }
                                        finally {
                                             Lifecycle.endCall();
                                        }
                                   }
                              
                              }



                              MessageProcessor.java

                              @Name("messageProcessor")
                              public class MessageProcessor {
                                   public void process(String message)
                                   {
                                        System.out.println("Received message: " + message);
                                   }
                              }



                              To use Seam components, keep in mind that the listener cannot be a remote client. That's because it would be outside of Seam. Of course, if the remote client is another Seam application, then you can use Seam components from that application. But Seam isn't going to be able to see across the wire.


                              It would be great if we could either provide an EJB interceptor to handle the Seam setup or a ContextualMessageHandlerRequest.


                              public abstract class ContextualMessageHandlerRequest<T> {
                              
                                   private Message message;
                                   private boolean setupRequired = false;
                                   private T delegate;
                              
                                   public ContextualMessageHandlerRequest(Message message) {
                                        this.message = message;
                                        this.setupRequired = !Contexts.isApplicationContextActive() && !Contexts.isEventContextActive();
                                   }
                              
                                   public void run() {
                                        setup();
                                        try {
                                             process();
                                        } finally {
                                             cleanup();
                                        }
                                   }
                                   public abstract void process();
                              
                                   public T getDelegate() {
                                        return delegate;
                                   }
                                   
                                   @SuppressWarnings("unchecked")
                                   public Class<T> getDelegateClass() {
                                        return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                                   }
                                   
                                   public Message getMessage() {
                                        return message;
                                   }
                                   
                                   // convenience method
                                   public TextMessage getTextMessage() {
                                        if (message instanceof TextMessage) {
                                             return ((TextMessage) message);
                                        }
                                        return null;
                                   }
                              
                                   @SuppressWarnings("unchecked")
                                   private void setup() {
                                        if (setupRequired) {
                                             Lifecycle.beginCall();
                                        }
                              
                                        Contexts.getEventContext().set(AbstractDispatcher.EXECUTING_ASYNCHRONOUS_CALL, true);
                                        Class<T> delegateClass = getDelegateClass();
                                        if (delegateClass != null) {
                                             delegate = (T) Component.getInstance(delegateClass);
                                        }
                                   }
                                   
                                   private void cleanup() {
                                        Contexts.getEventContext().remove(AbstractDispatcher.EXECUTING_ASYNCHRONOUS_CALL);
                                        if (setupRequired) {
                                             Lifecycle.endCall();
                                        }
                                   }
                              }



                              Then you can simplify the call (or at least make it more generic) in onMessage to the following:


                              new ContextualMessageHandlerRequest<MessageProcessor>(message) {
                              
                                   @Override
                                   public void process() {
                                        try {
                                             getDelegate().process(getTextMessage().getText());
                                        } catch (JMSException e) {
                                             e.printStackTrace();
                                        }
                                   }
                              }.run();
                              



                              Notice that the delegate is being looked up by type (which Seam uses to resolve a name) based on the generic type parameter. Not required, but certainly an interesting approach.


                              Perhaps a JIRA?

                              • 27. Re: JMS MessageListener in a web application
                                lvdberg

                                Dan,


                                many thanks for this great explanation !!! This is what we needed to know. Maybe not only a JIRA but also an addition to the examples or the Seam  documentation?


                                Leo

                                • 28. Re: JMS MessageListener in a web application
                                  nbhatia.bhatian.comcast.net

                                  Dan, thanks for jumping in - your solution works beautifully!


                                  I would like to add that this thread has been the perfect example of the Seam community at work - users and developers working together to solve a problem and increasing our confidence in the framework.


                                  Leo, I assume you are going to file the JIRA.


                                  There is one issue remaining. I am getting the following warning from JBoss server:


                                  08:48:07.250 TRACE [WorkManager(2)-3] [org.hibernate.impl.SessionImpl] before transaction completion
                                  08:48:07.250 TRACE [WorkManager(2)-3] [org.hibernate.impl.SessionImpl] automatically flushing session
                                  08:48:07.484 WARN  [WorkManager(2)-3] [com.arjuna.ats.arjuna.logging.arjLoggerI18N] [com.arjuna.ats.arjuna.coordinator.TwoPhaseCoordinator_4] TwoPhaseCoordinator.afterCompletion - returned failure for com.arjuna.ats.internal.jta.resources.arjunacore.SynchronizationImple@a17e57
                                  08:48:07.500 TRACE [WorkManager(2)-3] [org.hibernate.impl.SessionImpl] after transaction completion
                                  



                                  I assume this is because I am accessing the database while processing the onMessage(). Don't know what needs to be done to keep the transaction gods happy.


                                  Naresh

                                  • 29. Re: JMS MessageListener in a web application
                                    lvdberg