8 Replies Latest reply on Jan 20, 2011 3:17 AM by Leo van den berg

    Posting notifications to multiple users in Seam

    nraf Newbie

      I'm trying to implement AJAX-based 'notifications' on a website I'm working on. Basically, a user does something on his browser and this causes the pages of other users to be updated. I'm using a4j push to get the other pages to update and it's working relatively well.


      My issue is how I should approach setting out the code. What we first did was create an application scope 'feed handler'.


      We stored a reference to a session-scoped backing bean in a hashmap in this feed handler class (an unregister event was created so that non-active sessions will be removed automatically).


      When a user does something that needs to be reflected to other users, an event is raised and the feed handler loops through each bean in the hashmap, calling a method on that bean which updates some model data (the called method will also raise an event which will cause the a4j push to rerender the required areas when it next polls the server).


      This was working fine in the early stages when we were calling the method on each bean in the hashmap but once we started looking at implementing more complex logic, things started to fall apart.


      A simple example will be to consider Facebook. When a new message is posted, other users will have their browsers updated to indicate a change has been made. But not everyone will get that update - only those who are friends of the current user.


      The issue with the way I'm handling is that any injected members in the objects stored in the hashmap appear as null in the application object. I need a few injected objects to be able to handle the logic in the handler correctly (filtering only those users I want to update for example).


      The only workaround I've found is to create a new object which encapsulates each object I'll need in the handle and store this in the hashmap instead. This works however it's not really scalable and quite an ugly solution.


      Any ideas or suggestions? Would JMS provide a solution to this problem?

        • 1. Re: Posting notifications to multiple users in Seam
          Shervin Asgari Master

          If I have understood you correctly, you basically had a hashmap in application scope, and that is no longer sufficient, because you need to perform some logic before displaying the correct value in the hashmap?


          What I suggest is creating a @Factory that returns whatever you are returning today, but first performs the logic you are after.


          For instance:



             


          @BypassInterceptors
          @Factory
              public Map<K,V> getMap() {
                 //Here you can inject whatever you want by using Component.getInstance("foo");
                 //Or you can directly access the objects using Contexts.lookupInStatefulContext("foo") or Contexts.getSessionContext.get("foo");
                 //Finally based on some filtering you return the correct map
                Map<Foo,Bar> fooMap = (Map) Contexts.getApplicationContext.get("foo");
                return fooMap;
              } 



          • 2. Re: Posting notifications to multiple users in Seam
            Leo van den berg Master

            Hi,


            I certainly would recommend to look for a solution with JMS. Every Seam solution you invent in a bigger application/server park will fail because of the clustering problems. JMS is very scalable and provides with all the messaging filtering you need. You can basically publish everything and let the Topic subscriber figure out what you want to receive.


            Leo


             


            • 3. Re: Posting notifications to multiple users in Seam
              nraf Newbie

              Okay, I've tried moving to a JMS solution but am having issues.


              I was unable to convert my stateful session bean to a MDB (it seems that it's not supported).


              In the end, I managed to get the onMessage() event firing in my global session bean by creating my own instance of TopicPublisher and TopicSession and creating a new connection (initial I was injecting them but couldn't get the onMessage method firing in the SFSB).


              The issue is, inside the event my injected members are all null.


              In the following page: http://seamframework.org/Community/JMSMessageListenerInAWebApplication I found a post my Dan Allen which looked like it would be able to lookup an named bean with all it's injections intact.


              This isn't working for me however. I'm getting a ClassLoadException.


              I was having trouble getting the error to display here so I'm going to post it here: http://icefreze.pastebin.com/RCpJYTzV


              Any ideas or suggestions as to how I should implement this?

              • 5. Re: Posting notifications to multiple users in Seam
                Leo van den berg Master

                Hi,


                There are several threads on this subject. It all comes down to the same problem: Configuration. You should configure the MDB in such a way that it can be detected by Seam so:





                • Have you defined the SeamInterceptor as Annotation in the Bean or at a genaral level (in ejb-jar.xml).

                • Have you defined the JNDI name on the bean or at the generic level,

                • Has the bean a Seam name.



                If these are answered with a Yes, the bean should work fine.


                Leo


                • 6. Re: Posting notifications to multiple users in Seam
                  nraf Newbie

                  Thanks for that. Do you have any examples that show all those elements in place? I'm rather new to Seam and code samples would make things a lot clearer.

                  • 7. Re: Posting notifications to multiple users in Seam
                    nraf Newbie

                    Also, what exactly would be the purpose of the SeamInterceptor in this instance?

                    • 8. Re: Posting notifications to multiple users in Seam
                      Leo van den berg Master

                      Hi,


                      an eaxmple of a bean used by me. Its sole responsibility is relay an incoming JMS message to a Seam event. Mind you the Name annotation is MANDATORY to enable Seam to find the bean an inject other beans.





                      @MessageDriven(activationConfig = {
                                @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
                                @ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/events") })
                      @Name("SeamMDBEventListener")
                      public class EventMDB implements MessageListener {
                      
                           private Log log = LogFactory.getLog(EventMDB.class);
                      
                      
                           /** This is the event link to SEAM, whatever you receive will be notified. */ 
                           @In Events events;
                           
                           @Override
                           public void onMessage(Message message) {
                                
                                String type;
                                try {
                                     
                                     Object object = null;
                                     if (message instanceof javax.jms.ObjectMessage) {
                                          ObjectMessage objmsg = (ObjectMessage) message;
                                          object = objmsg.getObject();
                                     }
                                     
                                     type = message.getStringProperty("eventType");
                                     
                                     log.info("Received an event message: " + type);
                                     if (events != null) {
                                          events.raiseAsynchronousEvent("es.esam.im4u.admin", new Notification(1, ErrorCode._basicEvent.name() ,"Received event type " + type , this.getClass().getSimpleName()) );
                                          if (object == null) events.raiseAsynchronousEvent(type, "empty"); // Always send something
                                          else events.raiseAsynchronousEvent(type, object);
                                     }
                                     else log.error("Impossible to transmit events is NULL");
                                     
                                } catch (JMSException e) {
                                     log.error("Error while receiving an event", e);
                                }
                                
                                
                           }
                      }
                      





                      The Seam interceptor is defined in ejb-jar.xml and is needed to let Seam to do its Job.You can put an annotation on each individual bean, but putting it in the config-file saves you a lot of work:




                      <?xml version="1.0" encoding="UTF-8"?>
                      <ejb-jar xmlns="http://java.sun.com/xml/ns/javaee" 
                               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
                               xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
                               version="3.0">
                         <interceptors>
                            <interceptor>
                               <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
                            </interceptor>
                         </interceptors>
                         <assembly-descriptor>
                            <interceptor-binding>
                               <ejb-name>*</ejb-name>
                               <interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
                            </interceptor-binding>
                         </assembly-descriptor>
                      </ejb-jar>




                      Finally each bean has also a JNDI-name which also can be set as an annotation, but putting it in components.xml saves you other keystrokes.



                      <core:init jndi-pattern="/#{ejbName}/local" />
                      



                      Also be aware that every separete JAR which includes Seam components needs its own seam.properties file. Can be empty, but it has to be there, otherwise Seam won't even peek into the Jar to find out if there is some interesting stuff to handle!!


                      Especially when you have a project which includes EJB with MDB's  you shouldf carefully configure, because the interaction with EJB's is handled differently. With EJB your container takes the lead and Seam more or less borrows its power from the container, but doesn't completely take over as with POJO-components.


                      Hoepfully this helps,


                      Leo