1 Reply Latest reply on Sep 29, 2010 10:21 AM by timtimgo

    Strange behaviour of seam component in different roles

    timtimgo

      Hi,
      I was just experimenting with seam @Roles and stumbled upon a weird behaviour. I'm using a List component in two Roles, one called fileListCon, the other one called fileListConAdd. When the page is called, I set some parameters on fileListCon in order to receive a different result list.


      This works, when fileListCon.getResultList() is called before fileListConAdd.getResultList(). When calling the other way around, both return the same result list.
      Does anyone have a clue why this happens?


      Here is my setup:


      The seam component:


      The standard seam List component is modified to support fulltextsearch with hibernate search and lucene.



      package com.ifta.lager.action;
      
      import org.hibernate.Criteria;
      import org.hibernate.Session;
      import org.hibernate.criterion.Restrictions;
      import org.jboss.seam.Component;
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Role;
      import org.jboss.seam.annotations.Roles;
      
      import com.ifta.lager.model.File;
      
      @Name("fileList")
      @Roles( { @Role(name = "fileListConAdd", scope = ScopeType.CONVERSATION),
          @Role(name = "fileListCon", scope = ScopeType.CONVERSATION),
          @Role(name = "fileListConAll", scope = ScopeType.CONVERSATION) })
      public class FileList extends FullTextEntityQuery<File> {
      
        /**
         * 
         */
        private static final long serialVersionUID = 6158710691671984120L;
      
        private String childListOf = "komponenten";
      
        private Integer parentId;
      
        public FileList() {
          setMaxResults(25);
          setOrderColumn("name");
          setOrderDirection("asc");
          setEjbql("select file from File file");
        }
      
        @Override
        protected Class<File> getEntityClass() {
          return File.class;
        }
      
        private final String[] fields = { "name", "beschreibung" };
      
        @Override
        protected String[] getSearchFields() {
          return fields;
        }
      
        @Override
        protected Criteria getCriteria() {
          Session session = (Session) getEntityManager().getDelegate();
          Criteria criteria = session.createCriteria(File.class).createAlias(
              childListOf, "p").add(Restrictions.eq("p.id", parentId));
          if (parentId != null) {
            return criteria;
          }
          return null;
        }
      
        /**
         * @return the childListOf
         */
        public String getChildListOf() {
          return childListOf;
        }
      
        /**
         * @param childListOf
         *          the childListOf to set
         */
        public void setChildListOf(String childListOf) {
          if (!childListOf.equals(childListOf)) {
            parametersChanged();
          }
          this.childListOf = childListOf;
        }
      
        /**
         * @return the parentId
         */
        public Integer getParentId() {
          return parentId;
        }
      
        /**
         * @param parentId
         *          the parentId to set
         */
        public void setParentId(Integer parentId) {
          if (parentId != this.parentId) {
            parametersChanged();
          }
          this.parentId = parentId;
        }
      
        /**
         * http://seamframework.org/Community/AccessComponentName
         * 
         * @return will magically return the current seam component when called from
         *         xhtml (Seam interceptors must be avail
         */
        public Component getComponent() {
          return null;
        }
      
      }



      It's parent:




      package com.ifta.lager.action;
      
      import java.util.List;
      
      import org.apache.lucene.analysis.standard.StandardAnalyzer;
      import org.apache.lucene.queryParser.MultiFieldQueryParser;
      import org.apache.lucene.queryParser.ParseException;
      import org.apache.lucene.queryParser.QueryParser;
      import org.apache.lucene.search.MatchAllDocsQuery;
      import org.apache.lucene.search.Sort;
      import org.apache.lucene.search.SortField;
      import org.hibernate.Criteria;
      import org.hibernate.search.jpa.FullTextEntityManager;
      import org.hibernate.search.jpa.FullTextQuery;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Transactional;
      import org.jboss.seam.framework.EntityQuery;
      import org.jboss.seam.log.Log;
      
      public abstract class FullTextEntityQuery<E> extends EntityQuery<E> {
      
        /**
         * 
         */
        private static final long serialVersionUID = 4876483608027791290L;
      
        private String fulltextSearchString = "*";
      
        @Logger
        Log logger;
      
        private List<E> resultList = null;
      
        private boolean parametersChanged = true;
      
        private Long resultSize;
      
        private Integer firstResult;
      
        private String orderColumn;
      
        private Integer maxResult;
      
        private String name;
      
        /**
         * Returns result list and notifies the corresponding tree to update
         */
        @SuppressWarnings("unchecked")
        @Override
        public List<E> getResultList() {
          if (parametersChanged) {
            parametersChanged = false;
            try {
              FullTextQuery ftq;
      
              // Leading wildcards can easily cause an TooManyClausesException. Thus
              // we just match all documents with a MatchAllDocsQuery
              if (fulltextSearchString.equals("*") || fulltextSearchString.equals("")) {
                ftq = ((FullTextEntityManager) getEntityManager())
                    .createFullTextQuery(new MatchAllDocsQuery(), getEntityClass())
                    .setCriteriaQuery(this.getCriteria());
              } else {
                QueryParser parser = new MultiFieldQueryParser(getSearchFields(),
                    new StandardAnalyzer());
                parser.setAllowLeadingWildcard(true);
                org.apache.lucene.search.Query luceneQuery = parser
                    .parse(fulltextSearchString);
                ftq = ((FullTextEntityManager) getEntityManager())
                    .createFullTextQuery(luceneQuery, getEntityClass())
                    .setCriteriaQuery(this.getCriteria());
              }
      
              if (getOrderColumn() != null) {
                Sort sort = new Sort(new SortField(getOrderColumn(),
                    SortField.STRING, getOrderDirection().toLowerCase()
                        .equals("desc")));
                ftq.setSort(sort);
              }
              if (getFirstResult() != null) {
                ftq.setFirstResult(getFirstResult());
              }
              if (getMaxResults() != null) {
                ftq.setMaxResults(getMaxResults());
              }
              resultSize = new Long(ftq.getResultSize());
              resultList = ftq.getResultList();
              int hash = resultList.hashCode();
              String name = this.name;
      
            } catch (ParseException ex) {
              logger.error("Search terms: #0, Error: #1", fulltextSearchString, ex);
              return null;
            }
          }
          return resultList;
        }
      
        /**
         * dummy method to allow templating
         */
        public void doSearch() {
          parametersChanged = true;
        }
      
        protected abstract String[] getSearchFields();
      
        protected abstract Class<E> getEntityClass();
      
        @Override
        public void setOrderColumn(String orderColumn) {
          if (orderColumn != getOrderColumn())
            parametersChanged = true;
          this.orderColumn = orderColumn;
        }
      
        public String getOrderColumn() {
          return orderColumn;
        }
      
        public void setFirstResult(Integer firstResult) {
          if (firstResult != getFirstResult())
            parametersChanged = true;
          this.firstResult = firstResult;
        }
      
        public Integer getFirstResult() {
          if (firstResult == null) {
            return 0;
          }
          return firstResult;
        }
      
        @Override
        public void setMaxResults(Integer maxResults) {
          if (maxResults != getMaxResults())
            parametersChanged = true;
          this.maxResult = maxResults;
        }
      
        public Integer getMaxResults() {
          return this.maxResult;
        }
      
        @Override
        public Long getResultCount() {
          if (parametersChanged || resultSize == null) {
            this.getResultList();
          }
          return resultSize;
        }
      
        public boolean isPaginated() {
          return maxResult != null;
        }
      
        @Override
        @Transactional
        public boolean isNextExists() {
          return resultList != null && getMaxResults() != null
              && getFirstResult() + getResultList().size() < getResultCount();
        }
      
        public boolean isParametersChanged() {
          return this.parametersChanged;
        }
      
        public void parametersChanged() {
          this.parametersChanged = true;
        }
      
        public void setFulltextSearchString(String fulltextSearchString) {
          parametersChanged = true;
          setFirstResult(0);
          this.fulltextSearchString = fulltextSearchString;
        }
      
        public String getFulltextSearchString() {
          return fulltextSearchString;
        }
      
        public void resetSearch() {
          setFulltextSearchString("*");
        }
      
        protected Criteria getCriteria() {
          return null;
        }
      
        public void setName(String name) {
          this.name = name;
        }
      
      }
      




      On the xhtml page:


      #{fileListCon.component.getName()} #{fileListCon.resultList.hashCode()}  #{fileListCon.resultList.size()}
      #{fileListConAdd.component.getName()} #{fileListConAdd.resultList.hashCode()} #{fileListConAdd.resultList.size()}
      #{fileListCon.component.getName()} #{fileListCon.resultList.hashCode()}  #{fileListCon.resultList.size()}



      This will result in the following output:



      fileListCon 1 0
      fileListConAdd -201530514 25
      fileListCon 1 0


      When resultListConAdd.getResultList() is called first



      #{fileListConAdd.component.getName()} #{fileListConAdd.resultList.hashCode()} #{fileListConAdd.resultList.size()}
      #{fileListCon.component.getName()} #{fileListCon.resultList.hashCode()}  #{fileListCon.resultList.size()}
      #{fileListCon.component.getName()} #{fileListCon.resultList.hashCode()}  #{fileListCon.resultList.size()}



      the result is:



      fileListConAdd -1155361796 25
      fileListCon -1155361796 25
      fileListCon -1155361796 25


      Notice that in the first result, the result count first is 0, then 25 and then again 0. This is the expected bahviour. Also the hashes of first and third resultList are not equal to the one in the middle.
      In the second setup, all hashed and result counts are equal, but the should not.


      I have spent quite a while debugging this but I didn't get anywhere.
      Maybe somebody can give me a hint?


      I greatly appreciate your help :-)


      Timo


           


        • 1. Re: Strange behaviour of seam component in different roles
          timtimgo

          Hello everyone,


          I finally found the reason why my fulltext search did not work with to search requests following each other on the same site.


          I think this is a bug in hibernate search, but I'm not sure about that. I will just post the reason for the missbehaviour and my workaround.


          Reason:


          getResultList() in my class FullTextEntityQuery calls FullTextQuery.getResultList() which calls FullTextQueryImpl.list().


          This calls loader.load():




          Loader loader = getLoader();
          List list = loader.load( infos.toArray( new EntityInfo[infos.size()] ) );





          Which contains the commands causing the missbehaviour:





          ObjectLoaderHelper.initializeObjects( entityInfos, criteria, entityType, searchFactoryImplementor );
          return ObjectLoaderHelper.returnAlreadyLoadedObjectsInCorrectOrder( entityInfos, session );




          First, all entities that match the fulltextquery(everything found in the lucene index) and also the criteria, defined by FullTextEntityQuery, are initialized in the session.
          The next line returns all these initialized entities that match the fulltextquery. The problem at this point is: The additional criteria defined by FullTextEntityQuery are not being used here. As a result, every entity that is in the session and matches the fulltextquery is being returned.
          In my case, everything works fine when calling the query with few results first and afterwards, calling the query with many results. When using it the other way around, as described in my first post, too many results will be initialized in the session and thus be returned.


          My workaround:
          I use the Entities found by Criteria.list() (see ObjectLoaderHelper.initializeObjects(...)).


          Watch the line stating



           ftq.getResultList();
           rList = criteria.list();



          in



           /**
             * Returns result list and notifies the corresponding tree to update
             */
            @SuppressWarnings("unchecked")
            @Override
            public List<E> getResultList() {
              if (parametersChanged) {
                parametersChanged = false;
                try {
                  final FullTextQuery ftq;
          
                  /* Workaround */
                  Criteria criteria = this.getCriteria();
          
                  // Leading wildcards can easily cause an TooManyClausesException. Thus
                  // we just match all documents with a MatchAllDocsQuery
                  // TODO: What if there are many entries and a leading wildcard is used
                  // with for example *a? Will this give an error?
                  if (fulltextSearchString.equals("*") || fulltextSearchString.equals("")) {
                    ftq = ((FullTextEntityManager) getEntityManager())
                        .createFullTextQuery(new MatchAllDocsQuery(), getEntityClass())
                        .setCriteriaQuery(criteria);
                  } else {
                    QueryParser parser = new MultiFieldQueryParser(getSearchFields(),
                        new StandardAnalyzer());
                    parser.setAllowLeadingWildcard(true);
                    org.apache.lucene.search.Query luceneQuery = parser
                        .parse(fulltextSearchString);
                    ftq = ((FullTextEntityManager) getEntityManager())
                        .createFullTextQuery(luceneQuery, getEntityClass())
                        .setCriteriaQuery(criteria);
                  }
          
                  if (getOrderColumn() != null) {
                    Sort sort = new Sort(new SortField(getOrderColumn(),
                        SortField.STRING, getOrderDirection().toLowerCase()
                            .equals("desc")));
                    ftq.setSort(sort);
                  }
                  if (getFirstResult() != null) {
                    ftq.setFirstResult(getFirstResult());
                  }
                  if (getMaxResults() != null) {
                    ftq.setMaxResults(getMaxResults());
                  }
                  resultSize = new Long(ftq.getResultSize());
                  /*
                   * Workaround ftq.getResultList() returns all entities which have been
                   * initialized in the session see:
                   * org.hibernate.search.engine.QueryLoader.load() It first initializes
                   * all objects and then returns all objects which have been initialized.
                   * This works for one call, but not for two calls with the same session.
                   * The second call will additionaly return all objects found in the
                   * first call.
                   */
                  ftq.getResultList();
                  rList = criteria.list();
                  /* End of Workaround */
                  @SuppressWarnings("unused")
                  int hash = rList.hashCode();
                  String name = this.name;
          
                } catch (ParseException ex) {
                  logger.error("Search terms: #0, Error: #1", fulltextSearchString, ex);
                  return null;
                }
              }
              return rList;
            }




          Hope it helps.


          Timo