6 Replies Latest reply on Mar 31, 2008 1:34 PM by fsat.f_satyaputra.yahoo.co.uk

    Question on @In and Bijection

    fsat.f_satyaputra.yahoo.co.uk

      Hi All,


      I'd like to understand more about how bijection works in Seam, as some of the objects which I have annotated with @In does not get injected under certain circumstances.


      I have a link as such:



      <s:link id="bjtest" 
      view="/bjtest/start.xhtml" action="#{pageBean.prepare()}" 
      value="Start Element Test"/>
      



      Here's my page bean:



      @Name("pageBean")
      @Scope(ScopeType.CONVERSATION)
      @AutoCreate
      public class PageBean implements Serializable {
           
           @In
           protected FormElementSet formElementSet;
      
           @Begin(join=true)
           @RaiseEvent("pageBean.prepare")
           public String prepare() {
                this.formElementSet.initialize();
                return "prepare";
           }
           
           public String save() {
                this.formElementSet.validateSave();
                this.formElementSet.save();
                return "prepare";
           }
      
           public String submit() {
                this.formElementSet.validateSubmit();
                this.formElementSet.save();          
                return "prepare";
           }
           
           // snip...




      The PageBean instance have runtime dependencies to the following 4 classes:



      @Name("formElementSet")
      @Scope(ScopeType.CONVERSATION)
      @AutoCreate
      public class FormElementSet implements Serializable {
           private Set<FormElement> formElements = new TreeSet<FormElement>();
           
           public void initialize() {
                this.formElements.clear();
           }
           
           public void registerFormElement(FormElement... formElementsInput) {
                for (FormElement elem: formElementsInput) {
                     this.formElements.add(elem);
                }
           }
           
           public boolean validateSave() {
                boolean result = true;
                for (FormElement elem: this.getFormElements()) {
                     result = result & elem.validateSave();
                }          
                return result;
           }
           
           // snip...



           
           


      public abstract class BaseFormElement implements FormElement, 
      Serializable, Comparable<FormElement> {
           @In
           protected FormElementSet formElementSet;
      
           @Observer("pageBean.prepare")
           public abstract void register();
      }     
      


           


      @Name("includeFormElementOne")
      @Scope(ScopeType.CONVERSATION)
      @AutoCreate
      public class IncludeFormElementOne extends BaseFormElement {
           
           @Override
           public void register() {
                System.out.println("@@@@@@@@@@@@@@@ [" + this + ".register()]");
                this.formElementSet.registerFormElement(this);
           }
      
           public void save() {
                System.out.println("@@@@@@@@@@@@@@@ [" + this + ".save()]");          
           }
      
      
           public boolean validateSave() {
                System.out.println("@@@@@@@@@@@@@@@ [" + this + ".validateSave()]");
                return true;
           }
           
          // snip....



         


      @Name("includeFormElementTwo")
      @Scope(ScopeType.CONVERSATION)
      @AutoCreate
      public class IncludeFormElementTwo extends BaseFormElement {
           
           @In
           protected IncludeFormElementOne includeFormElementOne;
           
           @Override
           public void register() {
                System.out.println("@@@@@@@@@@@@@@@ [" + this + ".register()] w/ dependency [" + 
      this.includeFormElementOne + "]");
                this.formElementSet.registerFormElement(this);
           }
      
           public void save() {
                System.out.println("@@@@@@@@@@@@@@@ [" + this + ".save()] w/ dependency [" +
       this.includeFormElementOne + "]");          
           }
      
      
           public boolean validateSave() {
                System.out.println("@@@@@@@@@@@@@@@ [" + this + ".validateSave() w/ dependency [" + 
      this.includeFormElementOne + "]");
                return true;
           }
      
           // snip....         



           
      After clicking this s:link:


      <s:link id="bjtest" 
      view="/bjtest/start.xhtml" action="#{pageBean.prepare()}" 
      value="Start Element Test"/>
      



      The #{pageBean.prepare()} method is executed, which in turn raises the event and the event observer kicks in to register #{includeFormElementOne} and #{includeFormElementTwo}. This generates the following System.out, which shows the dependency between #{includeFormElementTwo} towards #{includeFormElementOne}:



      @@@@@@@@@@@@@@@ [com.juggle.seam.bjtest.IncludeFormElementOne@60feaf.register()]
      @@@@@@@@@@@@@@@ [com.juggle.seam.bjtest.IncludeFormElementTwo@613422.register()] 
      w/ dependency [com.juggle.seam.bjtest.IncludeFormElementOne@60feaf]
      
      



      On the subsequent page, I have the button which prompts the save method:



      <h:commandButton id="btnSave" 
      action="#{pageBean.save()}" value="Save">
      </h:commandButton>
      



      This is the resulting System.out:



      @@@@@@@@@@@@@@@ [com.juggle.seam.bjtest.IncludeFormElementOne@60feaf.validateSave()]
      @@@@@@@@@@@@@@@ [com.juggle.seam.bjtest.IncludeFormElementTwo@613422.validateSave() 
      w/ dependency [null]
      @@@@@@@@@@@@@@@ [com.juggle.seam.bjtest.IncludeFormElementOne@60feaf.save()]
      @@@@@@@@@@@@@@@ [com.juggle.seam.bjtest.IncludeFormElementTwo@613422.save()] 
      w/ dependency [null]





      For some reason the reference from #{includeFormElementTwo} to #{includeFormElementOne} shows null, which indicates that the @In annotation fails to be picked up by the BijectionInterceptor.


      My questions are as following:




      1. Is this an expected behaviour?

      2. Is #{includeFormElementTwo} intercepted by the BijectionInterceptor in when the #{pageBean.save()} method is called?

      3. Why would the reference #{includeFormElementTwo} to #{includeFormElementOne} becomes null?




      Apologies if this letter becomes a mouthful, but I really want to understand how bijection works in Seam.


      Thank you very much for your time and your help.


      Regards,


      Felix





        • 1. Re: Question on @In and Bijection
          pmuir

          You can't hold references to Seam component like that (in the tree set). This breaks the lifecycle of those components.

          • 2. Re: Question on @In and Bijection
            dan.j.allen

            I'll admit that I haven't completely dug into why this happens, other than to say that for now I am going to second Pete and say not to hold references to Seam components in a tree.


            Here is my advice to you. Don't make your form elements Seam components. Rather, if you want to be able to reference them using a shorthand syntax (e.g. #{includeFormElementTwo}) then use an event-scoped factory that goes in and pulls it out of the tree. That way, only the tree itself is an actual Seam component and the form elements are just context variable references.

            • 3. Re: Question on @In and Bijection
              dan.j.allen

              After further thinking, I have arrived at the definitive explanation. The problem is that when you pull the component out of the TreeSet, it was not handed to you by the Seam container, so therefore it has not interceptors around it. It lost them when it went into the set. You have to ask the Seam container for each reference that you want to use with interceptors (including bijection). Otherwise, no magic will happen.

              • 4. Re: Question on @In and Bijection
                dan.j.allen

                Also, if you stick to using the set, then you are going to need to pass the FormElementSet into each FormElement and then expose a method on FormElementSet that can look-up sibling FormElement instances. What that basically means is that you have to work with the FormElement instances themselves without the use of bijection. Bijection can only be applied to the FormElementSet itself.

                • 5. Re: Question on @In and Bijection
                  dan.j.allen

                  Ah, one more option. You can re-resolve the FormElement instance using an explicit Seam lookup, which will allow the interceptors to be reapplied.


                  Here is how the validateSave() method would look:



                  public boolean validateSave() {
                      boolean result = true;
                      for (FormElement elem : this.getFormElements()) {
                          // go through Seam container to have interceptors reapplied
                          FormElement e = (FormElement) Component.getInstance(elem.getClass());
                          result = result & e.validateSave();
                      }
                      return result;
                  }


                  • 6. Re: Question on @In and Bijection
                    fsat.f_satyaputra.yahoo.co.uk

                    Thank you, Dan and Pete for the answers.


                    I had a look over the weekend, and I guessed that it was because the IncludeFormElementOne and IncludeFormElementTwo instances were not intercepted by BijectionInterceptor.


                    From both your answers, I believe my guess is correct.


                    I did end up using this block of code to workaround the broken lifecycle:



                    public boolean validateSave() {
                         boolean result = true;
                         for (FormElement elem: this.getFormElements()) {
                              Component newComponent = new Component(elem.getClass());               
                              try {
                                   boolean enforeRequired = newComponent.
                    isLifecycleMethod(elem.getClass()
                    .getMethod("validateSave", new Class[]{}));
                                   newComponent.inject(elem, enforeRequired);
                                   result = result & elem.validateSave();
                                   newComponent.outject(elem, enforeRequired);
                                   newComponent.disinject(elem);
                              } catch (SecurityException e) {
                                   // TODO Auto-generated catch block
                                   e.printStackTrace();
                              } catch (NoSuchMethodException e) {
                                   // TODO Auto-generated catch block
                                   e.printStackTrace();
                              }
                         }          
                         return result;
                    }



                    Basically I had a poke around BijectionInterceptor and tried to replicate what it was doing.


                    Is this any different compared to what Dan's code is doing:


                    public boolean validateSave() {
                        boolean result = true;
                        for (FormElement elem : this.getFormElements()) {
                            // go through Seam container to have interceptors reapplied
                            FormElement e = (FormElement) Component.getInstance(elem.getClass());
                            result = result & e.validateSave();
                        }
                        return result;
                    }



                    Is what I am doing safe, or is there any risk that I might break something related to lifecycle management?