12 Replies Latest reply on May 25, 2007 2:25 PM by Fernando Montaño

    EntityHome for nested entity CRUD?

    Tong Wang Newbie

      Hi,

      I have an entity named Business defined as below:

      @Entity
      public class Business {
      
       @ManyToOne(fetch = FetchType.EAGER)
       @JoinColumn(name = "PARENT_BUSINESS_ID", nullable = true)
       private Business parentBusiness;
      
       @OneToMany(fetch = FetchType.LAZY)
       @JoinColumn(name = "PARENT_BUSINESS_ID", nullable = true)
       private List<Business> childBusinesses = new ArrayList<Business>(0);
      
       ...
      }
      


      As you can see, the Business has a one-to-many (parent to children) relation with itself. I am trying to use EntityHome to simply the CRUD code and ideally, it will only need a single BusinessEdit.xhtml page and a single BusinessHome class. The part where I got stuck is how the child BusinessHome object will get a reference to the parent Business instance, which is needed in the child BusinessHome's wire().

      In a non-nested one-to-many relation, e.g. EntityA (1) <----> EntityB (N), EntityAHome can be injected into EntityBHome because two EntityHome component will have different names. However, in my case, parent and child Home instances have the same name and are in different conversations.

      Any idea that this can be done in an elegant way?

      Thanks in advance.
      wt

        • 1. Re: EntityHome for nested entity CRUD?
          Tong Wang Newbie

          Tried to pass parentBusiness through <f:param>, but got java.lang.IllegalArgumentException. It looks like <f:param> only allows primitive types plus the String type.

          • 2. Re: EntityHome for nested entity CRUD?
            Gavin King Master

            If you use a seam page parameter, you can specify a JSF converter.

            • 3. Re: EntityHome for nested entity CRUD?
              Tong Wang Newbie

              Could you elaborate a little more on this? I only know JSF converter is used to convert between string representation and object type. However, parentBusiness here is a managed entity instance.

              • 4. Re: EntityHome for nested entity CRUD?
                Gavin King Master

                Convert the id, not the entity instance.

                • 5. Re: EntityHome for nested entity CRUD?
                  Tong Wang Newbie

                  Converter is not needed for id, since it's a Long. Now I can pass the id from the page to Home and use getEntityManager().find() to load the parent business entity and wire it up with the child. But we are kind of falling back to the old style, I was hoping that there be a way to inject the parent or its home into the child's Home. It looks to me that Seam's bijection is not general or flexible enough to cover this specific situation.

                  And now, I am getting into a new problem: the parent's Home object does not get cleaned up when creating the child, so the edit page for creating the child still shows values from the parent and thus the wrong set of buttons. Here is how my page looks like:

                  <ui:define name="body">
                  
                   <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
                  
                   <h:form id="business" styleClass="edit">
                  
                   <rich:panel>
                   <f:facet name="header">Edit Business</f:facet>
                  
                   <s:decorate id="nameDecoration" template="layout/edit.xhtml">
                   <ui:define name="label">name</ui:define>
                   <h:inputTextarea id="name"
                   cols="80"
                   rows="3"
                   value="#{businessHome.instance.name}"/>
                   </s:decorate>
                  
                   <s:decorate id="descriptionDecoration" template="layout/edit.xhtml">
                   <ui:define name="label">description</ui:define>
                   <h:inputTextarea id="description"
                   cols="80"
                   rows="3"
                   value="#{businessHome.instance.description}"/>
                   </s:decorate>
                  
                   <div style="clear:both">
                   <span class="required">*</span>
                   required fields
                   </div>
                   </rich:panel>
                  
                   <div class="actionButtons">
                  
                   <h:commandButton id="save"
                   value="Save"
                   action="#{businessHome.persist}"
                   disabled="#{!businessHome.wired}"
                   rendered="#{!businessHome.managed}"/>
                  
                   <h:commandButton id="update"
                   value="Update"
                   action="#{businessHome.update}"
                   rendered="#{businessHome.managed}"/>
                  
                   <h:commandButton id="delete"
                   value="Delete"
                   action="#{businessHome.remove}"
                   rendered="#{businessHome.managed}"/>
                  
                   <s:button id="done"
                   value="Done"
                   propagation="end"
                   view="/BusinessSearch.xhtml"
                   rendered="#{businessHome.managed}"/>
                  
                   <s:button id="cancel"
                   value="Cancel"
                   propagation="end"
                   view="/BusinessSearch.xhtml"
                   rendered="#{!businessHome.managed}"/>
                  
                   </div>
                  
                   <s:div styleClass="actionButtons">
                   <s:button view="/BusinessEdit.xhtml"
                   id="createChild"
                   value="Create Child Business">
                   <f:param name="parentBusinessId" value="#{businessHome.instance.id}"/>
                   </s:button>
                   </s:div>
                  
                   </h:form>
                  
                  </ui:define>


                  <page no-conversation-view-id="/BusinessSearch.xhtml">
                  
                   <begin-conversation nested="true"/>
                  
                   <action execute="#{businessHome.wire}"/>
                  
                   <param name="businessId" value="#{businessHome.businessId}"/>
                   <param name="parentBusinessId" value="#{businessHome.parentBusinessId}"/>
                  
                   <navigation from-action="#{businessHome.persist}">
                   <end-conversation/>
                   <redirect view-id="/BusinessSearch.xhtml"/>
                   </navigation>
                  
                   <navigation from-action="#{businessHome.update}">
                   <end-conversation/>
                   <redirect view-id="/BusinessSearch.xhtml"/>
                   </navigation>
                  
                   <navigation from-action="#{businessHome.remove}">
                   <end-conversation/>
                   <redirect view-id="/BusinessSearch.xhtml"/>
                   </navigation>
                  
                  </page>


                  • 6. Re: EntityHome for nested entity CRUD?
                    Gavin King Master

                    Huh?

                    The only reason you would have stuff not getting "cleaned up" is that you have a conversation.

                    If you have a conversation, why would you need to use page parameters??

                    Sounds like you're in a bit of a muddle here.

                    • 7. Re: EntityHome for nested entity CRUD?
                      Tong Wang Newbie

                      Because the one-to-many relation is on the Business entity itself, both parent and child have the same EntityHome class, which is BusinessHome. Accordingly, they will have the same Seam component name - "businessHome". And I don't think there can be two Seam components with teh same name within one conversation. Then I thought they should live in two separate conversations, that's why I have

                      <begin-conversation nested="true"/>
                      and was hoping whenever BusinessEdit.xhtml is loaded, a new conversation is created. The page parameter "id" is really meant to be passed from the parent conversation to the child conversation.

                      • 8. Re: EntityHome for nested entity CRUD?
                        Gavin King Master

                        Why not just give BusinessHome two different roles?

                        A nested conversation shares the instances from the outer conversation, so your approach certainly won't work.

                        • 9. Re: EntityHome for nested entity CRUD?
                          Hari N Newbie

                          Hi
                          We faced the same situation and we did the following

                          Added entries in the components.xml for the other entityhome .. So something like


                          and we used the reference to the childHome .

                          We added page parameters (in the .page.xml ) for the childentities id .

                          Let me know if this helps

                          Regards
                          Hari

                          • 10. Re: EntityHome for nested entity CRUD?
                            Tong Wang Newbie

                            Thank you both for the help. Now, I got it to work by adding another @Role to the BusinessHome. But, I had to create a second page for creating the child (the page is almost identical to the page creating the parent, except for the different name used for the Home component), which is a little less than ideal: originally, I though I could do this with just one page and now I have to maintain two almost identical pages.

                            One other thing is that I have my BusinessHome with two different names, but both in CONVERSATION scope and seem to work fine, which seems to be against what is stated in the reference doc (section 3.2.9):

                            The @Role annotation lets us define an additional named role for a component, with a different scope
                            Can someone please clarify?

                            Anyway, I'll post my code below and hopefully it will be helpful for others who may face the same issue. Please do let me know if there is any possibility of improvement. Thanks.

                            BusinessHome.java
                            @Name("businessHome")
                            @Scope(CONVERSATION)
                            @Role(name="childBusinessHome", scope=CONVERSATION)
                            public class BusinessHome extends EntityHome<Business> {
                            
                             private Long parentBusinessId;
                            
                             @Logger
                             Log log;
                            
                             public Long getParentBusinessId() {
                             return parentBusinessId;
                             }
                            
                             public void setParentBusinessId(Long parentBusinessId) {
                             this.parentBusinessId = parentBusinessId;
                             }
                            
                             public void setBusinessId(Long id) {
                             setId(id);
                             }
                            
                             public Long getBusinessId() {
                             return (Long) getId();
                             }
                            
                             @Override
                             protected Business createInstance() {
                             Business business = new Business();
                             return business;
                             }
                            
                             public void wire() {
                             if (parentBusinessId != null) {
                             EntityManager em = getEntityManager();
                             Business parentBusiness = em.find(Business.class, parentBusinessId);
                             getInstance().setParentBusiness(parentBusiness);
                             }
                             }
                            
                             public boolean isWired() {
                             return true;
                             }
                            
                             public Business getDefinedInstance() {
                             return isIdDefined() ? getInstance() : null;
                             }
                            
                            }
                            


                            BusinessEdit.xhtml
                            <ui:define name="body">
                            
                             <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
                            
                             <h:form id="business" styleClass="edit">
                            
                             <rich:panel>
                             <f:facet name="header">Edit Business</f:facet>
                            
                             <s:decorate id="nameDecoration" template="layout/edit.xhtml">
                             <ui:define name="label">name</ui:define>
                             <h:inputTextarea id="name"
                             cols="80"
                             rows="3"
                             value="#{businessHome.instance.name}"/>
                             </s:decorate>
                            
                             <s:decorate id="descriptionDecoration" template="layout/edit.xhtml">
                             <ui:define name="label">description</ui:define>
                             <h:inputTextarea id="description"
                             cols="80"
                             rows="3"
                             value="#{businessHome.instance.description}"/>
                             </s:decorate>
                            
                             <div style="clear:both">
                             <span class="required">*</span>
                             required fields
                             </div>
                             </rich:panel>
                            
                             <div class="actionButtons">
                            
                             <h:commandButton id="save"
                             value="Save"
                             action="#{businessHome.persist}"
                             disabled="#{!businessHome.wired}"
                             rendered="#{!businessHome.managed}"/>
                            
                             <h:commandButton id="update"
                             value="Update"
                             action="#{businessHome.update}"
                             rendered="#{businessHome.managed}"/>
                            
                             <h:commandButton id="delete"
                             value="Delete"
                             action="#{businessHome.remove}"
                             rendered="#{businessHome.managed}"/>
                            
                             <s:button id="done"
                             value="Done"
                             propagation="end"
                             view="/BusinessSearch.xhtml"
                             rendered="#{businessHome.managed}"/>
                            
                             <s:button id="cancel"
                             value="Cancel"
                             propagation="end"
                             view="/BusinessSearch.xhtml"
                             rendered="#{!businessHome.managed}"/>
                            
                             </div>
                            
                             <s:div styleClass="actionButtons">
                             <s:button view="/ChildBusinessEdit.xhtml"
                             id="createChild"
                             value="Create Child Business">
                             <f:param name="parentBusinessId" value="#{businessHome.instance.id}"/>
                             </s:button>
                             </s:div>
                            
                             </h:form>
                            
                            </ui:define>
                            


                            BusinessEdit.page.xml
                            <page no-conversation-view-id="/BusinessSearch.xhtml">
                            
                             <begin-conversation join="true"/>
                            
                             <action execute="#{businessHome.wire}"/>
                            
                             <param name="businessId" value="#{businessHome.businessId}"/>
                            
                             <navigation from-action="#{businessHome.persist}">
                             <end-conversation/>
                             <redirect view-id="/BusinessSearch.xhtml"/>
                             </navigation>
                            
                             <navigation from-action="#{businessHome.update}">
                             <end-conversation/>
                             <redirect view-id="/BusinessSearch.xhtml"/>
                             </navigation>
                            
                             <navigation from-action="#{businessHome.remove}">
                             <end-conversation/>
                             <redirect view-id="/BusinessSearch.xhtml"/>
                             </navigation>
                            
                            </page>


                            ChildBusinessEdit.xhtml
                            <ui:define name="body">
                            
                             <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
                            
                             <h:form id="business" styleClass="edit">
                            
                             <rich:panel>
                             <f:facet name="header">Edit Business</f:facet>
                            
                             <s:decorate id="nameDecoration" template="layout/edit.xhtml">
                             <ui:define name="label">name</ui:define>
                             <h:inputTextarea id="name"
                             cols="80"
                             rows="3"
                             value="#{childBusinessHome.instance.name}"/>
                             </s:decorate>
                            
                             <s:decorate id="descriptionDecoration" template="layout/edit.xhtml">
                             <ui:define name="label">description</ui:define>
                             <h:inputTextarea id="description"
                             cols="80"
                             rows="3"
                             value="#{childBusinessHome.instance.description}"/>
                             </s:decorate>
                            
                             <div style="clear:both">
                             <span class="required">*</span>
                             required fields
                             </div>
                             </rich:panel>
                            
                             <div class="actionButtons">
                            
                             <h:commandButton id="save"
                             value="Save"
                             action="#{childBusinessHome.persist}">
                             <f:param name="businessId" value="#{childBusinessHome.instance.id}"/>
                             </h:commandButton>
                            
                             <s:button id="cancel"
                             value="Cancel"
                             propagation="end"
                             view="/BusinessEdit.xhtml"/>
                            
                             </div>
                            
                             </h:form>
                            
                            </ui:define>
                            


                            ChildBusinessEdit.page.xml
                            <page no-conversation-view-id="/BusinessEdit.xhtml">
                            
                             <begin-conversation join="true"/>
                            
                             <action execute="#{childBusinessHome.wire}"/>
                            
                             <param name="parentBusinessId" value="#{childBusinessHome.parentBusinessId}"/>
                            
                             <navigation from-action="#{childBusinessHome.persist}">
                             <end-conversation/>
                             <redirect view-id="/BusinessEdit.xhtml"/>
                             </navigation>
                            
                             <navigation from-action="#{childBusinessHome.update}">
                             <end-conversation/>
                             <redirect view-id="/BusinessEdit.xhtml"/>
                             </navigation>
                            
                             <navigation from-action="#{childBusinessHome.remove}">
                             <end-conversation/>
                             <redirect view-id="/BusinessEdit.xhtml"/>
                             </navigation>
                            
                            </page>


                            • 11. Re: EntityHome for nested entity CRUD?
                              Fernando Montaño Expert

                              In order to avoid to have basically the same page twice, I think you can improve that in two ways:

                              1) Use a page composition for Edit form and pass the component instance as ui:param.

                              2) Declare in your pages.html alias view-id for each case but render only one page. Something like this:

                              
                              <page view-id="/admin/userListing.xhtml">
                               <action execute="#{userList.refresh}"/>
                               <navigation evaluate="#{userList.wired}">
                               <render view-id="/userList.xhtml"/>
                               </navigation>
                               </page>
                              
                              
                              <page view-id="/admin/userListingSpecific.xhtml">
                               <action execute="#{userList.refresh}"/> //do something with the action
                               <navigation evaluate="#{userList.wired}">
                               <render view-id="/userList.xhtml"/>
                               </navigation>
                               </page>
                              
                              


                              With above code I can define some component variables that then I can use to render specific content inside userList.xhtml.
                              userListing.xhtml and userListingSpecific.xhtml do not exist physically they are only declared in pages.xml.

                              HTH.

                              • 12. Re: EntityHome for nested entity CRUD?
                                Fernando Montaño Expert

                                In my last post in point 2) I meant pages.xml not pages.html