11 Replies Latest reply on Oct 19, 2008 5:37 PM by joblini

    Factory and Observer problem

    bashan

      Hi,


      I am trying to delete object from a list. After deleting it, I am activating observer event to reload the data. The data is deleted, the event is activated and the data is reloaded. I can see that the data from the new query is the updated one, but I still see the data on the screen. It is updated only when I leave the view and come back to it again.


      These are the 2 classes used for deleting and getting the data:
      The function deleteVideo responsible for the action


      
      @Name("userHomeAction")
      @Scope(ScopeType.CONVERSATION)
      public class UserHomeAction
      {
        @In
        private FacesMessages facesMessages;
      
        @In
        MediaDAO mediaDAO;
      
        @In
        Session nikoniansDatabase;
      
        @In
        UserDAO userDAO;
      
        @Logger
        private Log log;
      
        @In (required = false)
        User user;
      
        @In (required = true, create = true)
        UserService userService;
      
        public void deleteVideo()
        {
          Integer mediaId = FacesUtil.getRequestParameterAsInteger("mediaId");
          Media media = mediaDAO.getMedia(mediaId);
          media.setStatus(MediaStatus.DELETED);
          nikoniansDatabase.persist(media);
          if (media instanceof Video)
          {
            Events.instance().raiseEvent("user.video.changed");
          }
          else if (media instanceof ForumEntry)
          {
            Events.instance().raiseEvent("user.picture.changed");
          }
          else if (media instanceof Article)
          {
            Events.instance().raiseEvent("user.article.changed");
          }
      
          facesMessages.add(FacesMessage.SEVERITY_INFO, "Media was deleted", media.getMediaId());
        }
      }
      



      he function getUserVideos is responsible for getting the data shown on the view:


      @Name("userService")
      @Scope(ScopeType.CONVERSATION)
      public class UserService
      {
        @In
        UserDAO userDAO;
      
        @In
        User user;
      
        @Out (required = true)
        User serviceUser;
      
        @RequestParameter
        Integer userId;
      
        @Create
        @Begin
        public void init()
        {
          if (userId != null)
          {
            serviceUser = userDAO.getUser(userId);
          }
          else
          {
            serviceUser = user;
          }
        }
      
        @Factory(value="userVideos")
        @Observer("user.video.changed")
        public List<Media> getUserVideos()
        {
          return userDAO.getMedias(Video.class, serviceUser);
        }
      
        @Factory(value="userPicturePosts")
        @Observer("user.picture.changed")
        public List<Media> getUserPicturePosts()
        {
          return userDAO.getMedias(ForumEntry.class, serviceUser);
        }
      
        @Factory(value="userArticles")
        @Observer("user.article.changed")
        public List<Media> getUserArticles()
        {
          return userDAO.getMedias(Article.class, serviceUser);
        }
      }
      




      Am I missing something?

        • 1. Re: Factory and Observer problem

          What are you using to re-render your xhtml? (You need AJAX for this to work). Seam includes Ajax4JSF and RichFaces components that can help you with this, here is the documentation.

          • 2. Re: Factory and Observer problem
            monkeyden

            There is not enough code here to make a determination.  As Francisco pointed out, if you are using AJAX, you will also have to re-render some container/div in the view. If you are not, then I guess it's a typical JSF action invocation.


            How is userVideos exposed in the view?  I see you're returning the list in that @Observer method, but you're not assigning the result to the CONVERSATION scoped list name userVideos.  That's precisely what the factory does, but not in this case.  Here it's being executed as an @Observer, and not assigning the result.  If there is already a list in conversation scope, then you will see the event fired, the new videos loaded, but the list in scope will not receive the new list because it wasn't executed as a @Factory.

            • 3. Re: Factory and Observer problem
              bashan

              I am currently not using Ajax (maybe in the future...). This is a simple JSF command action.


              The ui itself is:


              <ui:composition xmlns="http://www.w3.org/1999/xhtml"
                              xmlns:ui="http://java.sun.com/jsf/facelets"
                              xmlns:f="http://java.sun.com/jsf/core"
                              xmlns:h="http://java.sun.com/jsf/html"
                              xmlns:s="http://jboss.com/products/seam/taglib"
                              xmlns:t="http://myfaces.apache.org/tomahawk"
                              xmlns:view="http://com.nikonians.ui/view"
                              xmlns:path="http://com.nikonians.ui/path"
                              xmlns:n="http://www.nikonians.tv/jsf"
                              template="template.xhtml">
                <ui:define name="content">
                  <h:messages globalOnly="true" warnClass="error" errorClass="error" infoClass="info" layout="table"/>
                  <h:form id="formUserHome">
                    <div id="panelLeft">
                      <div class="title">My Videos</div>
                      <n:videoFull videos="#{userVideos}" isEditable="true"/>
              
                      <div class="title">My Picture Posts</div>
                      <n:pictureFull forumEntries="#{userPicturePosts}"/>
              
                      <div class="title">My Articles</div>
                      <n:articleFull articles="#{userArticles}"/>
                    </div>
                    <div id="panelRight">
                      <div class="title">#{serviceUser.username}</div>
                      <n:userProfile/>
                      <div style="border-top:solid 1px #cccccc; margin-top:10px; margin-bottom:10px;"/>
                      <s:link view="/user.settings.xhtml" value="Change My Settings"/>
                    </div>
                  </h:form>
                </ui:define>
              </ui:composition>



              and the Facelets component is just a loop showing data:


              <ui:component xmlns="http://www.w3.org/1999/xhtml"
                              xmlns:ui="http://java.sun.com/jsf/facelets"
                              xmlns:f="http://java.sun.com/jsf/core"
                              xmlns:h="http://java.sun.com/jsf/html"
                              xmlns:s="http://jboss.com/products/seam/taglib"
                              xmlns:t="http://myfaces.apache.org/tomahawk"
                              xmlns:a4j="http://richfaces.org/a4j"
                              xmlns:rich="http://richfaces.org/rich"
                              xmlns:view="http://com.nikonians.ui/view"
                              xmlns:path="http://com.nikonians.ui/path"
                xmlns:n="http://www.nikonians.tv/jsf">
              
              <h:outputText value="No Videos..." styleClass="titleNikonian" rendered="#{empty videos}"/>
              <t:dataList value="#{videos}" var="video">
                <table width="500">
                  <tr>
                    <td width="5%">
                      <s:link view="/video.show.xhtml" propagation="none">
                        <f:param name="vid" value="#{video.mediaPhysicalId}"/>
                        <h:graphicImage value="#{path:getVideoThumbMedium(video.mediaPhysicalId)}" styleClass="picture"/>
                      </s:link>
                    </td>
                    <td valign="top">
                      <table width="100%" border="0">
                        <tr>
                          <td>
                            <s:link view="/video.show.xhtml" propagation="none" value="#{video.name}" styleClass="linkVideo">
                              <f:param name="vid" value="#{video.mediaPhysicalId}"/>
                            </s:link>
                          </td>
                          <td align="right">
                            #{view:formatDays(video.dateCreated)}
                          </td>
                        </tr>
                        <tr>
                          <td colspan="2" style="border-top:solid 1px #cccccc">
                            <table>
                              <tr>
                                <td>
                                  <n:ratingShow media="#{video}"/>
                                  <f:verbatim>&amp;nbsp;|&amp;nbsp;</f:verbatim>#{video.views} Views<f:verbatim>&amp;nbsp;|&amp;nbsp;</f:verbatim>#{videoService.commentsNum}
                                  Comments<f:verbatim>&amp;nbsp;|&amp;nbsp;</f:verbatim>#{view:formatTime(video.duration)}
                                </td>
                              </tr>
                              <tr>
                                <td>
                                  #{video.description}
                                </td>
                              </tr>
                              <tr>
                                <td>
                                  From<f:verbatim>&amp;nbsp;</f:verbatim><s:link view="/user.media.public.xhtml"
                                                                                 value="#{video.user.username}"
                                                                                 propagation="none"><f:param
                                  name="userId" value="#{video.user.userId}"/></s:link> in #{video.channel.name}
                                  <h:panelGroup rendered="#{isEditable}">
                                    <f:verbatim>&amp;nbsp;|&amp;nbsp;</f:verbatim>
                                    <h:commandLink action="#{userHomeAction.deleteVideo}" value="Delete Video" onclick="if (!confirm('Are you sure you would like to delete video?')) return false;">
                                      <f:param name="mediaId" value="#{video.mediaId}" />
                                    </h:commandLink>
                                  </h:panelGroup>
                                </td>
                              </tr>
                            </table>
                          </td>
                        </tr>
                      </table>
                    </td>
                  </tr>
                </table>
              </t:dataList>
              </ui:component>
              


              • 4. Re: Factory and Observer problem
                bashan

                Sorry, regarding the Factory issue: as far as I understand is supports 2 options:
                1) Initialize a member in the Bean and store the value in the relevant context. This allows: Single initialization, Easy access in the UI and easy access in Java code.
                2) Return a value from a function (This is what I did), which loads data to relevant context, but no member is needed (I simply don't need to use it in the Java code).


                Am I right?
                Should I do things differently?

                • 5. Re: Factory and Observer problem
                  monkeyden

                  I'm not sure your design is the one I would use but you may want to programatically outject the list in getUserVideos(). 


                  List<Media> userVideos = userDAO.getMedias(Video.class, serviceUser);
                  ...
                  Contexts.getConversationContext().set("userVideos", userVideos);
                  



                  or add


                  @Out List<Media> userVideos;
                  


                  to UserService and do the assignment in getUserVideos(), though the semantics of your requirements/design may prohibit the latter.

                  • 6. Re: Factory and Observer problem
                    bashan

                    Your solution seem good. I think I have already used this approach somewhere else.
                    But... shouldn't it be better using the Factory and Observer pattern?


                    By the way, why do you think the design is the one you would use? do you have any other solution? I will be glad to hear, because I don't think I am doing all the right things with Seam and right now I working pretty muck solo on a project so I don't have much feedback...

                    • 7. Re: Factory and Observer problem
                      monkeyden

                      The most important thing is that you understand the purposes of the @Observer and @Factory annotations. 


                      You're trying to use one method to perform two fundamentally different tasks.  The @Factory method is invoked only out of necessity (i.e. when userVideos is requested but it is null).  If there is never any assignment, and the variable never outjected, there will never be any update to the list.  When you raise the event, the @Observer does it's work correctly, but it has no idea of the pre-existing userVideos


                      When @Observer is executed, it does absolutely nothing with the return value because it expects the @Observer (or a data member annotated with @Out) to do that work.  If it didn't then the inherent decoupling of the event model would be degraded.  To put it simply, @Factory is notified when something is needed.  @Observer is notified when something happened.  You can't expect something is needed behavior to happen when the context of the invocation is something happened.  Hope this helps.

                      • 8. Re: Factory and Observer problem
                        bashan

                        I read your reply carefully, but can't understand from it what is the real problem.
                        It seems from the things you write I do everything correct:
                        1) I need the userVideos to be a factory, because I use it in my view. I use it in several views in fact... therefore I don't want to bind it to a specific action bean and I want it to be invoked whenever needed.
                        2) I need the Observer because in one of my action beans, user can delete a video, and since I am in a conversation, the userVideos is loaded only once. So, in order to reload the list I activate the observer, telling the list to be reloaded.


                        As far as I understand (and I have already wrote about it...), the Factory can be used when bounded to a member in the class or when returning a value from a function. I choose the second one. Is it a wrong choice? Is it not working correct with Observer?



                        • 9. Re: Factory and Observer problem
                          monkeyden

                          Did you understand the contrast between something is needed vs. something happened?  That's the crux of the matter.



                          Is it not working correct with Observer?


                          Exactly!  When that method is invoked by way of an event being raised, Seam does not synchronize the state of userVideos just because the observer method has a @Factory annotation.  The method is invoked because the event was fired, not because userVideos was requested and needed some value. 


                          Have a look at the raiseEvent() method of org.jboss.seam.core.Events.  You'll see that Seam doesn't explicitly do anything to synchronize state after the observer methods are executed.  It simply invokes them and correctly assumes each observer will do whatever work it needs to do when that event is fired. 


                          So generally, you need to do something more to synchronize the state when the observer is fired, either by putting an @Out in UserService or doing it programmatically as I explained above.

                          • 10. Re: Factory and Observer problem
                            bashan

                            Finally, I got it...  ;-)
                            Thanks for your patience.

                            • 11. Re: Factory and Observer problem
                              joblini

                              I know that the original posters question has been answered, but I thought that this example, which helped me to understand the @factory @observer pattern, might be useful : AJAX Magic With JSF DataTables in Seam 2.0