5 Replies Latest reply on Apr 7, 2008 11:46 AM by Vivian Steller

    Inject c:forEach iteration variable in seam component (JSP 2.1, JSF 1.2, JSTL 1.2)

    Vivian Steller Newbie

      Hi all,
      I've problems getting the iteration variable (the var from JSTLs c:forEach) injected into a seam component using JSP as view technology.


      First, the scenario:




      • Seam 2.0.1.CR1

      • JSP 2.1 (!)

      • JSTL 1.2 (provided by container)



      My application Server is JBoss AS 4.2.0.


      Now, some source code:


      JSP file:



      <?xml version="1.0" encoding="UTF-8"?>
      <jsp:root version="2.1"
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:jsp="http://java.sun.com/JSP/Page"
        xmlns:c="http://java.sun.com/jsp/jstl/core">
          ...
          <c:forEach items="#{lecturesView.languageSectors}" var="languageSector">
              ...
              <c:forEach items="#{lecturesView.lectures}" var="lecture">
              ...
              </c:forEach>
              ...
          </c:forEach>
          ...
      </jsp:root>
      



      Seam Component


      @Name("lecturesView")
      @Scope(ScopeType.STATELESS)
      public class LecturesViewHelper {
      ...
          @In(value="#{languageSector}", required=false)
          @Out(required=false)
          LanguageSector languageSector;
      
          public List<LanguageSector> getLanguageSectors() {
              return languageSectorAgent.findAll();
          }
          
          public List<Lecture> getLectures() {
              return lectureAgent.listLectures(languageSector);
          }
      ...
      }
      



      I'd expect that the var languageSector in the JSP c:forEach is exposed to the ELContext's VariableMapper (see item 1. in http://jcp.org/aboutJava/communityprocess/maintenance/jsr052/jstl-12-mr-changeLogPFD.html, I'm not 100% sure if JBoss AS's JSTL implementation does this, but it basically implements JSTL 1.2, that's for sure).



      However, the problem is that I can't access this variable in my seam component (from within the getLectures() method), neither by injecting it as in the source code above nor by resolving the variable explicitely using (in Eclipse Debugger):


      javax.faces.context.FacesContext.getCurrentInstance().
          getELContext().getVariableMapper().resolveVariable("languageSector");



      Isn't that supposed to work? That should have been be the aim of JSTL 1.2, right? Are there other ways to lookup the value? E.g. by accessing the JSP page context? It seems that the JSP ELContext/VariableMapper (i.e. pageContext.getELContext()) is not the same instance as the one accessed by the code snippet above (even if they both are accessed in the same thread)?


      I'm asking this list since I'm not sure if there is a much nicer approach to achieve the same (?) or if this a JBoss EL (2.0.1.GA) issue?


      Thanks for any comments!


        • 1. Re: Inject c:forEach iteration variable in seam component (JSP 2.1, JSF 1.2, JSTL 1.2)
          Pete Muir Master

          It's not entirely clear what you are trying to do, but in general trying to bind/access view layer variables in your business layer is a bad idea, and you shouldn't desgin your app to require it.


          I'm sure there is a better way to do this, but as you don't describe your use case, I can't say what it is...

          • 2. Re: Inject c:forEach iteration variable in seam component (JSP 2.1, JSF 1.2, JSTL 1.2)
            John Ament Master

            the core forEach runs outside of the EL injection capabilities.


            You should be able to do what you're looking for by writing your component like this:



            @Name("lecturesView")
            @Scope(ScopeType.STATELESS)
            public class LecturesViewHelper {
            ...
                @In(required=false)
                @Out(required=false)
                LanguageSector languageSector;
            
                public List<LanguageSector> getLanguageSectors() {
                    return languageSectorAgent.findAll();
                }
                
                public List<Lecture> getLectures(LanguageSector languageSector) {
                    return lectureAgent.listLectures(languageSector);
                }
            ...
            }
            
            



            and your JSP like this


            <?xml version="1.0" encoding="UTF-8"?>
            <jsp:root version="2.1"
              xmlns="http://www.w3.org/1999/xhtml"
              xmlns:jsp="http://java.sun.com/JSP/Page"
              xmlns:c="http://java.sun.com/jsp/jstl/core">
                ...
                <c:forEach items="#{lecturesView.languageSectors}" var="languageSector">
                    ...
                    <c:forEach items="#{lecturesView.getLectures(languageSector)}" var="lecture">
                    ...
                    </c:forEach>
                    ...
                </c:forEach>
                ...
            </jsp:root>
            



            But just to point out... you're bound to run into concurrency issues using your original approach, since the bean is stateless in scope and you're injecting values.  I would think REQUEST is the proper scope you want.

            • 3. Re: Inject c:forEach iteration variable in seam component (JSP 2.1, JSF 1.2, JSTL 1.2)
              Vivian Steller Newbie

              John Ament wrote on Apr 06, 2008 07:55 PM:

                     ...
                      <c:forEach items="#{lecturesView.getLectures(languageSector)}" var="lecture">
                      ...
              




              That was indeed my first attempt. Unfortunately, as the latest Seam documentation describes as well, this is not supported with JSP 2.1 since the compiler complains about the parenthesis. Switching versions in the source code does not help and deploying to a pre-2.1 container would take too much effort :(




              But just to point out... you're bound to run into concurrency issues using your original approach, since the bean is stateless in scope and you're injecting values.  I would think REQUEST is the proper scope you want.



              Good point, thanks for that hint! Is this also true for Stateless session beans? I thought to remember that Seam is able to inject variables into stateless components as well?

              • 4. Re: Inject c:forEach iteration variable in seam component (JSP 2.1, JSF 1.2, JSTL 1.2)
                mars1412 Apprentice


                I thought to remember that Seam is able to inject variables into stateless components as well?

                AFAIK, injection is ok - outjection is not.

                • 5. Re: Inject c:forEach iteration variable in seam component (JSP 2.1, JSF 1.2, JSTL 1.2)
                  Vivian Steller Newbie

                  First, I agree with you that binding view variables to business components is a bad design. At the beginning I started with an approach like John showed below by passing parameters to the agent methods, however this does not work with JSP 2.1. Therefore I created view helper classes (not shown in the original post) which are relevant and valid only for certain views.


                  The view helper class looks similar to this one:


                  @Name("lecturesView")
                  @Scope(ScopeType.STATELESS)
                  public class LecturesViewHelper {
                  
                      @Logger
                      private Log log;
                  
                      @In
                      LanguageSectorAgent languageSectorAgent;
                  
                      @In
                      LectureAgent lectureAgent;
                  
                      @In
                      MagnoliaContent currentContent;
                      
                      @In(value="#{languageSector}", required=false)
                      @Out(required=false)
                      LanguageSector languageSector;
                  
                      /**
                       * Checks the {@link #currentContent} if a {@link LanguageSector} is set and if so
                       * only returns the appropriate {@link LanguageSector}. Otherwise returns
                       * all available {@link LanguageSector}s.
                       * 
                       * @see LanguageSector
                       * @return a list of {@link LanguageSector}s depending on the current content.
                       */
                      public List<LanguageSector> getLanguageSectors() {
                          final String selectedLanguageSector = (String) currentContent.getProperties().get("languageSector");
                  
                          if (StringUtils.isNotEmpty(selectedLanguageSector)) {
                              return asList(languageSectorAgent.findByName(selectedLanguageSector));
                          }
                  
                          return asList(languageSectorAgent.findAll());
                      }
                  ...
                  



                  But let me explain the use case bit more detailed.


                  We build an application that manages language sectors (of a univerity), lectures and courses. Language sectors have multiple lectures, lectures have multiple courses. However, since the business life cycle of those entities is very different we only have unidirectional access  (@ManyToOne) from Lecture to LanguageSector but not vice versa via the entities.


                  @Entity
                  public class Lecture {
                  ...
                  
                      @ManyToOne
                      public LanguageSector getLanguageSector() {
                          return languageSector;
                      }
                  
                      public void setLanguageSector(LanguageSector languageSector) {
                          this.languageSector = languageSector;
                      }
                  
                  ...
                  }
                  



                  We then use JPA queries to find the right Lectures for a LanguageSector, which is done in methods of a stateless session bean:


                  @Stateless
                  @Name("lectureAgent")
                  @AutoCreate
                  public class LectureAgentBean extends AbstractAgent<Lecture> implements LectureAgent {
                  ...
                  
                      public List<Lecture> listLectures(Semester semester, LanguageSector sector) {
                          return namedQuery(QUERY_LECTURES_BY_LANGUAGE_SECTOR).assign(QUERY_PARAMETER_SEMESTER, semester)
                                  .assign(QUERY_PARAMETER_SECTOR, sector)
                                  .find();
                      }
                  ...
                  }
                  



                  So, this was all related to the business layer.


                  In general, what we need to do now is generating a view which displays a list of languageSectors showing each lecture with its courses.


                  Bit more complicated now, the view layer is actually constructed by the Magnolia CMS. In this case we can't use facelets and have to rely on JSP, which is why we have troubles using something like #{agent.method(with, parameters)}. So, the view helper is more a work-a-round than a real solution.


                  My other idea to solve this was make the relationship between LanguageSector and Lecture bidirectional accessible. Thus, having a (maybe @Transient marked) public List<Lecture> getLectures() method in the LanguageSector entity as well. Do you think this would be a more appropriate solution?


                  Thanks very much again for your comments!