6 Replies Latest reply on Oct 22, 2009 4:17 AM by Nikolay Elenkov

    Nice @Unwrap puzzel

    Leo van den berg Master

      Hi guys,


      Yesterday I found a solution (well sort of) for something which still keeps me puzzling. I have made repositories for a numebr of entities to speed-up access. Basicall they are Application scoped beans with a method with the @Unwrap te get a list of entities and a method with @Observer listening to changes. I am use them for all valueObjects (DataDictionaries) and and also to access List of real-time entities which must be accessed by all users. When the Observerd method is called another event is created to warn all users (they have a page with a push in it which is triggered by the event. Everything works fine with the exception of one specific entity.


      Some code: 




      @MappedSuperclass
      @Inheritance(strategy=InheritanceType.JOINED)
      
      public abstract class Organisation implements Serializable  {
      ...}
      
      Entity
      @Table(name="MANAGEMENTCENTRES")
      public abstract class ManagementCentre extends Organisation {
      ..}
      
      @Entity
      @Table(name = "INC_EMERGENCY_CNTRS")
      public class EmergencyCentre extends ManagementCentre {
      ...}
      
      @Entity
      @Table(name = "TRAFFIC_MCS")
      public class TrafficManagementCentre extends ManagementCentre {
      ...}
      
      And the Repository
      
      Name("managementCentreRepository")
      @Scope(ScopeType.APPLICATION)
      @Startup
      @AutoCreate
      public class ManagementCentreRepository implements Serializable {
      
           private static final long serialVersionUID = 6455717316209216130L;
      
           // Framework context variables
           @Logger Log log;
           @In  EntityManager entityManager;
           @In Map<String,String> messages;
           @In Events events;
           
           // Application context variables
           private final static String queryString = 
                "select o " +
                "from ManagementCentre o " +
                " order by o.uniqueOrganisationID desc ";
           
           private List<ManagementCentre> centres;
           
           @Unwrap
           public synchronized List<ManagementCentre> getCentres(){ 
                return centres;
           }
           
           /** This method is called at startup and whenever an important change has occured in the list of 
            * management centres  
            * */
           @Observer(value="es.esam.im4u.centreEvent")
           @SuppressWarnings("unchecked")
           @Transactional
           public synchronized void readCentres(){
                centres = entityManager.createQuery(queryString).getResultList();
                log.info("Caching " + centres.size() + " ManagementCentres ");
               events.raiseAsynchronousEvent("es.esam.im4u.managementCentreRepository.refreshed");
           }
           
      }
      
      
      



      All the other Repositories work fine, but this specific one returns an error when I access it in the view. The relevant code in the view (I want to connect it to a new User:




      <s:decorate id="centreField" template="../layout/edit.xhtml">
       <ui:define name="label">#{messages['operator_managementCentre']}</ui:define>
        <h:selectOneMenu immediate="true" value="#{newOperatorAction.mc}">
          <s:selectItems noSelectionLabel="select"  value="#{managementCentreRepository}" var="c" label="#{c.name}"  />
           <a4j:support event="onchange" eventsQueue="inputQueue" reRender="centreField"   ajaxSingle="true" />
           <s:convertEntity />
         </h:selectOneMenu>
      </s:decorate>
      



      After two days of crunching I replaced the repository with a normal query and everything works fine, but I am missing this specific part of the functionality 


      My main question is if anybody has experienced something like this or is there something really wrong with my perception of @Unwrap?


      Thanks in advance,


      Leo van den Berg












            

        • 1. Re: Nice @Unwrap puzzel
          Nikolay Elenkov Master

          What error are you getting? And how is this repository different from the others?
          Post some working code (repository and view) for comparison.

          • 2. Re: Nice @Unwrap puzzel
            Leo van den berg Master

            Hi Nikolay,


            I've included the UserRepository, which is basically the same (the create and destroy also exist , the create - with @create, - performas the query when the component is created).





            Name("userRepository")
            @Scope(ScopeType.APPLICATION)
            @Startup
            @AutoCreate
            public class UserRepository implements Serializable {
            
                 private static final long serialVersionUID = 6455717316209216130L;
            
                 protected SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy hh:mm");
            
                 
                 // Framework context variables
                 @Logger Log log;
                 @In  EntityManager entityManager;
                 @In Map<String,String> messages;
                 @In Events events;
                 
                 // Application context variables
                 private final static String queryString = 
                      "select distinct " +
                      "  u " +
                      "from User u " +
                      " where " +
                      "   u.userUntil = null " +
                      " order by u.userName desc ";
                 
                 private List<User> users;
                 
                 @Unwrap
                 public synchronized List<User> getUsers(){ 
                      return users;
                 }
                 
                 /** This method is called at startup and whenerver an important change has occured in the list of users,
                 @Observer(value="es.esam.im4u.userEvent")
                 @SuppressWarnings("unchecked")
                 @Transactional
                 public synchronized void readUsers(){
                      users = entityManager.createQuery(queryString).getResultList();
                      log.info("Caching " + users.size() + " Users ");
                      events.raiseAsynchronousEvent("es.esam.im4u.userRepository.refreshed");
                 }
                 
                 
            }
            




            I use this in the following piece of view and everything works as it should be (I use the userRepository in the dataGrid). The User is a subclass of Person, which has basically the same mapping as in the previous example. Person is a @MappedSuperClass and User is a subclass.




            <rich:dataGrid width="100%" id="userGrid" value="#{userRepository}"
                                     var="_user" columnClasses="userPanel userPanel userpanel" columns="3" elements="3">
              <f:facet name="header">Users</f:facet>
               <h:panelGrid columns="1">
               <s:graphicImage width="50" value="#{_user.userPicture.image}" />
               <a4j:commandLink value="#{_user.fullName}" action="#{userManagementAction.selectUser(_user)}"                               eventsQueue="inputQueue" reRender="userDetails" />
               </h:panelGrid>
              <f:facet name="footer">
               <rich:datascroller renderIfSinglePage="false" />
              </f:facet>
            </rich:dataGrid>
                                
             



            If have a number of comparable combinations and they all work with the exception of the most complex on; the ManagementCentre. I've tried the actionBean, excetending the EntityController (to be sure), used it inside an SLSB, but it all has the same result. One of the classes gives an error in the view value is not valid, when I check it, the provided value is NULL. Wjen I look at the query, the entities are included in the SQl and they are in memory (I did some testing with logging and all entities exits and have id's). So basically it refuses to work at specific subclasssesd entities.


            It works for now with the normal Query, but that's not the way I really want it. Any suggestion would be great.


            Leo

            • 3. Re: Nice @Unwrap puzzel
              Nikolay Elenkov Master

              The main difference I see that one (#{userRepository}) is bound to a data grid, while
              the other (#{managementCentreRepository}) is bound to a h:selectOneMenu (via s:selectItems). JSF's
              select components are quite picky about items being the same, so if you are getting a 'value is not valid'
              error, it is probably realted to this. Most likely nothing to do with @Unwrap.
              Are you implementing equals/hashCode in ManagementCentre?


              • 4. Re: Nice @Unwrap puzzel
                Leo van den berg Master

                Hi Nikolay,


                The equality (and HashCode) is based on the id of the entity AND an uniqueCode which I create in a PostPersist method on the bean. UniqueId is a String based on the actual timestamp.


                I checked all entries in the database and they are all very different.


                If it was an equality problem, than I would expect also errors with another subclass. The SelectOneMenu contains a List of all ManagementCentre entitiy (subclasses). So why only an error on a specific subclass while the rest works as expected? When I use a normal query and feed it to the same SelectOneMenu, the application works fine.




                <framework:entity-query name="allMC" 
                          entity-manager="#{entityManager}" 
                          ejbql="select mc from ManagementCentre mc" 
                          order="mc.name" />
                
                




                Leo



                • 5. Re: Nice @Unwrap puzzel
                  Stuart Douglas Master

                  As far as I am aware s:convertEntity does not work properly with detached entities, try using a custom converter.

                  • 6. Re: Nice @Unwrap puzzel
                    Nikolay Elenkov Master

                    Leo van den Berg wrote on Oct 21, 2009 12:26:



                    The equality (and HashCode) is based on the id of the entity AND an uniqueCode which I create in a PostPersist method on the bean. UniqueId is a String based on the actual timestamp.

                    I checked all entries in the database and they are all very different.


                    The issue with JSF is that it requires that the selected item (entity) is in the original list bound to the control (the one returned from
                    @Unwrap). So if for some reason something changes (timestamp?), equals might return false and you will get the dreaded 'Value is not valid'. What you can try is to temporarily replace your equals with one that only checks DB ID's and see if it works.



                    If it was an equality problem, than I would expect also errors with another subclass. The SelectOneMenu contains a List of all ManagementCentre entitiy (subclasses). So why only an error on a specific subclass while the rest works as expected?


                    Well, I don't know really. There must be something different about ManagementCentre and its subclasses. I've had nothing but trouble with the s:selectItems/s:convertEntity combination, so maybe I'm biased, but the symptoms are the same, so I'm betting on the same problem.



                    When I use a normal query and feed it to the same SelectOneMenu, the application works fine.

                    <framework:entity-query name="allMC" 
                              entity-manager="#{entityManager}" 
                              ejbql="select mc from ManagementCentre mc" 
                              order="mc.name" />
                    
                    





                    I missed that one :) I don't use the EntityQuery/Home framework, so not sure, but might have to do with the difference in scope of
                    both components. Set a breakpoint on ManagementCenter.equals just before you select from the list and check what happens in both cases.