1 2 Previous Next 19 Replies Latest reply on Jan 2, 2007 9:27 PM by guanwh

    Avoiding the DRY Principle with beans

    dhinojosa

      Question, I find myself doing something wrong with seam beans....and that is copying and pasting ad nauseum and violating the DRY Priniciple (Don't Repeat Yourself). Here is a prime example

      
      package com.evolutionnext.session;
      
      import com.evolutionnext.data.Employee;
      import java.io.Serializable;
      import javax.ejb.Stateful;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      import javax.persistence.PersistenceContextType;
      import org.hibernate.validator.Valid;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      
      
      @Stateful
      @Scope(value=ScopeType.SESSION)
      @Name(value="employeeBean")
      public class EmployeeBean implements Serializable, EmployeeLocal {
      
       @PersistenceContext(unitName="hrsystem",
       type=PersistenceContextType.EXTENDED)
       private EntityManager entityManager;
      
       @In @Valid
       private Employee employee;
      
       /** Creates a new instance of EmployeeBean */
       public EmployeeBean() {
       }
      
       public void setEmployee(Employee employee) {
       this.employee = employee;
       }
      
       @In @Valid
       public Employee getEmployee() {
       return employee;
       }
      
       public String update() {
       entityManager.merge(employee);
       return "Success";
       }
      
       public String create() {
       entityManager.persist(employee);
       return "Success";
       }
      
       public String delete() {
       entityManager.remove(employee);
       return "Success";
       }
      }
      



      All is well with this code...now what happens if I want the same functionality for uh...department, well I would need to copy and paste this code. This would violate the dry principal and make my app fragile in the process.

      One solution I thought would be to create a superclass and refactor my create, update, and delete methods. This is almost an elegant solution but still has some copy/pasting problems. Here is the code...



      package com.evolutionnext.session;
      
      import java.io.Serializable;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      import javax.persistence.PersistenceContextType;
      
      public abstract class PersistentBean implements Serializable, PersistentLocal {
      
       @PersistenceContext(unitName="hrsystem",
       type=PersistenceContextType.EXTENDED)
       private EntityManager entityManager;
      
       private Object object;
      
       public abstract void setObject(Object object);
      
       public abstract Object getObject();
      
       public String update() {
       entityManager.merge(object);
       return "Success";
       }
      
       public String create() {
       entityManager.persist(object);
       return "Success";
       }
      
       public String delete() {
       entityManager.remove(object);
       return "Success";
       }
      }
      


      My EmployeeBean would like this now:

      package com.evolutionnext.session;
      
      import javax.ejb.Stateful;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.annotations.Scope;
      
      /**
       *
       * @author Administrator
       */
      @Stateful
      @Scope(value=ScopeType.SESSION)
      @Name(value="employeeBean")
      public class EmployeeBean extends PersistentBean {
      
       /** Creates a new instance of EmployeeBean */
       public EmployeeBean() {
       }
      
       private Object object;
      
       public void setObject(Object object) {
       this.object = object;
       }
      
       @In(value="employee") @Out(value="employee")
       public Object getObject() {
       return object;
       }
      
      }
      
      


      I am not even sure if this type of subclassing works in Seam. Is this what I have to look to as far as abstraction. Let me know. ;)

        • 1. Re: Avoiding the DRY Principle with beans
          gavin.king

          Yeah, this is a good approach, and should work in Seam. (If it does not, its a bug.)

          • 2. Re: Avoiding the DRY Principle with beans
            bfo81

            Mh... template method pattern, very beautiful. I'm messing around with JSF frameworks and example codes for a while now, and I often wondered why no one did it like this before.

            I'd like to add something. I believe you can implement the setObject()-method in the abstract class PersistenceBean, too, since it should be the same in alle inheriting classes.


            BUT: What about casting? I mean when you access the employee, e.g.

            <h:outputText value="#{employee.name}" />

            then JSF accesses the getName() method of the employee object. But since this one is a java.lang.Object and not a Employee I fear that we might get a MethodNotFoundError. Or is the reflection mechanism in Java smart enough for this?

            • 3. Re: Avoiding the DRY Principle with beans
              gavin.king

               

              "bfo81" wrote:
              BUT: What about casting? I mean when you access the employee, e.g.
              <h:outputText value="#{employee.name}" />

              then JSF accesses the getName() method of the employee object. But since this one is a java.lang.Object and not a Employee I fear that we might get a MethodNotFoundError. Or is the reflection mechanism in Java smart enough for this?


              No problem. EL totally ignores the declared type.

              • 4. Re: Avoiding the DRY Principle with beans
                markfoerstein

                Good point dhinojosa. I kinda liked your approach too. This is a very useful topic.

                Gavin, could you tell us more about your last reply? I confess I didn't get it when you said EL ignores the declared type. Can you provide us more info about it?

                Thanks in advance.

                • 5. Re: Avoiding the DRY Principle with beans

                  JSF will use reflection to call the getName() method on your employee object. As long as this object (in its most narrow form) has that method you should be good to go.

                  In this case it might just be simpler to try it first and ask questions later if it doesn't work. :)

                  • 6. Re: Avoiding the DRY Principle with beans
                    markfoerstein

                    I can't say you're wrong ;-)

                    Thanks for your reply.

                    • 7. Re: Avoiding the DRY Principle with beans
                      dhinojosa

                      I'm going officially "give it a try" tonight, and let all of you know. That is of course, someone else has already tried it, and would like to let us in on the results. ;)

                      • 8. Re: Avoiding the DRY Principle with beans
                        iradix

                        If you're concerned about typing, throw some generics in there.

                        public abstract class PersistentBean<T> implements Serializable, PersistentLocal {
                        
                         @PersistenceContext(unitName="hrsystem",
                         type=PersistenceContextType.EXTENDED)
                         private EntityManager entityManager;
                        
                         public abstract void setObject(T object);
                        
                         public abstract T getObject();
                        
                         public String update() {
                         entityManager.merge(getObject());
                         return "Success";
                         }
                        
                         public String create() {
                         entityManager.persist(getObject());
                         return "Success";
                         }
                        
                         public String delete() {
                         entityManager.remove(getObject());
                         return "Success";
                         }
                        }


                        @Stateful
                        @Name(value="employeeBean")
                        public class EmployeeBean extends PersistentBean<Employee> {
                        
                         public EmployeeBean() {
                         }
                        
                         @In(create = true) @Out
                         private Employee object;
                        
                         public void setObject(Employee object) {
                         this.object = object;
                         }
                        
                         public Employee getObject() {
                         return object;
                         }
                        
                        }


                        Should work. Pretty cool huh? Now not only will your outjected object work in your jsf pages, any other methods you add in your subclass will have the correct type to work with.


                        • 9. Re: Avoiding the DRY Principle with beans
                          dhinojosa

                          Sweet, but with this I think this can be changed from

                           @In(create = true) @Out
                           private Employee object;
                          


                          to

                           @In(create = true) @Out
                           private Employee employee;
                          


                          Very nice...thanks iradix

                          • 10. Re: Avoiding the DRY Principle with beans
                            bfo81

                            Of course, there are many ways to build such a CRUD stuff. But I think one shouldn't persist a new object right after creation, cause then you have an item in your database with empty fields. I mean objects should only be persisted after JSF validation and a click on "Save".

                            In addition to that, I regard a Delete button on the edit page as useful. And this one should not be shown to the user when he just created a new object. Just for existing ones.

                            My approach would be like that:

                            - A page with a list of all existing instances of an entity. Every entity gets an edit link, and at the bottom there's a create button.

                            - Both buttons lead to the edit page. On the edit page there are the fields belonging to that entity, a save button, a cancel button, and a delete button (not for newly created entities).


                            So that's what I suggest:

                            The abstract superclass (it should use generics like iradix suggested):

                            ...
                            
                            private boolean new;
                            //true: Object has just bean created
                            //false: User is editing an existing object
                            
                            public setNew(boolean new) {
                             this.new = new;
                            }
                            
                            public boolean getNew() {
                             return new;
                            }
                            
                            
                            
                            public String create() {
                             setNew(true);
                             setObject(new T());
                             return "editPage";
                            }
                            
                            
                            public String edit() {
                             setNew(false);
                             setObject((T) entityManager.merge(theList.getSelection())); //how to get the entity from the previous page's list is discussed in another topic - so that's something to implement later ;)
                             return "editPage";
                            }
                            
                            
                            public String delete() {
                             entityManager.remove(getObject());
                             return "listPage";
                            }
                            
                            
                            public String save() {
                            
                             if(getNew())
                             entityManager.persist(getObject());
                             else
                             entityManager.merge(getObject());
                            
                             return "listPage";
                            }
                            
                            



                            The concrete bean class:
                            ...
                            
                            @In(required=false) @Out //Don't create here, this is done in the edit() or create() method ;)
                            private Employee employee;
                            
                            public Employee getObject() {
                             return employee;
                            }
                            
                            public void setObject(Employee employee) {
                             this.employee = employee;
                            }
                            ...
                            



                            The list page:
                            ...
                            <h:form>
                             <h:dataTable....>
                            
                             ...
                            
                             <h:column>
                             <h:commandLink value="Edit" action="#{employeeBean.edit}" />
                             </h:column>
                            
                             </h:dataTable>
                            
                            <h:commandLink value="Create new employee" action="#{employeeBean.create}" />
                            
                            </h:form>
                            



                            The edit page:
                            ...
                            <h:form>
                            
                            
                             <inputText value="#{employee.someproperty...}" />
                             ...
                            
                             <h:commandLink value="Save" action="#{employeeBean.save}" />
                             <h:commandLink value="Delete" action="#{employeeBean.delete}" rendered="#{not employeeBean.new}" />
                             <h:commandLink value="Cancel" action="#{employeeBean.cancel}" />
                            
                            </h:form>
                            ...
                            



                            Note: This is just a draft and I wasn't able to test it here. Let me know if you like it or if you would do it an other way. Feel free to add your comments and don't be polite ;).


                            My TODOs (I'm thankful for any hint):

                            - How to put an entity that's been selected on the list page into the editor bean in a convenient way (without violating the "Don't Repeat Yourself" rule ;))

                            - cancel method (I'm not sure if return "pageList" is sufficient cause I'm not sure if the entityManager does some stuff behind my back ;))

                            - How to refresh the list when returning to it. I encounter problems here as the list page always needs an explicit refresh (F5 key) before it shows updated and new entries.

                            - Safety question: "Oh my god, do you really wanna delete this?" ;)


                            • 11. Re: Avoiding the DRY Principle with beans
                              bruce3

                              The generics example above uses the @PersistenceContext annotation in an abstract superclass:

                              public abstract class PersistentBean implements Serializable, PersistentLocal {

                              @PersistenceContext(unitName="hrsystem",
                              type=PersistenceContextType.EXTENDED)
                              private EntityManager entityManager;

                              // Other code which I have omitted

                              }

                              Can anyone point me to EJB3 documentation which specifies that using the @PersistenceContext in an abstract superclass is valid according to the specification and will therefore be portable across application servers?

                              Thanks.

                              • 12. Re: Avoiding the DRY Principle with beans
                                denis-karpov

                                Thanks for your efforts in developing best practices. I think it is important to polish it.

                                1. (small one) In cancel action you should specify immediate="true". If not you will not be able to cancel without posting data back. For instance, you do not need validation if you want to cancel ;-)

                                <h:commandLink value="Cancel" action="#{employeeBean.cancel}" immediate="true" />


                                2. Page flows are extremely convenient and easy to use. It removes all "flow logic" from beans and from views also. You bean methods can be void (return nothing). And you page actions do not need method binding. Thus one view can be reused in different scenarios (page flows). By clicking SAVE, it just signals "save" and in different scenarios can be executed different methods.

                                <h:commandLink value="Save" action="save" />
                                 <h:commandLink value="Delete" action="delete" rendered="#{not employeeBean.new}" />
                                 <h:commandLink value="Cancel" action="cancel" />
                                

                                You page flow logic can be visualized by the eclipse plug in. And changed separately from a bean and view.
                                More over, you can change a page flows without restarting app and server :-)
                                Somewhere on your debug page, just put such an action.

                                public void reloadJBPM() {
                                 Contexts.removeFromAllContexts("org.jboss.seam.core.jbpm");
                                 Component.newInstance("org.jboss.seam.core.jbpm");
                                 }
                                


                                3. For all conditional logic like a context validation, decision making, changing something in some cases, I suggest to consider to use the Drools (jBoss Rules). It fits perfectly to jBpm (thanks to Gavin). See Seam sample ?drools?. After including Drools, in my bean code there is no more "if" sentences :-) almost.

                                4. Look at facelets template capabilities, specially user defined tags (Tag Source Files). With this you can create a building bricks for views.
                                Imaging that you have Person entity.
                                public class Person implements Serializable {
                                 private String fio;
                                 private PassportType passType;
                                 private String passNumber;
                                .........
                                }
                                


                                Tag file:
                                ....
                                <ui:component xmlns="http://www.w3.org/1999/xhtml"
                                 xmlns:f="http://java.sun.com/jsf/core"
                                 xmlns:h="http://java.sun.com/jsf/html">
                                 <div class="panel">
                                 <div class="entry">
                                 <div class="label">FIO</div>
                                 <div class="input">
                                 <input id="#{id}_fio" type="text" jsfc="h:inputText" value="#{person.fio}"/>
                                 <br/><span class="errors"><h:message for="#{id}_fio" /></span>
                                 </div>
                                 </div>
                                 <div class="entry">
                                 <div class="label">Document</div>
                                 <div class="combo">
                                 <h:selectOneMenu id="#{id}_passType" class="combo" value="#{person.passType}" converter="#{PassTypeList.converter}">
                                 <f:selectItems value="#{PassTypeList.selectItems}" />
                                 </h:selectOneMenu>
                                 <br/><span class="errors"><h:message for="#{id}_passType" /></span>
                                 </div>
                                 </div>
                                 <div class="entry">
                                 <div class="label">Document number</div>
                                 <div class="input">
                                 <input id="#{id}_passNumber" type="text" jsfc="h:inputText" value="#{person.passNumber}"/>
                                 <br/><span class="errors"><h:message for="#{id}_passNumber" /></span>
                                 </div>
                                 </div>
                                 </div>
                                 </ui:component>
                                ....
                                


                                page code (*.xhtml)
                                ....
                                <my:person id="good" person=?#{bean.goodPerson}?/>
                                <my:person id="bad" person=?#{bean.badPerson}?/>
                                .....
                                



                                • 13. Re: Avoiding the DRY Principle with beans
                                  bfo81

                                  1) Yes, that's what I found out, too. Thanks ;).

                                  4) Sounds good. At the end of each edit page I have something like

                                  <h:commandButton value="Save" action="#{whateverentityEditor.save}" />
                                  <h:commandButton value="Delete" action="#{whateverentityEditor.delete}" immediate="true" rendered="#{not whateverentityEditor.newOne}" />
                                  <h:commandButton value="Cancel" action="#{whateverentityEditor.cancel}" immediate="true" />


                                  It would be quite nice to have it like that:
                                  <xyz:showEditButtons value="whateverentityEditor" />


                                  Even the list page could get more decorated, so that I only need to enter the columns shown. The surrounding dataTable-Tags, the "edit item" buttons, the "create new" button, and even the "No items found" or other messages shouldn't be typed in by the developer explicitly.

                                  Well, there's a lot of work to do for me. I already have a working list/edit example, but there are so many more ideas. Pagination on the list page is another thing I haven't built in yet.

                                  • 14. Re: Avoiding the DRY Principle with beans

                                    Hi,

                                    I'm trying to implement Session Bean Inheritance, so common business methods (like defaults for search and save) are encapsulated on a superclass, and subclasses overrides just the methods they have a particular behavor. Superclasses can have properties that holds bean state. Multiple interface inheritance is desirable too. Ex:

                                    interface BusinessService {
                                     save();
                                     search();
                                     ...
                                     Object getMyProperty(); // holds some state
                                    }
                                    


                                    class BusinessManager implements BusinessService {
                                     save() { ... };
                                     search() { ... };
                                     ...
                                    
                                     Object myProperty;
                                    
                                     public getMyProperty() {
                                     // defaults to subclasses
                                     return myProperty;
                                     }
                                    }
                                    


                                    interface UserService {
                                     login();
                                    }
                                    


                                    @Stateful
                                    class UserManager extends BusinessManager implements UserService {
                                     login() { ... };
                                    
                                    // overrides default BusinessManager behavior
                                     @Override
                                     save() {
                                     if (checkIfLoginIsUnique())
                                     super.save();
                                     }
                                    }
                                    


                                    When I write Session Beans with no inheritance, everything works fine, but with inheritance, I can't get page rendering work, because JSF can't find superclass properties that holds bean states, and even superclass methods.

                                    com.sun.facelets.tag.TagAttributeException: index.xhtml @14,44
                                    test="#{empty myBean.myProperty}": Bean: myBean$$EnhancerByCGLIB$$e16dcde2, property: myProperty
                                    


                                    java.lang.IllegalArgumentException: method not found: myMethod for component: myBean (check that it is declared on the session bean business interface)
                                    ...
                                    java.lang.NoSuchMethodException: MyService$$EnhancerByCGLIB$$e16dcde2.myMethod()
                                    


                                    Any Help?
                                    Thanx,
                                    Fábio.

                                    1 2 Previous Next