views:

1165

answers:

1

Hi,

I have a domain class named Parent as follows

public class Parent {

    public List<Child> childList = new ArrayList<Child>();

    public void setChildList(List<Child> childList) {
        this.childList = childList;
    }

    public List<Child> getChildList() {
        return childList;
    }

    public void addChild(Child child) {
        childList.add(child);

        child.setParent(this);
    }

    // other getters and setters

}

Notice Parent/Child is a bidirectional relationship. A addChild convenience method takes care of the bidirectional references.

And a Child class as follows

public class Child {

    private Parent parent;

    public Parent getParent() {
        return parent;
    }

    public void setParent(Parent parent) {
        this.parent = parent;   
    }

    private String descripton;

    // other getters and setters

}

I have a Spring form as follows

<form:form method="POST" action="addCommand.xhtml" commandName="command">
    <div>
        <p>1° child:</p>
        <form:input path="childList[0].description"/>    
    </div>
    <div>
        <p>2° child:</p>
        <form:input path="childList[1].description"/>
    </div>
    <div>
        <p>3° child:</p>    
        <form:input path="childList[2].description"/>
    </div>
    <input type="submit"/>
</form:form>

Has someone an idea how to bind a child element through addChild convenience method ? I need it because i want to save a cascace Parent/Child bidirectional relationship in Hibernate.

Added to Jacob's answer:

Hi Jacob,

The purpose of AutoPopulatingList.ElementFactory's createElement(int index) method is sets up each Child created "just in time". Then if you use something as follows:

protected Object formBackingObject(HttpServletRequest request) throws Exception {
    Parent parent = new Parent();
    parent.setChildList(
        new AutoPopulatingList.ElementFactory() {
            public Object createElement(int index) {
                Child child = new Child();

                // You have just added A NEW CHILD to command object
                parent.addChild(child);

                return child;
            }
        });

    return parent;
}

So if i submit THREE Child objects, my Parent command object will have SIX Child objects instead.

You could TEST it according to:

public class BinderTest {

   private Parent parent;

   private MockHttpServletRequest request;
   private ServletRequestDataBinder binder;

   @BeforeMethod
   public void setUp() {
       parent = new Parent();
       parent.setChildList(
        new AutoPopulatingList.ElementFactory() {
            public Object createElement(int index) {
                Child child = new Child();

                // You have just added A NEW CHILD to command object
                parent.addChild(child);

                return child;
            }
        });

       request = new MockHttpServletRequest();
       binder = new ServletRequestDataBinder(parent, "parent")
   }

   @DataProvider(name="bindParameter")
   public Object [][] bindParameter() {
       return new Object [][] {
           {new String [] {"d0", "d1", "d2", "d3", "d4"}}
       };
   }

   @Test(dataProvider="bindParameter")
   public void bind(String [] descriptionArray) {

       // Notice five childs created (descriptionArray.length)
       int i = 0;
       for(String description: descriptionArray)
           request.addParameter("childList[" + i++ + "].description", description);

       // It will fail
       // parent.getChildList().size() return TEN Child objects
       assertEquals(parent.getChildList().size(), descriptionArray.length);
   }

}

So in order to pass it, you need the following

   parent = new Parent();
   parent.setChildList(
    new AutoPopulatingList.ElementFactory() {
        public Object createElement(int index) {
            Child child = new Child();

            // child now reference parent
            child.setParent(parent);

            return child;
        }
    });

When the form is submitted, parent comand object will reference Child objects because they has been added to childList. Both bidirectional references have been set up.

Regards

Arthur Ronald F D Garcia (Java programmer)

Natal/RN - Brazil

+1  A: 

When you bind to childList[1].description, this gets translated behind the scenes into getChildList().get(1).setDescription().

Question: does this run as written? I'd expect it to fail, since the list doesn't have any objects in it to bind to, unless somewhere else (say in a controller) you are adding some empty Child objects to the childList (the alternative to this is to use some kind of lazy list such as Spring's AutoPopulatingList).

If you are either manually adding Child objects, or using a lazy list (which allows you to specify a factory method for creating new objects), I'd set up the parent/child relationship at the point the Child objects are added to the list.

To set up the parent/child relationship if using a lazy list: For example, if you're using Spring's AutoPopulatingList, you can pass in a custom class that implements an ElementFactory interface, which has one method createElement. You could create a ChildElementFactory that takes a Parent object in its constructor, and uses it to set up the relationship.

Maybe code will be clearer:

public class ChildElementFactory implements AutoPopulatingList.ElementFactory {
  private Parent parent;

  public ChildElementFactory(Parent parent) {
     this.parent = parent;
  }

  public Object createElement(int index) {
    Child child = new Child();
    parent.addChild(child);
    return child;
  }
}

public class Parent {
    public List<Child> childList = new AutoPopulatingList(new ChildElementFactory(this));

// and the rest of Parent is the same, as is child
}
JacobM
When the form is submitted, according to getChildList().get(index).setDescription(String description) as said. But what could i do if i use a lazy list in order to set up a bidirectional relationship through addChild convenience method ? I need it because i want to save a cascade Parent/Child bidirectional relationship in Hibernate. regards
Arthur Ronald F D Garcia
obs: i know that i need to set up a comand class before use it.
Arthur Ronald F D Garcia
added to my original answer to elaborate on how to use a lazy list to set up the relationship.
JacobM
Hi Jacob, Thanks for your reply. I have seen in AutoPopulatingList reference documentation the following: AutoPopulatingList is not thread-safe. To create a thread-safe version, use the Collections.synchronizedList(java.util.List) utility methods. See http://docs.huihoo.com/javadoc/spring/2.0/org/springframework/util/AutoPopulatingList.html Regards
Arthur Ronald F D Garcia
Yes, good point about AutoPopulatingList not being thread-safe.
JacobM
Jacob, see a correction about your answer. Regards
Arthur Ronald F D Garcia